进程运行轨迹的跟踪与统计
概述
-
概述:进程从创建(Linux下调用fork())到结束的整个过程就是进程的生命期,进程在其生命期中的运行轨迹实际上就表现为进程状态的多次切换,如进程创建以后会成为就绪态;当该进程被调度以后会切换到运行态;在运行的过程中如果启动了一个文件读写操作,操作系统会将该进程切换到阻塞态(等待态)从而让出CPU;当文件读写完毕以后,操作系统会在将其切换成就绪态,等待进程调度算法来调度该进程执行……
-
任务:
- 基于模板“process.c”编写多进程的样本程序,实现如下功能:
(1) 所有子进程都并行运行,每个子进程的实际运行时间一般不超过30秒;
(2) 父进程向标准输出打印所有子进程的id,并在所有子进程都退出后才退出; - 在Linux 0.11上实现进程运行轨迹的跟踪。基本任务是在内核中维护一个日志文件/var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一log文件中。
- 在修改过的0.11上运行样本程序,通过分析log文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量。可以自己编写统计程序,也可以使用python脚本程序—— stat_log.py ——进行统计。
- 修改0.11进程调度的时间片,然后再运行同样的样本程序,统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。
- 基于模板“process.c”编写多进程的样本程序,实现如下功能:
-
修改的文件
/home/teacher/process.c # 编写多进程
/linux-0.11/init/main.c # 添加log文件描述符
/linux-0.11/kernel/printk.c # 添加用于log文件写的fprintk函数
/linux-0.11/kernel/fork.c # 输出进程的新建、就绪态
/linux-0.11/kernel/sched.c # 输出进程的就绪态、运行态、等待态
/linux-0.11/kernel/exit.c # 输出进程的退出态
/linux-0.11/include/linux/sched.h # 修改时间片
编写多进程样本
- process.c的main()函数内容如下
int main(int argc, char * argv[])
{
int status1, status2;
pid_t pid1, pid2;
if (!(pid1 = fork())) {
cpuio_bound(3, 0, 1);
exit(7);
}
if (!(pid2 = fork())) {
cpuio_bound(5, 0, 1);
exit(9);
}
wait(&status1);
wait(&status2);
printf("pid1: %d\n", pid1);
printf("pid2: %d\n", pid2);
return 0;
}
添加log文件描述符
- 参照文档内容
//……
move_to_user_mode();
/***************添加开始***************/
setup((void *) &drive_info);
// 建立文件描述符0和/dev/tty0的关联
(void) open("/dev/tty0",O_RDWR,0);
//文件描述符1也和/dev/tty0关联
(void) dup(0);
// 文件描述符2也和/dev/tty0关联
(void) dup(0);
(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666);
/***************添加结束***************/
if (!fork()) { /* we count on this going ok */
init();
}
//……
添加fprintk()函数
- 参照文档内容
寻找状态切换点
fork.c中添加新建和就绪
# 函数copy_process()中:
//……
p->tss.trace_bitmap = 0x80000000;
fprintk(3, "%d\t%c\t%d\n", p->pid, 'N', jiffies); // 新建
if (last_task_used_math == current)
//……
p->state = TASK_RUNNING; /* do this last, just in case */
fprintk(3, "%d\t%c\t%d\n", p->pid, 'J', jiffies); // 就绪
return last_pid;
//……
sched.c中添加就绪、等待和运行
# 函数schedule()中:
//......
(*p)->state=TASK_RUNNING;
fprintk(3, "%d\t%c\t%d", (*p)->pid, 'J', jiffies); // 就绪
//......
/*编号为next的进程 运行*/
if(current->pid != task[next] ->pid)
{
/*时间片到时程序 => 就绪*/
if(current->state == TASK_RUNNING)
fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies); // 就绪
fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies); // 运行
}
switch_to(next);
# 函数sys_pause()中:
//......
current->state = TASK_INTERRUPTIBLE;
fprintk(3, "%d\t%c\t%d", current->pid, 'W', jiffies); // 等待
schedule();
# 函数sleep_on()中:
//......
current->state = TASK_UNINTERRUPTIBLE;
fprintk(3, "%d\t%c\t%d", current->pid, 'W', jiffies); // 等待
schedule();
if (tmp)
tmp->state=0;
fprintk(3, "%d\t%c\t%d", tmp->pid, 'J', jiffies); // 就绪
# 函数interruptible_sleep_on()中:
//......
repeat: current->state = TASK_INTERRUPTIBLE;
fprintk(3, "%d\t%c\t%d", current->pid, 'W', jiffies); // 等待
schedule();
if (*p && *p != current) {
(**p).state=0;
fprintk(3, "%d\t%c\t%d", (*p)->pid, 'J', jiffies); // 就绪
goto repeat;
}
*p=NULL;
if (tmp)
tmp->state=0;
fprintk(3, "%d\t%c\t%d", tmp->pid, 'J', jiffies); // 就绪
# 函数wake_up()中:
if (p && *p) {
(**p).state=0;
fprintk(3, "%d\t%c\t%d", (*p)->pid, 'J', jiffies); // 就绪
*p=NULL;
exit.c中添加退出
# 函数do_exit()中:
//.....
current->state = TASK_ZOMBIE;
fprintk(3,"%d\tE\t%d\n",current->pid,jiffies); // 退出
current->exit_code = code;
# 函数sys_exit()中:
//.....
current->state=TASK_INTERRUPTIBLE;
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies); // 退出
schedule();
修改时间片
时间片轮转法——基本原理
- 在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把cpu分配给队首进程,并令其执行一个时间片。时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计数器发出时钟中断请求,调度程序便依据此信号来停止该进程的执行,并将它送往就绪队列的末尾。
- 然后,再把cpu分配给就绪队列中新的队首进程,也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定时间内均能获得一个时间片的CPU执行时间。换言之,系统能在给定的时间内响应所有用户的请求。
时间片大小的影响
- 在时间片轮转算法中,时间片的大小对系统性能有很大的影响,如选择很小的时间片将有利于短作业,因为它能较快完成,但一个时间片内无法完成的长作业会频繁发生中断、上下文切换,从而增加系统的开销;反之,如选择太长的时间片,使得每个进程都能在一个时间片内完成,时间片轮转算法便退化为FCFS算法,无法满足交互式用户的需求。
- 一个较为可取的大小是,时间片略大于一次典型的交互所需要的时间,这样可使大多数进程在一个时间片内完成。
修改sched.h中的时间片
#define INIT_TASK \
{ 0,15,15,
// 上述三个值分别对应 state、counter 和 priority;
- 时间片初值即为priority
- 某个进程剩余的时间片为counter
- switch_to(next)切换的next进程是就绪队列中counter值最大的进程
- 运行态的counter数值会随着时钟中断而不断-1(时钟每10ms中断一次)
参考
实验中所有完整的源码可参考:https://github.com/hoverwinter/HIT-OSLab