进程运行轨迹的跟踪

来源:互联网 发布:淘宝笔记本电脑 编辑:程序博客网 时间:2024/05/21 05:05

一、源代码

/*    process.c    Linux下多进程程序,父进程循环创建NUM_CPROC个子进程,每个子进程执行cpuio_bound程序,父进程循    环打印子进程的PID,之后阻塞等待子进程结束返回。*/#include<stdio.h>#include<unistd.h>#include<time.h>#include<sys/times.h>#include<sys/types.h>#define NUM_CPROC 50#define LAST_TIME 25 #define RATE (LAST_TIME/NUM_CPROC)#define HZ 100void cpuio_bound(int last,int cpu_time,int io_time);int main(int argc, char *argv[]){  pid_t n_cproc[NUM_CPROC];  int i;  for(i=0;i<NUM_CPROC;i++){    n_cproc[i]=fork();//创建子进程    if(n_cproc[i]==0){      cpuio_bound(LAST_TIME, RATE*i, LAST_TIME-RATE*i);//子进程执行cpuio_bound程序      return 0;    }else if(n_cproc[i]<0){      printf("Failed to fork child process %d!", i+1);//创建失败      return -1;    }  }  for(i=0;i<NUM_CPROC;i++)    printf("Child PID: %d\n", n_cproc[i]);//打印子进程PID  wait(NULL);//等待子进程结束(阻塞)  return 0;}//由子进程调用,执行last秒,其中cpu操作花费cpu_time秒,io操作花费io_time秒。void cpuio_bound(int last,int cpu_time,int io_time){  clock_t beg_time,end_time;  int sleep_time;  double num;  while(last>0){  //模拟CPU操作,执行cpu_time秒    beg_time=times(NULL);    num=1.012345    do{      num*=1.010101      end_time=times(NULL);    }while(((end_time-beg_time)/HZ)<cpu_time);    last-=(end_time-beg_time)/HZ;    if(last<0)      break;//模拟IO操作,执行io_time秒    sleep_time=0;    while(sleep_time<io_time){      sleep(1);      sleep_time++;    }    last-=sleep_time;  }}
/*    cal_time.sh    上面的多进程程序写完可以在实体机上先用脚本测一下运行时间*/#!/bin/bashstart=$(date "+%s")./processend=$(date "+%s")time=$((end-start))echo "time used:$time seconds"#输出:time used:25 seconds#本来以为时间会很长(51个进程每个运行25秒),但实际上一共用时25seconds。#这就是多进程的魅力啊
/*    linux-0.11/init/main.c    为了更早的记录追踪进程的状态信息和生命周期,在任务0切换到用户模式下立刻创建文件描述符0,1,2,3分    别和stdin,stdout,stderr,/var/process.log关联。之后创建子进程1,将继承这些文件描述符。我    们在追踪的时候就可以直接在内核空间fprintk进程状态到文件描述符3上,即写入process.log里面。*/    move_to_user_mode();//切换到用户模式    setup((void *)  &drive_info);//加载文件系统    (void)  open("/dev/tty0",O_RDWR,0);//打开/dev/tty0,建立文件描述符0和/dev/tty0的关联    (void)  dup(0); //让文件描述符1也和/dev/tty0关联    (void)  dup(0); //让文件描述符2也和/dev/tty0关联    //在文件描述符3上打开/var/process.log文件(无则创建,有则清空,只写打开,UGO权限为读写)    (void) open("/var/process.log", O_CREAT|O_TRUNC|O_WRONLY,0666);    if (!fork()) { //创建进程1,执行init函数                     init();    }
//linux-0.11/kernel/fork.c//copy_process复制进程 p->start_time = jiffies;//设置子进程开始运行时间(当前滴答数) fprintk(3,"%d\tN\t%d\n",p->pid,jiffies);//打印N (新建)标志 ... p->state = TASK_RUNNING;//子进程处于可执行状态,时间片一切换到即可执行 fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies);//打印J(就绪)标志
//linux-0.11/kernel/sched.c//schedule调度函数if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&(*p)->state==TASK_INTERRUPTIBLE){    (*p)->state=TASK_RUNNING;//可中断睡眠任务得到信号之后置为就绪状态    fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);}...if(current->pid!=task[next]->pid){     if(current->state==TASK_RUNNING)         //当前执行的进程时间片用完了,设为就绪状态          fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies);    //切换到下一个进程,设置下一个进程状态为R(运行)状态     fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies); } switch_to(next);int sys_pause(void){        //转换当前任务状态为可中断的等待状态        current->state = TASK_INTERRUPTIBLE;        if(current->pid!=0)                fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);//打印W(阻塞)标志        schedule();        return 0;}void sleep_on(struct task_struct **p){        struct task_struct *tmp;        if (!p)                return;        if (current == &(init_task.task))                panic("task[0] trying to sleep");        tmp = *p;        *p = current;        current->state = TASK_UNINTERRUPTIBLE;//设置当前进程为不可中断的睡眠状态        fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);        schedule();        //schedule执行别的进程,只有当进程被唤醒重新执行时才会返回执行后续的语句,把比它早进入等        //待队列的一个进程唤醒,置为就绪状态。        if (tmp){                tmp->state=0;                fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);        }}void interruptible_sleep_on(struct task_struct **p){        struct task_struct *tmp;        if (!p)                return;        if (current == &(init_task.task))                panic("task[0] trying to sleep");        tmp=*p;        *p=current;repeat: current->state = TASK_INTERRUPTIBLE;//设置当前进程为可中断的睡眠状态        fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);        schedule();        //只有当进程被唤醒才会返回这里        if (*p && *p != current) {                (**p).state=0;                //进程被唤醒且不是当前任务时置为就绪状态                fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);                goto repeat;        }        *p=NULL;        if (tmp){                tmp->state=0;                //队列空了,如果tmp不为NULL,亦置为就绪状态,原因同上。                fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);        }}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;        }}
//linux-0.11/kernel/exit.c//do_exit执行退出current->state = TASK_ZOMBIE;//设置当前进程为僵死状态fprintk(3,"%d\tE\t%d\n",current->pid,jiffies);//打印E(退出)标志//sys_waitpidif (flag) {//flag被置位说明子进程既不是停止也不是僵死                if (options & WNOHANG)                        return 0;//没有子进程处于退出或者终止态就返回                current->state=TASK_INTERRUPTIBLE;//设置子进程为中断睡眠状态                fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);                schedule();                if (!(current->signal &= ~(1<<(SIGCHLD-1))))                        goto repeat;                else                        return -EINTR;        }

二、运行结果

linux-0.11编译运行process.c结果如图:
这里写图片描述

我们会发现/var/下面多了一个process.log文件,拷贝到实体机上查看
这里写图片描述
//开始阶段进程状态大多数为新建、就绪、运行、阻塞

这里写图片描述
//后面开始有进程终止退出

用Python脚本处理日志文件
这里写图片描述
//这里我们排除了ID为0,1,2,3,4,5,57,58的进程,因为它们不是我们process.c创建的进程,为了后续时间片
//修改之后的比较,我们选择排除它们。

三、修改时间片

/*    进程时间片的设置只存在于进程创建和调度中,这里我们选择将时间片分别设置为原来的一半和两倍,观察    结果。*///linux-0.11/kernel/fork.c        p->counter = p->priority;        p->counter = p->priority/2;//时间片减半        p->counter = p->priority*2;//时间片加倍//Linux-0.11/kernel/sched.c         //就绪进程的counter均为0,则重新给所有进程的counter减半并加上优先级        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)                  if (*p)                          (*p)->counter = ((*p)->counter >> 1) +                                          (*p)->priority;        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)                  if (*p)                          (*p)->counter = ((*p)->counter >> 1) +                                          (*p)->priority/2;//减半        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)                  if (*p)                          (*p)->counter = ((*p)->counter >> 1) +                                          (*p)->priority*2;//加倍                                                                                                        

重复上面的运行步骤,得到结果

这里写图片描述
//时间片减半

这里写图片描述
//时间片加倍

四、结果分析

当时间片分别减半、正常、加倍的时候,CPU burst和IO burst几乎没有什么变化,有变化的只是Turnaround 时间和waiting 时间,这里我们统计一下平均值。

时间片 Turnaround Waiting 减半 2532.14 5.55 正常 2540.00 13.43 加倍 2556.06 29.22



这里我们很容易发现随着时间片的增长,进程的周转时间和等待时间增加。这是因为时间片长了,进程不容易切换到其他进程,即响应其他进程的速度变慢,其他进程等待时间增长(如果是交互式的话即前台响应变慢会更明显),但时间片变长可以减少进程切换次数,增加CPU执行指令的吞吐量。

五、参考资料

  • HIT-OSLAB-MANUAL进程运行轨迹的跟踪
  • UNIX系统中struct tms 分析
  • Linux 进程状态
  • What is meant by CPU Burst and I/O Burst?
0 0
原创粉丝点击