一, 实验内容

1, 在Linux0.11上实现进程运行轨迹的跟踪。基本任务是在内核中维护一个日志文件/var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一log文件中.

2, 写一个统计分析函数, 通过分析log文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量.

3, 修改0.11进程调度的时间片,体会不同时间片带来的差异。

注意: /var/process.log文件的格式必须为:

pid    X    time

  其中:pid是进程的ID;

       X可以是N,J,R,W和E中的任意一个,分别表示进程新建(N)、进入就绪态(J)、进入运行态(R)、进入阻塞态(W)和退出(E);

     time表示X发生的时间。这个时间不是物理时间,而是系统的滴答时间(tick);

 

二, 实验步骤

1, 生成log文件

  为了能尽早地开始记录进程运行轨迹, 必须在操作系统初始化之后就打开var/process.log文件. 注意到init/main.c中的init()函数前面有如下代码:

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关联

  这段代码建立了文件描述符0、1和2,它们分别就是stdin、stdout和stderr。类似的, 可以建立文件描述符3, 并关联到文件var/process.log. 但是, 这个init()函数是fork()之后的子进程, 如果在这里关联文件描述符3的话, 未免有点为时过晚了. 所以需要把这段代码移动到init/main()函数中, 放在main()函数中的move_to_user_mode()之后, 同时加上打开log文件的代码. 修改之后的init/main()函数代码如下:

......

void main(void)    
{    
    ......
    mem_init(main_memory_start,memory_end);     // 主内存区初始化                    (mm/memory.c)
    trap_init();                                // 中断向量初始化                    (kernel/traps.c)
    blk_dev_init();                             // 块设备初始化                      (blk_drv/ll_rw_blk.c)
    chr_dev_init();                             // 字符设备初始化                    (chr_drv/tty_io.c)
    tty_init();                                 // tty初始化(键盘、显示器)            (chr_dev/tty_io.c)
    time_init();                                // 设置开机启动时间
    sched_init();                               // 调度程序初始化                    (kernel/sched.c)
    buffer_init(buffer_memory_end);             // 缓冲管理初始化, 建内存链表等
    hd_init();                                  // 硬盘初始化                        (blk_drv/hd.c)
    floppy_init();                              // 软驱初始化                        (blk_drv/floppy.c)
    sti();                                      // 开启中断
    move_to_user_mode();                        // 移动到用户模式下执行               (include/asm/system.h)

/***************添加开始***************/
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0);    //建立文件描述符0和/dev/tty0的关联
(void) dup(0);        //文件描述符1也和/dev/tty0关联
(void) dup(0);        //文件描述符2也和/dev/tty0关联
(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666);  // 打开log文件的参数的含义是建立只写文件,如果文件已存在则清空已有内容。文件的权限是所有人可读可写。    
/***************添加结束***************/

    if (!fork()) {
        init();                                 // 在新建的子进程(任务1即init进程)中执行
    }

    for(;;) pause();
}

......

  这样,文件描述符0、1、2和3就在进程0中建立了, 虽然以后的进程都是进程0的子进程, 但是由于在后面的进程又会重新建立文件描述符, 所以这里添加的文件描述符只在进程0和进程1中有效. 所以, 如果要向文件写数据, 就必须先建立文件描述符, 然后再调用write(), 但是这样做的话, 非常麻烦, 需要在每个进程中都再关联一次文件描述符3, 所以不如写一个类似于fprintf()函数, 来统一处理. 可以模仿内核中的函数printk()的写法, 具体代码如下:

#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);

    if (fd < 3)    /* 如果输出到stdout或stderr,直接调用sys_write即可 */
    {
        __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"
            "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的描述符都与文件关联。事实上,还存在很多其它情况,这里并没有考虑。*/
    {
        if (!(file=task[0]->filp[fd]))    /* 从进程0的文件描述符表中得到文件句柄 */
            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;
}

  因为和printk的功能近似,建议将此函数放入到kernel/printk.c中。fprintk()的使用方式类同与C标准库函数fprintf(),唯一的区别是第一个参数是文件描述符,而不是文件指针。例如:

fprintk(1, "The ID of running process is %ld", current->pid); //向stdout打印正在运行的进程的ID
fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'R', jiffies); //向log文件输出跟踪进程运行轨迹

 

2, 修改copy_process()函数

  进程一共有5中状态, 首先是从"新建"到"就绪"状态, 也就是在调用fork()函数之后的变化. 而fork()函数是在/kernel/system_call.s中以汇编代码给出的:

.align 2
sys_fork:
    call find_empty_process
    testl %eax,%eax
    js 1f
    push %gs
    pushl %esi
    pushl %edi
    pushl %ebp
    pushl %eax
    call copy_process
    addl $20,%esp
1:    ret

  注意到这里调用了copy_process函数, 从名字就可以联想到这个函数的作用就是"把父进程的数据结构都复制给子进程". 所以进程的创建过程就是在这个函数中完成的. 可以发现, 其实这个函数完成了进程的"新建"和"就绪"两个过程, 这两个过程应该是同时完成的. 所以就在这个函数返回之前记录两次, 第一次是"新建", 紧接着是"就绪".

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
        long ebx,long ecx,long edx,
        long fs,long es,long ds,
        long eip,long cs,long eflags,long esp,long ss)
{
    struct task_struct *p;
    int i;
    struct file *f;

    p = (struct task_struct *) get_free_page();
    if (!p)
        return -EAGAIN;
    task[nr] = p;
    *p = *current;    /* NOTE! this doesn't copy the supervisor stack */
    p->state = TASK_UNINTERRUPTIBLE;
    p->pid = last_pid;
    p->father = current->pid;
    p->counter = p->priority;
    p->signal = 0;
    p->alarm = 0;
    p->leader = 0;        /* process leadership doesn't inherit */
    p->utime = p->stime = 0;

    ......

    p->state = TASK_RUNNING;    /* do this last, just in case */
    
    /***************添加开始***************/
    fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'N', jiffies);
    fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'J', jiffies);
    /***************添加结束***************/

    return last_pid;
}

 

3, 修改wake_up()函数

  该函数用来唤醒不可中断进程

void wake_up(struct task_struct **p)
{
    if (p && *p) {
        
        /***************添加开始***************/
        if ((*p)->state != 0)
            fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);
        /***************添加结束***************/

        (**p).state=0;
        *p=NULL;
    }
}

 

4, 修改sys_pause()函数

  当一个正在运行中的进程遇到系统资源不足等原因时, 就会主动进入睡眠状态中, 让出CPU, 然后让系统进行进程调度. 这里有一个需要注意的就是: 当系统无事可做的时候,进程0会不停地调用sys_pause(),以激活调度算法。此时它的状态可以是等待态,等待有其它可运行的进程;也可以叫运行态,因为它是唯一一个在CPU上运行的进程,只不过运行的效果是等待. 所以需要在sys_pause()中不应该记录进程0.

int sys_pause(void)
{
    /***************添加开始***************/
    if (current->state != TASK_INTERRUPTIBLE && current->pid != 0)
        fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
    /***************添加结束***************/

    current->state = TASK_INTERRUPTIBLE;    
    schedule();
    return 0;
}

 

5, 修改sys_waitpid()函数

  该函数主要用来等待子进程结束(比如父进程调用了wait()函数时), 函数中将当前进程状态修改为可中断等待. 该函数中需要修改的地方仅有一处,记录进程切换为"睡眠"状态即可.

int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
    int flag, code;
    struct task_struct ** p;

    verify_area(stat_addr,4);
repeat:
    flag=0;
    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
        if (!*p || *p == current)
            continue;
        if ((*p)->father != current->pid)
            continue;
        if (pid>0) {
            if ((*p)->pid != pid)
                continue;
        } else if (!pid) {
            if ((*p)->pgrp != current->pgrp)
                continue;
        } else if (pid != -1) {
            if ((*p)->pgrp != -pid)
                continue;
        }
        switch ((*p)->state) {
            case TASK_STOPPED:
                if (!(options & WUNTRACED))
                    continue;
                put_fs_long(0x7f,stat_addr);
                return (*p)->pid;
            case TASK_ZOMBIE:
                current->cutime += (*p)->utime;
                current->cstime += (*p)->stime;
                flag = (*p)->pid;
                code = (*p)->exit_code;
                release(*p);
                put_fs_long(code,stat_addr);
                return flag;
            default:
                flag=1;
                continue;
        }
    }
    if (flag) {
        if (options & WNOHANG)
            return 0;
        current->state=TASK_INTERRUPTIBLE;

        /***************添加开始***************/
        fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
        /***************添加结束***************/

        schedule();
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
            return -EINTR;
    }
    return -ECHILD;
}

 

6, 修改sleep_on()函数

  这个函数表示把当前进程设置为不可中断状态. 也就是说进程只能由wake_up()函数唤醒.

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;

    /***************添加开始***************/
    if (current->state != TASK_UNINTERRUPTIBLE)
        fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
    /***************添加结束***************/

    current->state = TASK_UNINTERRUPTIBLE;
    schedule();    //调度后还要查看"睡眠"状态队列上是否有要唤醒的进程。
    if (tmp)
    {
        /***************添加开始***************/
        if (tmp->state != TASK_RUNNING)
            fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies);
        /***************添加结束***************/

        tmp->state = 0;  // 被唤醒了
    }
}

 

7, 修改interruptible_sleep_on()函数

  这个函数和sleep_on()函数的功能差不多, 功能是把当前进程设置为可中断状态, 也就是说除了可以调用wake_up()函数将这个进程唤醒进入"就绪"状态之外, 还可以通过其它信号来唤醒进入"就绪"状态. 修改也是和sleep_on()函数类似的

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:    
    /***************添加开始***************/
    if (current->state != TASK_INTERRUPTIBLE)
        fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
    /***************添加结束***************/
    
    current->state = TASK_INTERRUPTIBLE;
    schedule();  // 调度后还要查看等待进程链上是否有要唤醒的进程。
    if (*p && *p != current) {
        if ((*p)->state != 0)
            fprintk(3, "%ld\t%c\t%ld\n", (**p).pid, 'J', jiffies);
        (**p).state=0;
        goto repeat;
    }
    *p=NULL;
    if (tmp)
    {
        /***************添加开始***************/
        if (tmp->state != 0)
            fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies);
        /***************添加结束***************/

        tmp->state = 0;   // 进程被唤醒进入"就绪"状态
    } 
}

 

8, 修改schedule()函数

  schedule()函数在kernel/sched.c文件中, 这个函数就是系统的调度函数. 该函数主要完成两个部分的内容, 第一个是将"睡眠"状态的所有得到信号的可中断进程转换为"就绪"状态, 所以当状态发生改变时, 就需要添加一个记录. 另一个就是循环检查就绪队列中的进程有哪个进程应该被调度执行, 然后切换到改进程. 这里需要注意2点

     (1) 有可能就绪队列为空, 这时进程就没有切换了, 所以必须要先判断这种情况. 

     (2) 当前进程的状态有两种情况:

    一种是"运行"态, 则说明当前进程是被抢占的(即时间片用完了或者其他进程的优先级提高了).

    另一种"睡眠"状态. 也就是说是在sys_pause()、sys_waitpid()、sleep_on() 或interruptible_sleep_on() 函数中调用的schedule()函数的. 

void schedule(void)
{
    int i,next,c;
    struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal */

    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
        if (*p) {
            if ((*p)->alarm && (*p)->alarm < jiffies) {
                    (*p)->signal |= (1<<(SIGALRM-1));
                    (*p)->alarm = 0;
                }
            if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
            (*p)->state==TASK_INTERRUPTIBLE)
            {
                (*p)->state = TASK_RUNNING;

                /***************添加开始***************/
                fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);
                /***************添加结束***************/
            }
        }


    while (1) {
        c = -1;
        next = 0;
        i = NR_TASKS;
        p = &task[NR_TASKS];
        while (--i) {
            if (!*--p)
                continue;
            if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
                c = (*p)->counter, next = i;
        }
        if (c) break;
        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
            if (*p)
                (*p)->counter = ((*p)->counter >> 1) +
                        (*p)->priority;
    }
    
    /***************添加开始***************/
    if (task[next] != current)        // next是下一个要执行的进程在task中的下标
    {
        //判断当前正在运行的进程状态是否为TASK_RUNNING,
        //如果是,则表明当前的进程是时间片到期被抢走的,这时当前进程的状态还应是TASK_RUNNING,
        //如果不是,则说明当前进程是主动让出CPU,那么当前进程的状态在这里不需要改变.仍然为"睡眠"状态
        if (current->state == TASK_RUNNING)
            fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'J', jiffies);
        fprintk(3, "%ld\t%c\t%ld\n", task[next]->pid, 'R', jiffies);
    }
    /***************添加结束***************/

    switch_to(next);
}

 

9, 修改do_exit()函数

  该函数中关闭进程所占用资源,然后将进程状态修改为TASK_ZOMBIE,设置退出代码,最后通知其父进程进行最后的清理,如果找不到父进程,则将其父进程手工修改为进程1,然后在系统调度时再统一进行清理。该函数中需要修改的地方仅有一处,记录进程退出即可。

int do_exit(long code)
{
    int i;
    free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
    free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
    for (i=0 ; i<NR_TASKS ; i++)
        if (task[i] && task[i]->father == current->pid) {
            task[i]->father = 1;
            if (task[i]->state == TASK_ZOMBIE)
                /* assumption task[1] is always init */
                (void) send_sig(SIGCHLD, task[1], 1);
        }
    for (i=0 ; i<NR_OPEN ; i++)
        if (current->filp[i])
            sys_close(i);
    iput(current->pwd);
    current->pwd=NULL;
    iput(current->root);
    current->root=NULL;
    iput(current->executable);
    current->executable=NULL;
    if (current->leader && current->tty >= 0)
        tty_table[current->tty].pgrp = 0;
    if (last_task_used_math == current)
        last_task_used_math = NULL;
    if (current->leader)
        kill_session();
    current->state = TASK_ZOMBIE;

    /***************添加开始***************/
    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 */
}

 

10, 修改进程调度的时间片大小

  在kernel/sched.c中的sched_init()中有下面的代码:

outb_p(0x36,0x43);
outb_p(LATCH & 0xff , 0x40);    
outb(LATCH >> 8 , 0x40);

  这三条语句用来设置每次时钟中断的间隔,即为LATCH,而LATCH是定义在文件kernel/sched.c中的一个宏:

#define LATCH (1193180/HZ)

  而HZ是在include/linux/sched.h中定义的一个宏:

#define HZ 100

  所以, PC机8253定时芯片的输入时钟频率为1.193180MHz,即1193180/每秒,LATCH=1193180/100,时钟每跳11931.8下产生一次时钟中断,即每1/100秒(10ms)产生一次时钟中断,所以jiffies实际上记录的滴答数就表示从开机以来共经过了多少个10ms。

  而进程调度的时间片大小其实就是每次时钟中断的时间间隔, 所以只要修改宏HZ就可以了. 如: 将HZ修改为200就表示把进程调度的时间片大小修改为20ms

 

11, 编写统计代码

#!/usr/bin/python
import sys
import copy

P_NULL = 0
P_NEW = 1
P_READY = 2
P_RUNNING = 4
P_WAITING = 8
P_EXIT = 16

S_STATE = 0
S_TIME = 1

HZ = 100

graph_title = r"""
-----===< COOL GRAPHIC OF SCHEDULER >===-----

             [Symbol]   [Meaning]
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
             number   PID or tick
              "-"     New or Exit 
              "#"       Running
              "|"        Ready
              ":"       Waiting
                    / Running with 
              "+" -|     Ready 
                    \and/or Waiting

-----===< !!!!!!!!!!!!!!!!!!!!!!!!! >===-----
"""

usage = """
Usage: 
%s /path/to/process.log [PID1] [PID2] ... [-x PID1 [PID2] ... ] [-m] [-g]

Example:
# Include process 6, 7, 8 and 9 in statistics only. (Unit: tick)
%s /path/to/process.log 6 7 8 9

# Exclude process 0 and 1 from statistics. (Unit: tick)
%s /path/to/process.log -x 0 1

# Include process 6 and 7 only and print a COOL "graphic"! (Unit: millisecond)
%s /path/to/process.log 6 7 -m -g

# Include all processes and print a COOL "graphic"! (Unit: tick)
%s /path/to/process.log -g
"""

class MyError(Exception):
    pass

class DuplicateNew(MyError):
    def __init__(self, pid):
        args = "More than one 'N' for process %d." % pid
        MyError.__init__(self, args)

class UnknownState(MyError):
    def __init__(self, state):
        args = "Unknown state '%s' found." % state
        MyError.__init__(self, args)

class BadTime(MyError):
    def __init__(self, time):
        args = "The time '%d' is bad. It should >= previous line's time." % time
        MyError.__init__(self, args)

class TaskHasExited(MyError):
    def __init__(self, state):
        args = "The process has exited. Why it enter '%s' state again?" % state
        MyError.__init__(self, args)

class BadFormat(MyError):
    def __init__(self):
        args = "Bad log format"
        MyError.__init__(self, args)

class RepeatState(MyError):
    def __init__(self, pid):
        args = "Previous state of process %d is identical with this line." % (pid)
        MyError.__init__(self, args)

class SameLine(MyError):
    def __init__(self):
        args = "It is a clone of previous line."
        MyError.__init__(self, args)

class NoNew(MyError):
    def __init__(self, pid, state):
        args = "The first state of process %d is '%s'. Why not 'N'?" % (pid, state)
        MyError.__init__(self, args)

class statistics:
    def __init__(self, pool, include, exclude):
        if include:
            self.pool = process_pool()
            for process in pool:
                if process.getpid() in include:
                    self.pool.add(process)
        else:
            self.pool = copy.copy(pool)

        if exclude:
            for pid in exclude:
                if self.pool.get_process(pid):
                    self.pool.remove(pid)
    
    def list_pid(self):
        l = []
        for process in self.pool:
            l.append(process.getpid())
        return l

    def average_turnaround(self):
        if len(self.pool) == 0:
            return 0
        sum = 0
        for process in self.pool:
            sum += process.turnaround_time()
        return float(sum) / len(self.pool)

    def average_waiting(self):
        if len(self.pool) == 0:
            return 0
        sum = 0
        for process in self.pool:
            sum += process.waiting_time()
        return float(sum) / len(self.pool)
    
    def begin_time(self):
        begin = 0xEFFFFF
        for p in self.pool:
            if p.begin_time() < begin:
                begin = p.begin_time()
        return begin

    def end_time(self):
        end = 0
        for p in self.pool:
            if p.end_time() > end:
                end = p.end_time()
        return end

    def throughput(self):
        return len(self.pool) * HZ / float(self.end_time() - self.begin_time())

    def print_graphic(self):
        begin = self.begin_time()
        end = self.end_time()

        print graph_title

        for i in range(begin, end+1):
            line = "%5d " % i
            for p in self.pool:
                state = p.get_state(i)
                if state & P_NEW:
                    line += "-"
                elif state == P_READY or state == P_READY | P_WAITING:
                    line += "|"
                elif state == P_RUNNING:
                    line += "#"
                elif state == P_WAITING:
                    line += ":"
                elif state & P_EXIT:
                    line += "-"
                elif state == P_NULL:
                    line += " "
                elif state & P_RUNNING:
                    line += "+"
                else:
                    assert False
                if p.get_state(i-1) != state and state != P_NULL:
                    line += "%-3d" % p.getpid()
                else:
                    line += "   "
            print line

class process_pool:
    def __init__(self):
        self.list = []
    
    def get_process(self, pid):
        for process in self.list:
            if process.getpid() == pid:
                return process
        return None

    def remove(self, pid):
        for process in self.list:
            if process.getpid() == pid:
                self.list.remove(process)

    def new(self, pid, time):
        p = self.get_process(pid)
        if p:
            if pid != 0:
                raise DuplicateNew(pid)
            else:
                p.states=[(P_NEW, time)]
        else:
            p = process(pid, time)
            self.list.append(p)
        return p

    def add(self, p):
        self.list.append(p)

    def __len__(self):
        return len(self.list)
    
    def __iter__(self):
        return iter(self.list)

class process:
    def __init__(self, pid, time):
        self.pid = pid
        self.states = [(P_NEW, time)]
    
    def getpid(self):
        return self.pid

    def change_state(self, state, time):
        last_state, last_time = self.states[-1]
        if state == P_NEW:
            raise DuplicateNew(pid)
        if time < last_time:
            raise BadTime(time)
        if last_state == P_EXIT:
            raise TaskHasExited(state)
        if last_state == state and self.pid != 0: # task 0 can have duplicate state
            raise RepeatState(self.pid)

        self.states.append((state, time))

    def get_state(self, time):
        rval = P_NULL
        combo = P_NULL
        if self.begin_time() <= time <= self.end_time():
            for state, s_time in self.states:
                if s_time < time:
                    rval = state
                elif s_time == time:
                    combo |= state
                else:
                    break
            if combo:
                rval = combo
        return rval

    def turnaround_time(self):
        return self.states[-1][S_TIME] - self.states[0][S_TIME]

    def waiting_time(self):
        return self.state_last_time(P_READY)

    def cpu_time(self):
        return self.state_last_time(P_RUNNING)

    def io_time(self):
        return self.state_last_time(P_WAITING)

    def state_last_time(self, state):
        time = 0
        state_begin = 0
        for s,t in self.states:
            if s == state:
                state_begin = t
            elif state_begin != 0:
                assert state_begin <= t
                time += t - state_begin
                state_begin = 0
        return time


    def begin_time(self):
        return self.states[0][S_TIME]

    def end_time(self):
        return self.states[-1][S_TIME]
        
# Enter point
if len(sys.argv) < 2:
    print usage.replace("%s", sys.argv[0])
    sys.exit(0)

# parse arguments
include = []
exclude = []
unit_ms = False
graphic = False
ex_mark = False

try:
    for arg in sys.argv[2:]:
        if arg == '-m':
            unit_ms = True
            continue
        if arg == '-g':
            graphic = True
            continue
        if not ex_mark:
            if arg == '-x':
                ex_mark = True
            else:
                include.append(int(arg))
        else:
            exclude.append(int(arg))
except ValueError:
    print "Bad argument '%s'" % arg
    sys.exit(-1)

# parse log file and construct processes
processes = process_pool()

f = open(sys.argv[1], "r")

# Patch process 0's New & Run state
processes.new(0, 40).change_state(P_RUNNING, 40)

try:
    prev_time = 0
    prev_line = ""
    for lineno, line in enumerate(f):

        if line == prev_line:
            raise SameLine
        prev_line = line

        fields = line.split("\t")
        if len(fields) != 3:
            raise BadFormat

        pid = int(fields[0])
        s = fields[1].upper()

        time = int(fields[2])
        if time < prev_time:
            raise BadTime(time)
        prev_time = time

        p = processes.get_process(pid)

        state = P_NULL
        if s == 'N':
            processes.new(pid, time)
        elif s == 'J':
            state = P_READY
        elif s == 'R':
            state = P_RUNNING
        elif s == 'W':
            state = P_WAITING
        elif s == 'E':
            state = P_EXIT
        else:
            raise UnknownState(s)
        if state != P_NULL:
            if not p:
                raise NoNew(pid, s)
            p.change_state(state, time)
except MyError, err:
    print "Error at line %d: %s" % (lineno+1, err)
    sys.exit(0)

# Stats
stats = statistics(processes, include, exclude)
att = stats.average_turnaround()
awt = stats.average_waiting()
if unit_ms:
    unit = "ms"
    att *= 1000/HZ
    awt *= 1000/HZ
else:
    unit = "tick"
print "(Unit: %s)" % unit
print "Process   Turnaround   Waiting   CPU Burst   I/O Burst"
for pid in stats.list_pid():
    p = processes.get_process(pid)
    tt = p.turnaround_time()
    wt = p.waiting_time()
    cpu = p.cpu_time()
    io = p.io_time()

    if unit_ms:
        print "%7d   %10d   %7d   %9d   %9d" % (pid, tt*1000/HZ, wt*1000/HZ, cpu*1000/HZ, io*1000/HZ)
    else:
        print "%7d   %10d   %7d   %9d   %9d" % (pid, tt, wt, cpu, io)
print "Average:  %10.2f   %7.2f" % (att, awt)
print "Throughout: %.2f/s" % (stats.throughput())

if graphic:
    stats.print_graphic()

 

三, 运行结果:

 1, 操作系统启动到系统关机过程中所有进程的运行轨迹:

1    N    48        // 进程1新建(init()). 此前是进程0建立和运行
1    J    48        // 新建之后就立即进入就绪队列
0    J    48        // 进程0从"运行"->"就绪"
1    R    48        // 进程1运行
2    N    49        // 进程1建立进程2. 即shell
2    J    49        // shell进入就绪队列
1    W    49        // 进程1调用sys_waitpid()来等待shell退出
2    R    49        // shell运行
3    N    63        // 进程3建立 (shell运行脚本程序etc/rc)
3    J    64        // etc/rc就绪
2    J    64        // shell被抢占, 进入就绪状态, 让出CPU
3    R    64        // etc/rc运行
3    W    68        // etc/rc运行
2    R    68        // shell运行
2    E    73        // shell退出, 唤醒父进程
1    J    73        // 进程1从睡眠状态醒来
1    R    73        // 进程1运行
4    N    74        // 进程4建立 (shell)
4    J    74        // 进程4就绪
1    W    74        // 进程1调用sys_waitpid()等待shell退出
4    R    74        // shell运行
5    N    106        // 进程5是shell建立的不知道做什么的进程
5    J    106        
4    W    107
5    R    107
4    J    109
5    E    109        // 进程5很快就退出了
4    R    109        // shell运行
4    W    115        // shell等待用户输入命令
0    R    115        // 因为无事可做, 所以进程0重出江湖
4    J    2089       // 用户输入命令了, 唤醒了shell
4    R    2089    
4    W    2089 
0    R    2089
4    J    2126

......

4    J    2675
4    R    2675
6    N    2677
6    J    2678
4    J    2678
6    R    2678

  统计结果:

(Unit: tick)
Process   Turnaround   Waiting   CPU Burst   I/O Burst
      0         2423        67           8           0
      1           26         0           2          24
      2           24         4          20           0
      3            5         0           4           0
      4         2604         0          42        2562
      5            3         1           2           0
      6            1         0           0           0
Average:      726.57     10.29
Throughout: 0.27/s

 

2, 把时间片由10毫秒修改为20毫秒之后:

  操作系统启动到系统关机过程中所有进程的运行轨迹:

1    N    94
1    J    95
0    J    95
1    R    95
2    N    96
2    J    96
1    W    96
2    R    97
3    N    125
3    J    126
2    J    127
3    R    127
3    W    139
2    R    139
2    E    146
1    J    146
1    R    146
4    N    146
4    J    147
1    W    147
4    R    147
5    N    211
5    J    212
4    W    213
5    R    213
4    J    217
5    E    218
4    R    218
4    W    228
0    R    229
4    J    2513
4    R    2513
4    W    2514
0    R    2514
......
6    N    3379
6    J    3380
4    W    3381
6    R    3381

    统计结果:

(Unit: tick)
Process   Turnaround   Waiting   CPU Burst   I/O Burst
      0         3038       134          55           0
      1           53         0           2          50
      2           50        13          37           0
      3           14         1          12           0
      4         3235         2          83        3149
      5            7         1           5           0
      6            2         1           0           0
Average:      914.14     21.71
Throughout: 0.21/s

  

可见, 时间片增加之后, 进程的等待状态和总的占用时间都变大了. 所以时间片的大小对于系统来说是非常重要的.