Lab4 【哈工大_操作系统】进程运行轨迹的跟踪与统计

本节将更新哈工大《操作系统》课程第四个 Lab 实验 进程运行轨迹的跟踪与统计。按照实验书要求,介绍了非常详细的实验操作流程,并提供了超级无敌详细的代码注释

实验目的:

  • 掌握 Linux 下的多进程编程技术;
  • 通过对进程运行轨迹的跟踪来形象化进程的概念;
  • 在进程运行轨迹跟踪的基础上进行相应的数据统计,从而能对进程调度算法进行实际的量化评价,更进一步加深对调度和调度算法的理解,获得能在实际操作系统上对调度算法进行实验数据对比的直接经验。

实验任务:

进程在其生命期中的运行轨迹实际上就表现为进程状态的多次切换,如进程创建以后会成为就绪态;当该进程被调度以后会切换到运行态;在运行的过程中如果启动了一个文件读写操作,操作系统会将该进程切换到阻塞态(等待态)从而让出 CPU;当文件读写完毕以后,操作系统会在将其切换成就绪态,等待进程调度算法来调度该进程执行……
1、基于模板 process.c 编写多进程的样本程序,实现如下功能: + 所有子进程都并行运行,每个子进程的实际运行时间一般不超过 30 秒; + 父进程向标准输出打印所有子进程的 id,并在所有子进程都退出后才退出;
2、在 Linux0.11 上实现进程运行轨迹的跟踪。 + 基本任务是在内核中维护一个日志文件 /var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一 log 文件中。
3、在修改过的 0.11 上运行样本程序,通过分析 log 文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量。可以自己编写统计程序,也可以使用 python 脚本程序—— stat_log.py(在 /home/teacher/ 目录下) ——进行统计。
4、修改 0.11 进程调度的时间片,然后再运行同样的样本程序,统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。

一、编写样本程序

编写 process.c 样本程序,实现函数模拟占用 CPU 和 I/O 时间

定义占用 CPU 和 I/O 时间函数:

/** 此函数按照参数占用 CPU 和 I/O 时间* last            :     函数实际占用CPU和I/O的总时间,不含在就绪队列中的时间,>=0是必须的* cpu_time        :     一次连续占用CPU的时间,>=0是必须的* io_time        :     一次I/O消耗的时间,>=0是必须的* 如果 last > cpu_time + io_time,则往复多次占用CPU和I/O* 所有时间的单位为秒*/
void cpuio_bound(int last, int cpu_time, int io_time)
{// tms 结构体记录 用户代码 和 内核代码 运行时间struct tms start_time, current_time;// 长整型,记录时钟滴答数clock_t utime, stime;int sleep_time;while (last > 0){/* CPU Burst */times(&start_time);/* 其实只有t.tms_utime才是真正的CPU时间。但我们是在模拟一个* 只在用户状态运行的CPU大户,就像“for(;;);”。所以把t.tms_stime* 加上很合理。*/do{times(&current_time);utime = current_time.tms_utime - start_time.tms_utime;    // 进程执行用户代码时间stime = current_time.tms_stime - start_time.tms_stime;    // 进程执行内核代码时间} while ( ( (utime + stime) / HZ )  < cpu_time );            // 得到时钟滴答次数,乘10ms得到真正的时间last -= cpu_time;                                            // 运行CPU后剩余时间if (last <= 0 )break;/* IO Burst *//* 用sleep(1)模拟1秒钟的I/O操作 */sleep_time=0;while (sleep_time < io_time){sleep(1);                            // 模拟 I/O操作 占用进程时间sleep_time++;}last -= sleep_time;                        // 运行 CPU + I/O 剩余时间}
}

创建多进程并调用占用时间函数

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>
#include <sys/types.h>#define HZ    100void cpuio_bound(int last, int cpu_time, int io_time);
/*
1.  所有子进程都并行运行,每个子进程的实际运行时间一般不超过30秒;
2.  父进程向标准输出打印所有子进程的id,并在所有子进程都退出后才退出;
*/
int main(int argc, char * argv[])
{pid_t n_proc[10]; /*10个子进程 PID*/int i;for(i=0;i<10;i++){n_proc[i] = fork();        // 父进程-返回子进程ID; 子进程-返回0/*子进程*/if(n_proc[i] == 0){cpuio_bound(20,2*i,20-2*i);     /*每个子进程都占用20s*/return 0;                         /*执行完cpuio_bound 以后,结束该子进程*/}/*fork 失败*/else if(n_proc[i] < 0 ){printf("Failed to fork child process %d!\n",i+1);return -1;}/*父进程继续fork, 此处为父进程 */pid_t father = getpid();        // 获得进程标志符printf("Now is the father's pid: %d\n", father);}/*打印所有子进程PID*/for(i=0;i<10;i++)printf("Child PID: %d\n",n_proc[i]);/*等待所有子进程完成*/wait(&i);  /*Linux 0.11 上 gcc要求必须有一个参数, gcc3.4+则不需要*/ return 0;
}

编译 process.c 样本程序

gcc -o myprocess process.c

二、维护日志文件

把从操作系统启动到系统关机过程中所有的进程运行轨迹都记录在log文件中

1、内核启动时就打开 process.log 文件

init/main.c 文件中添加如下代码:

//……
move_to_user_mode();/***************添加开始***************/
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0);    
(void) dup(0);        
(void) dup(0);        
(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666);
/***************添加结束***************/if (!fork()) {        /* we count on this going ok */init();
}
//……

其中 init() 函数建立了文件描述符 0、1 、2和/dev/tty0的关联,它们分别就是 stdinstdout 和 stderr。这三者的值是系统标准。仿照将把 log 文件的描述符关联到 3。此后 fork() 将会继承这些文件描述符

2、编写向 log 文件写函数 fprintk

在内核状态下无法直接使用write()函数向log文件输出,因此需要自己编写函数。参考了 printk() 和 sys_write() 而写成,向 kernel/printk.c 文件中添加如下函数:

#include "linux/sched.h"
#include "sys/stat.h"static char logbuf[1024];
int fprintk(int fd, const char *fmt, ...)
{va_list args;int count;struct file * file;struct m_inode * inode;va_start(args, fmt);count=vsprintf(logbuf, fmt, args);va_end(args);
/* 如果输出到stdout或stderr,直接调用sys_write即可 */if (fd < 3){__asm__("push %%fs\n\t""push %%ds\n\t""pop %%fs\n\t""pushl %0\n\t"/* 注意对于Windows环境来说,是_logbuf,下同 */"pushl $logbuf\n\t" "pushl %1\n\t"/* 注意对于Windows环境来说,是_sys_write,下同 */"call sys_write\n\t" "addl $8,%%esp\n\t""popl %0\n\t""pop %%fs"::"r" (count),"r" (fd):"ax","cx","dx");}else    
/* 假定>=3的描述符都与文件关联。事实上,还存在很多其它情况,这里并没有考虑。*/{/* 从进程0的文件描述符表中得到文件句柄 */if (!(file=task[0]->filp[fd]))    return 0;inode=file->f_inode;__asm__("push %%fs\n\t""push %%ds\n\t""pop %%fs\n\t""pushl %0\n\t""pushl $logbuf\n\t""pushl %1\n\t""pushl %2\n\t""call file_write\n\t""addl $12,%%esp\n\t""popl %0\n\t""pop %%fs"::"r" (count),"r" (file),"r" (inode):"ax","cx","dx");}return count;
}

使用示例:

// 向stdout打印正在运行的进程的ID
fprintk(1, "The ID of running process is %ld", current->pid); // 向log文件输出跟踪进程运行轨迹
fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'R', jiffies); 
// 进程id、进程状态、滴答数(开机以来经历多少个10ms)

3、向状态切换点添加输出log文件代码

需要对 /kernel 文件夹下的 fork.c, sched.c, exit.c 有所了解。linux 0.11中进程状态有三种:就绪(运行)、等待、退出

  1. 新建进程时
    fork() -> sys_fork()-> kernel/system_call.s -> copy_process(),在 kernel/fork.c 文件中添加输出语句
int copy_process(int nr,……)
{
//    ……
// 设置 start_time 为 jiffiesp->start_time = jiffies;
//  新建进程,向log文件输出,'N'-新建fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'N', jiffies); 
//       ……p->state = TASK_RUNNING;    
//  设置进程状态为就绪,向log文件输出,'J'-就绪fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'J', jiffies); return last_pid;
}
  1. 进入睡眠态

修改 kenrel/sched.c 中的 sys_pause() 和 sleep_on() 和 interruptible_sleep_on()

int sys_pause(void)
{current->state = TASK_INTERRUPTIBLE;// 输出log文件if(current->pid != 0)fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);schedule();return 0;
}void sleep_on(struct task_struct **p)
{struct task_struct *tmp;
//    ……tmp = *p;
// 仔细阅读,实际上是将 current 插入“等待队列”头部,tmp 是原来的头部*p = current;  
// 切换到睡眠态current->state = TASK_UNINTERRUPTIBLE; // 输出log文件fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies); schedule();  // 唤醒队列中的上一个(tmp)睡眠进程。if (tmp){tmp->state=0;//  输出log文件fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies); }
}void interruptible_sleep_on(struct task_struct **p)
{struct task_struct *tmp;…tmp=*p;*p=current;
repeat:    current->state = TASK_INTERRUPTIBLE;//  输出log文件fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies); schedule();if (*p && *p != current) { (**p).state=0;//  输出log文件fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies); goto repeat;}*p=NULL;if (tmp){tmp->state=0;//  输出log文件fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies); }
}

修改 exit.c 中的 sys_waitpid()

int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
//    ……if (flag) {if (options & WNOHANG)return 0;current->state=TASK_INTERRUPTIBLE;//  0号进程是守护进程,cpu空闲的时候一直在waiting,输出它的话是不会通过脚本检查的哦if(current->pid != 0)fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies); schedule();
//    ……
}
  1. 调度算法

调度程序在分配CPU时会修改进程状态,同样需要记录,修改 kernel/sched.c 中的 schedule():

void schedule(void)
{
//    ……if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&(*p)->state==TASK_INTERRUPTIBLE) {(*p)->state=TASK_RUNNING;/*可中断睡眠 => 就绪*/fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);}}//    ……// 如果当前进程和 next 进程不一样,则输出log文件if(current->pid != task[next] ->pid){// 当前进程由 运行 -> 就绪if(current->state == TASK_RUNNING)fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies);// next进程由 就绪 -> 运行fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies);}switch_to(next);
}
  1. 唤醒

修改 kernel/sched.c 中的 wake_ip():

void wake_up(struct task_struct **p)
{if (p && *p) {(**p).state=0;// 唤醒 最后进入等待序列的 进程fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);*p=NULL;}
}
  1. 进程退出

进程结束了运行或在半途中终止了运行,那么内核就需要释放该进程所占用的系统资源,在 kernel/exit.c 文件中添加输出log文件指令:

int do_exit(long code)
{
//    ……current->state = TASK_ZOMBIE;// 退出一个进程,输出log文件fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'E', jiffies); current->exit_code = code;tell_father(current->father);schedule();return (-1);	/* just to suppress warnings */
}

总结:实现log进程全面跟踪,Linux0.11包括六种状态转移,

  • 就绪 到 运行 :schedule()
  • 运行 到 就绪 :schedule()
  • 运行 到 睡眠 : sleep_on() 、interruptible_sleep_on()sys_pause() 、sys_waitpid()
  • 睡眠 到 就绪 :wake_up()
  • 新建
  • 退出

注意:系统无事可做的时候,进程 0 会不停地调用 sys_pause(),以激活调度算法。此时它的状态可以是等待态,等待有其它可运行的进程;也可以叫运行态,它是唯一一个在 CPU 上运行的进程,只不过运行的效果是等待。所以输出log文件时经常要判断当前进程pid是否为零,防止sys_pause() 不断输出log文件

编程输出log文件

cd ~/my_space/OS_HIT/oslab_Lab3/linux-0.11
make     // 编译
cd ..
./run// 在 Bochs 中查看 log 文件
more /var/process.log
sync   // 退出 bochs 时,刷新 cache,确保文件确实写入了磁盘。// 将 process.log 取出到 Ubuntu
sudo ./mount-hdc
cd hdc/var
mv process.log ../..
// 此时即可在Ubuntu中查看log文件了

三、统计时间与调度算法修改

使用 python 代码计算数据统计,计算平均周转时间、平均等待时间和吞吐率

sudo apt-get install python
chmod +x stat_log.py
./stat_log.py process.log 0 1 2 3 4 5 -g | more

按照实验任务书,接下来需要修改时间片大小再进行统计。具体修改 include/linux/sched.h 文件中的 INIT_TASK 宏定义第三个值,即修改15为期望的时间片大小,再运行 stat_log.py 分析

#define INIT_TASK \
/* state etc */    { 0,15,15, \
/* signals */    0,{{},},0, \

依次将时间偏设为1,5,10,15,20,25,50,100,150后,经统计分析log文件可以发现:
1)在一定的范围内,平均等待时间,平均完成时间的变化随着时间片的增大而减小。这是因为在时间片小的情况下,cpu将时间耗费在调度切换上,所以平均等待时间增加。
2)超过一定的范围之后,这些参数将不再有明显的变化,这是因为在这种情况下,RR轮转调度就变成了FCFS先来先服务了。随着时间片的修改,吞吐量始终没有明显的变化,这是因为在单位时间内,系统所能完成的进程数量是不会变的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/444331.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JUC高并发编程10:线程池

1 线程池概述 1.1 线程池简介 线程池&#xff08;Thread Pool&#xff09;是一种线程使用模式。在多线程编程中&#xff0c;线程的创建和销毁会带来一定的开销&#xff0c;尤其是在处理大量短时间任务时&#xff0c;频繁的线程创建和销毁会导致调度开销增加&#xff0c;进而影…

Java 集合 Collection常考面试题

理解集合体系图 collection中 list 是有序的,set 是无序的 什么是迭代器 主要遍历 Collection 集合中的元素,所有实现了 Collection 的集合类都有一个iterator()方法,可以返回一个 iterator 的迭代器。 ArrayList 和 Vector 的区别? ArrayList 可以存放 null,底层是由数…

【算法】滑动窗口(续)

一、将x减到0的最小操作数 1658. 将 x 减到 0 的最小操作数 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&#xff0c;然后从 x 中减去该元素的值。请注意&#xff0c;需要…

2024长城杯WP

WEB SQLUP 打开题目给了一个登录页面结合名字猜测为SQL注入 查看源码发现有hint提示开发者使用的是模式匹配 所以我尝试使用%来模糊匹配&#xff0c;登陆成功 usernameadmin&password% 进入面板之后发现有一个文件上传功能 尝试上传php文件&#xff0c;结果被waf&#xff0…

【银河麒麟高级服务器操作系统】安全配置基线相关分析全过程及解决方案

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 服务器环境以及配置 【机型】物理机或虚机 【…

SpringBoot开发——整合Admin监控服务

文章目录 1、SpringBoot-Admin简介2、SpringBoot整合Admin监控服务2.1 创建SpringBoot-Admin项目(服务端)2.1.1 创建一个SpringBoot项目2.1.2 选择相关依赖2.1.3 启用Admin监控服务2.1.4 启用项目2.2 配置需要被监听的项目(客户端)2.2.1 被监听的项目添加相关依赖2.2.2 配置被…

Redis高级篇 —— 分布式缓存

Redis高级篇 —— 分布式缓存 文章目录 Redis高级篇 —— 分布式缓存1 Redis持久化1.1 RDB1.2 RDB的fork原理1.3 RDB总结1.4 AOF持久化1.5 RDB和AOF的对比 2 Redis主从2.1 搭建主从架构2.2 数据同步原理2.2.1 全量同步2.2.2 增量同步 3 Redis哨兵3.1 哨兵的作用和原理3.1.1 哨兵…

kafka和zookeeper单机部署

安装kafka需要jdk和zookeeper环境&#xff0c;因此先部署单机zk的测试环境。 zookeeper离线安装 下载地址&#xff1a; zookeeper下载地址&#xff1a;Index of /dist/zookeeper 这里下载安装 zookeeper-3.4.6.tar.gz 版本&#xff0c;测试环境单机部署 上传服务器后解压缩 …

Python酷库之旅-第三方库Pandas(142)

目录 一、用法精讲 641、pandas.Timestamp.hour属性 641-1、语法 641-2、参数 641-3、功能 641-4、返回值 641-5、说明 641-6、用法 641-6-1、数据准备 641-6-2、代码示例 641-6-3、结果输出 642、pandas.Timestamp.is_leap_year属性 642-1、语法 642-2、参数 6…

使用Python编写你的第一个算法交易程序

背景 Background ​ 最近想学习一下量化金融&#xff0c;总算在盈透投资者教育&#xff08;IBKRCampus&#xff09;板块找到一篇比较好的算法交易入门教程。我在记录实践过程后&#xff0c;翻译成中文写成此csdn博客&#xff0c;分享给大家。 ​ 如果你的英语好可以直接看原文…

用FPGA做一个全画幅无反相机

做一个 FPGA 驱动的全画幅无反光镜数码相机是不是觉得很酷&#xff1f; 就是上图这样。 Sitina 一款开源 35 毫米全画幅 (3624 毫米) CCD 无反光镜可换镜头相机 (MILC)&#xff0c;这个项目最初的目标是打造一款数码相机&#xff0c;将 SLR [单镜头反光] 相机转换为 DSLR [数码…

SpringBoot 集成 Redis

一&#xff1a;SpringBoot 集成 Redis ①Redis是一个 NoSQL&#xff08;not only&#xff09;数据库&#xff0c; 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器&#xff1b;常用的数据类型&#xff1a; string , hash ,set ,zset , list ③通过Redis客…

初阶C语言-结构体

一.结构体的声明 1.结构体类型的声明 1.1结构的基础知识 结构是一些值的集合&#xff0c;这些值称为称为变量。结构的每个成员可以是不同类型的变量。 1.2结构的声明 struct tag //struct是结构体关键字&#xff0c;tag是结构体类型名称 { member - list;//成员变…

D26【python 接口自动化学习】- python 基础之判断与循环

day26 语句嵌套 学习日期&#xff1a;20241003 学习目标&#xff1a;判断与循环&#xfe63;-36 语句嵌套&#xff1a;如何处理多重嵌套的问题&#xff1f; 学习笔记&#xff1a; 语句嵌套的用途 在条件语句中使用另外一个条件语句 在循环中使用条件语句 多重循环 总结 1…

linux查看k8s的开机启动状态 systemctl is-enabled 查看开机启动状态

查看k8s的开机启动状态 在Kubernetes中&#xff0c;通常使用systemd来管理服务的启动。但是&#xff0c;Kubernetes节点上的服务可能不是由systemd直接管理&#xff0c;而是通过kubelet服务来管理。因此&#xff0c;检查Kubernetes节点的开机启动状态&#xff0c;你需要检查ku…

Unity网络开发 - C#开源网络通信库PESocket的使用

概述 在现代多人在线游戏中&#xff0c;稳定且高效的网络通信是确保游戏体验的关键。本文将探讨如何利用C#开源网络通信库PESocket来构建一个简单的Unity客户端与.NET控制台服务器之间的实时消息传递系统。通过本例&#xff0c;读者不仅能够了解PESocket的基本用法&#xff0c…

稀土抗紫外屏蔽剂的用途

稀土抗紫外屏蔽剂具有光、热稳定性好&#xff0c;可高效吸收/有效屏蔽280-400nm范围内的紫外线&#xff0c;无二次氧化过程的缺点&#xff0c;彻底解决产品因紫外线原因造成的变质和老化问题&#xff0c;并且具有添加量小、无毒、不易析出等优点。 稀土抗紫外屏蔽剂的用途只要有…

安全网络架构

网络安全解决方案是指通过一系列技术和措施来保护网络系统和数据的安全。它涉及多个方面&#xff0c;包括网络设备的防护、数据的加密和备份、安全策略的制定和执行等。以下是一些常见的网络安全解决方案&#xff1a; 防火墙&#xff1a;防火墙是一种硬件或软件设备&#xff0c…

qt+opengl 实现纹理贴图,平移旋转,绘制三角形,方形

1 首先qt 已经封装了opengl&#xff0c;那么我们就可以直接用了&#xff0c;这里面有三个函数需要继承 virtual void initializeGL() override; virtual void resizeGL(int w,int h) override; virtual void paintGL() override; 这三个函数是实现opengl的重要函数。 2 我们…

Leetcode 买卖股票的最佳时机

这段代码的目的是解决“买卖股票的最佳时机”这个问题&#xff0c;即在给定的股票价格数组中&#xff0c;找到一次买入和卖出所能获得的最大利润。 算法思想&#xff1a; 定义两个变量&#xff1a; minPrice: 这个变量用于记录迄今为止遇到的最小股票价格&#xff08;买入价格…