進程運行軌跡的跟蹤

一、源代碼

/*
    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 100

void 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/bash
start=$(date "+%s")
./process
end=$(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_waitpid
if (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執行指令的吞吐量。

五、參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章