MOOC哈工大操作系统实验3:进程运行轨迹的跟踪与统计

1.修改init/main.c文件。

为了能尽早开始记录,应当在内核启动时就打开 log 文件。那么就需要把init()中的部分工作提前做了。
原始的代码如下:该代码在进程0中执行,可见init()是在系统第一次调用fork后建立的进程1中执行的,所以我们要把init()中的部分工作挪到进程0中执行,即把init()中的部分代码挪到 move_to_user_mode() 和 if (!fork()) 之间。

//……
move_to_user_mode();
if (!fork()) {        /* we count on this going ok */
    init();
}
//……

init()中有一段代码是加载文件系统并建立文件描述符的(建立了文件描述符 0、1 和 2,它们分别就是 stdin、stdout 和 stderr。这三者的值是系统标准,不可改变。这里没有深入研究,像是一个文件系统初始化的过程?只有完成了这个初始化才能访问文件)。如下:

// ……
//加载文件系统
setup((void *) &drive_info);    

// 打开/dev/tty0,建立文件描述符0和/dev/tty0的关联
(void) open("/dev/tty0",O_RDWR,0);

// 让文件描述符1也和/dev/tty0关联
(void) dup(0);

// 让文件描述符2也和/dev/tty0关联
(void) dup(0);

// ……

因此为了能尽早访问 log 文件,我们要让上述工作在进程 0 中就完成。即把上述代码移动到move_to_user_mode() 和 if (!fork()) 之间,同时加上打开 log 文件的代码。修改后的代码如下:

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

2.在 kernel/printk.c 中增加写文件函数fprintk() 。(由于该函数编写有一定难度,所以该实验中直接给出了源码)

注意在内核状态下不能使用C语言中的写文件函数write(),因为write()函数是位于用户态的库函数,是对系统调用的封装和扩展,在用户态为用户提供一个使用系统调用的接口。
如果要在内核中使用库函数,就必须先载入C函数库,而内核代码是十分注重效率的,所以内核的代码中是不会载入C函数库的(库函数本质上就是个接口,最终还是要进入内核使用其中定义的系统调用的,如果在内核中再载入C函数库的话,未免先得多此一举、不伦不类了)。
所以如果内核要使用某个函数的话,直接找到对应功能的系统调用就可以了,如果没有的话,就直接在内核代码中新建一个系统调用,例如本例中在kernel/printk.c文件里新增了一个“系统调用” fprintk(),在这里只定义这个函数即可,无需增添中断向量表和修改系统调用个数等操作,因为该函数只在内核中使用(因此叫它系统调用似乎不太严谨?因为它只是一个内核中定义的函数,并没有建立用户态和它的连接)。

#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)
    {
        __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    
    {
        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;
}

3.在进程的状态改变时,向log文件中写入记录。

进程的状态在什么时候会改变呢?无非就是它创建、调度和销毁的时候,所以我们修改这三个过程对应的内核文件fork.c 、sched.c 和 exit.c ,在其中控制进程状态改变的代码后面加上一段向log文件写入记录的代码。 实验中用jiffies表示时间,该变量记录了从开机时到现在发生的时钟中断次数:中断次数和时间秒数是有一定的换算关系的,如对于PC 机 8253 定时芯片的输入时钟频率为 1.193180MHz,那么就是每10ms产生一次时钟中断。

3.1 修改fork.c
首先我们来看系统调用fork的代码如下:

sys_fork:
    call find_empty_process
!    ……
! 传递一些参数
    push %gs
    pushl %esi
    pushl %edi
    pushl %ebp
    pushl %eax
! 调用 copy_process 实现进程创建
    call copy_process 
    addl $20,%esp

可以发现真正实现进程创建的函数是 copy_process(),它定义在 kernel/fork.c 中,
以下的一段代码设置了指向结构体变量的指针p的一系列属性之后,把start_time置成了jiffies,即相当于完成了进程的创建,所以在这行代码之后加上一条写log的代码,就可以记录进程新建的时刻。

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->cutime = p->cstime = 0;
p->start_time = jiffies;

即在以上代码之后(原始文件第92行后)加上如下代码,向文件描述符3对应的文件(log)处写入进程的id、表示新建状态的N、jiffies时间。

fprintk(3,"%d\tN\t%d\n",p->pid,jiffies);

该文件之后的代码对本进程进行了一些处理以及判断该进程是否进入就绪态。最终这条代码(原始文件第132行)将进程置成了可运行的就绪状态:

p->state = TASK_RUNNING;  

因此只需在以上指令后加上一条向log文件记录就绪状态J的代码即可:

fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies);

3.2 修改kernel/sched.c:以下的修改的原理类似,即在进程状态改变的时候添加fprintk()指令写log文件。具体为什么加,就要理解代码,理解调度的过程,在这里不展开讲,仅描述在哪里增加了fprintk()。

3.2.1修改schedule():对原始代码新增fprintk后如下,其中第18行、42~47行为两处新增内容。
注意对于42~47行,实验中给出了提示:schedule() 找到的 next 进程是接下来要运行的进程(注意,一定要分析清楚 next 是什么)。如果 next 恰好是当前正处于运行态的进程,swith_to(next) 也会被调用。这种情况下相当于当前进程的状态没变。

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,"%d\tJ\t%d\n",(*p)->pid,jiffies);
                }
        }

/* this is the scheduler proper: */

    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(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);
}

3.2.2修改sys_pause():其中第4~5行为新增内容。实验中也给出了提示:系统无事可做的时候,进程 0 会不停地调用 sys_pause(),以激活调度算法。此时它的状态可以是等待态,等待有其它可运行的进程;也可以叫运行态,因为它是唯一一个在 CPU 上运行的进程,只不过运行的效果是等待。

int sys_pause(void)
{
    current->state = TASK_INTERRUPTIBLE;
    if(current->pid != 0)
        fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
    schedule();
    return 0;
}

3.2.3修改sleep_on():其中第12行、17行为新增内容。

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();
    if (tmp)
    {
        tmp->state=0;
        fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
    }
}

3.2.4修改interruptible_sleep_on():其中第12行、16行、23行为新增内容。

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;
        fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
    }
}

3.2.5修改wake_up():其中第5行为新增内容。

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;
    }
}

所以完整的sched.c文件如下:

/*
 *  linux/kernel/sched.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'sched.c' is the main kernel file. It contains scheduling primitives
 * (sleep_on, wakeup, schedule etc) as well as a number of simple system
 * call functions (type getpid(), which just extracts a field from
 * current-task
 */
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#include <signal.h>

#define _S(nr) (1<<((nr)-1))
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

void show_task(int nr,struct task_struct * p)
{
    int i,j = 4096-sizeof(struct task_struct);

    printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state);
    i=0;
    while (i<j && !((char *)(p+1))[i])
        i++;
    printk("%d (of %d) chars free in kernel stack\n\r",i,j);
}

void show_stat(void)
{
    int i;

    for (i=0;i<NR_TASKS;i++)
        if (task[i])
            show_task(i,task[i]);
}

#define LATCH (1193180/HZ)

extern void mem_use(void);

extern int timer_interrupt(void);
extern int system_call(void);

union task_union {
    struct task_struct task;
    char stack[PAGE_SIZE];
};

static union task_union init_task = {INIT_TASK,};

long volatile jiffies=0;
long startup_time=0;
struct task_struct *current = &(init_task.task);
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&(init_task.task), };

long user_stack [ PAGE_SIZE>>2 ] ;

struct {
    long * a;
    short b;
    } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 */
void math_state_restore()
{
    if (last_task_used_math == current)
        return;
    __asm__("fwait");
    if (last_task_used_math) {
        __asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
    }
    last_task_used_math=current;
    if (current->used_math) {
        __asm__("frstor %0"::"m" (current->tss.i387));
    } else {
        __asm__("fninit"::);
        current->used_math=1;
    }
}

/*
 *  'schedule()' is the scheduler function. This is GOOD CODE! There
 * probably won't be any reason to change this, as it should work well
 * in all circumstances (ie gives IO-bound processes good response etc).
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */
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,"%d\tJ\t%d\n",(*p)->pid,jiffies);
                }
        }

/* this is the scheduler proper: */

    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(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);
}

int sys_pause(void)
{
    current->state = TASK_INTERRUPTIBLE;
    if(current->pid != 0)
        fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
    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();
    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;
        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;
    }
}

/*
 * OK, here are some floppy things that shouldn't be in the kernel
 * proper. They are here because the floppy needs a timer, and this
 * was the easiest way of doing it.
 */
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};
static int  mon_timer[4]={0,0,0,0};
static int moff_timer[4]={0,0,0,0};
unsigned char current_DOR = 0x0C;

int ticks_to_floppy_on(unsigned int nr)
{
    extern unsigned char selected;
    unsigned char mask = 0x10 << nr;

    if (nr>3)
        panic("floppy_on: nr>3");
    moff_timer[nr]=10000;       /* 100 s = very big :-) */
    cli();              /* use floppy_off to turn it off */
    mask |= current_DOR;
    if (!selected) {
        mask &= 0xFC;
        mask |= nr;
    }
    if (mask != current_DOR) {
        outb(mask,FD_DOR);
        if ((mask ^ current_DOR) & 0xf0)
            mon_timer[nr] = HZ/2;
        else if (mon_timer[nr] < 2)
            mon_timer[nr] = 2;
        current_DOR = mask;
    }
    sti();
    return mon_timer[nr];
}

void floppy_on(unsigned int nr)
{
    cli();
    while (ticks_to_floppy_on(nr))
        sleep_on(nr+wait_motor);
    sti();
}

void floppy_off(unsigned int nr)
{
    moff_timer[nr]=3*HZ;
}

void do_floppy_timer(void)
{
    int i;
    unsigned char mask = 0x10;

    for (i=0 ; i<4 ; i++,mask <<= 1) {
        if (!(mask & current_DOR))
            continue;
        if (mon_timer[i]) {
            if (!--mon_timer[i])
                wake_up(i+wait_motor);
        } else if (!moff_timer[i]) {
            current_DOR &= ~mask;
            outb(current_DOR,FD_DOR);
        } else
            moff_timer[i]--;
    }
}

#define TIME_REQUESTS 64

static struct timer_list {
    long jiffies;
    void (*fn)();
    struct timer_list * next;
} timer_list[TIME_REQUESTS], * next_timer = NULL;

void add_timer(long jiffies, void (*fn)(void))
{
    struct timer_list * p;

    if (!fn)
        return;
    cli();
    if (jiffies <= 0)
        (fn)();
    else {
        for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)
            if (!p->fn)
                break;
        if (p >= timer_list + TIME_REQUESTS)
            panic("No more time requests free");
        p->fn = fn;
        p->jiffies = jiffies;
        p->next = next_timer;
        next_timer = p;
        while (p->next && p->next->jiffies < p->jiffies) {
            p->jiffies -= p->next->jiffies;
            fn = p->fn;
            p->fn = p->next->fn;
            p->next->fn = fn;
            jiffies = p->jiffies;
            p->jiffies = p->next->jiffies;
            p->next->jiffies = jiffies;
            p = p->next;
        }
    }
    sti();
}

void do_timer(long cpl)
{
    extern int beepcount;
    extern void sysbeepstop(void);

    if (beepcount)
        if (!--beepcount)
            sysbeepstop();

    if (cpl)
        current->utime++;
    else
        current->stime++;

    if (next_timer) {
        next_timer->jiffies--;
        while (next_timer && next_timer->jiffies <= 0) {
            void (*fn)(void);
            
            fn = next_timer->fn;
            next_timer->fn = NULL;
            next_timer = next_timer->next;
            (fn)();
        }
    }
    if (current_DOR & 0xf0)
        do_floppy_timer();
    if ((--current->counter)>0) return;
    current->counter=0;
    if (!cpl) return;
    schedule();
}

int sys_alarm(long seconds)
{
    int old = current->alarm;

    if (old)
        old = (old - jiffies) / HZ;
    current->alarm = (seconds>0)?(jiffies+HZ*seconds):0;
    return (old);
}

int sys_getpid(void)
{
    return current->pid;
}

int sys_getppid(void)
{
    return current->father;
}

int sys_getuid(void)
{
    return current->uid;
}

int sys_geteuid(void)
{
    return current->euid;
}

int sys_getgid(void)
{
    return current->gid;
}

int sys_getegid(void)
{
    return current->egid;
}

int sys_nice(long increment)
{
    if (current->priority-increment>0)
        current->priority -= increment;
    return 0;
}

void sched_init(void)
{
    int i;
    struct desc_struct * p;

    if (sizeof(struct sigaction) != 16)
        panic("Struct sigaction MUST be 16 bytes");
    set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
    set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
    p = gdt+2+FIRST_TSS_ENTRY;
    for(i=1;i<NR_TASKS;i++) {
        task[i] = NULL;
        p->a=p->b=0;
        p++;
        p->a=p->b=0;
        p++;
    }
/* Clear NT, so that we won't have troubles with that later on */
    __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
    ltr(0);
    lldt(0);
    outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */
    outb_p(LATCH & 0xff , 0x40);    /* LSB */
    outb(LATCH >> 8 , 0x40);    /* MSB */
    set_intr_gate(0x20,&timer_interrupt);
    outb(inb_p(0x21)&~0x01,0x21);
    set_system_gate(0x80,&system_call);
}

3.3修改kernel/exit.c:
3.3.1修改do_exit:其中第30行为新增内容。

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,"%d\tE\t%d\n",current->pid,jiffies);
    current->exit_code = code;
    tell_father(current->father);
    schedule();
    return (-1);    /* just to suppress warnings */
}

3.3.2修改sys_waitpid:其中第47行为新增内容。

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,"%d\tW\t%d\n",current->pid,jiffies);
        schedule();
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
            return -EINTR;
    }
    return -ECHILD;
}

以上修改完内核的代码后,退回linux-0.11目录中,运行make all编译内核,就大功告成了。

4.最终编写一个process.c程序,用它创建多个进程来进行测试。

该文件的目的是创建几个进程用来测试,因此完全是个性化的自定义即可,注意用到fork()和wait()两个函数。
fork()函数,简单说该函数的作用就是创建一个子进程,开辟一条与父进程平行的时间线,即创建了一个分叉路口。在调用fork的时候就产生这个分叉路口了,fork之前与之后的代码都会被分岔路口之后的父进程和子进程执行。 fork()的返回值是什么样呢?它在父进程和子进程里的返回值是不同的:父进程中返回子进程的ID,子进程中返回0(感觉返回值有点像指针的意思,指向子进程的id,如果没子进程就指向0)。 那么如果我想让一条语句在子进程中执行而不在父进程中执行,那么就可以判断fork()的返回值是不是等于0,如果等于0就执行这条语句。
wait()函数,当调用该函数时(如果没有参数),意思就是判断当前进程的子进程有没有结束,如果有任意一个子进程结束了,那么当前进程就可以结束了,否则就处于阻塞状态;如果当前进程已经没有子进程了,那么就不会发生什么事,不会阻塞,只是wait的返回值发生变化。也就是说,如果当前进程有三个子进程,如果我要让这三个子进程先结束,让当前进程最后结束,那么就连用三个wait(),才能确保当前进程在三个子进程都结束后再结束。
该实验给的原始的process.c中实现了一个函数cpuio_bound,该函数的实现我没有具体去研究其中涉及到的函数变量和结构类型,暂时把它当成黑盒子来用:就是last参数是占用cpu和io的总时间,cpu_time是一次占用cpu的时间,io_time是一次占用io的时间。
(注意如果先编辑好代码再到实验楼中复制黏贴的话,需要把代码中的中文去掉,因为实验楼中是不能粘贴中文的)

#include <stdio.h>//这四行和下面最后一行的宏是实验文件自带的
#include <unistd.h>
#include <time.h>
#include <sys/times.h> 


#include <sys/wait.h> //这两行是新加的
#include <sys/types.h>  //包含基本系统数据类型,进程id类型如pid_t


#define HZ    100

void cpuio_bound(int last, int cpu_time, int io_time);

int main(int argc, char * argv[])
{
       pid_t Pid1;
       pid_t Pid2;
       pid_t Pid3;

       Pid1 = fork(); 
       if (Pid1 < 0) printf("error in fork!"); 
       else if (Pid1 == 0) 
            {
                 printf("child process 1:\n");
                 cpuio_bound(5, 2, 2);
            }

       Pid2 = fork();
       if (Pid2 < 0) printf("error in fork!"); 
       else if (Pid2 == 0) 
            {
                 printf("child process 2:\n");
                 cpuio_bound(5, 4, 0);
            }

       Pid3 = fork();
       if (Pid3 < 0) printf("error in fork!"); 
       else if (Pid3 == 0) 
            {
                 printf("child process 3:\n");
                 cpuio_bound(5, 0, 4);
            }

       printf("This process's Pid is %d\n", getpid());
       printf("Pid of child process 1 is %d\n", Pid1);
       printf("Pid of child process 2 is %d\n", Pid2);
       printf("Pid of child process 3 is %d\n", Pid3);
       wait(NULL);
       wait(NULL);
       wait(NULL);
       return 0;
}

/*
 * 此函数按照参数占用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)
{
    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 );
        last -= cpu_time;

        if (last <= 0 )
            break;

        /* IO Burst */
        /* 用sleep(1)模拟1秒钟的I/O操作 */
        sleep_time=0;
        while (sleep_time < io_time)
        {
            sleep(1);
            sleep_time++;
        }
        last -= sleep_time;
    }
}

5.测试

先 sudo ./mount-hdc 挂载hdc,然后把process.c放到hdc/usr/root/中,然后./run打开模拟器,编译process文件并运行,就可以查看日志文件了,日志文件位置在var/process.log。

sudo ./mount-hdc
然后把process.c放到hdc/usr/root/./run
gcc -o process process.c 
./process

运行sync指令进行同步,将缓冲区信息加载到硬盘。然后关闭模拟器,再次挂载hdc后,进入查看hdc/var/process.log。(也可以不关模拟器,直接在模拟器中的linux0.11系统上查看,就是0.11版本的编辑器可能不太方便)

sync

我们可以用该实验提供的一个用python写的数据统计程序stat_log.py,它从 log 文件读入原始数据,然后计算平均周转时间、平均等待时间等。
如果是实验楼的话就已经自带python解释器了,如果是Ubuntu则执行以下指令下载。

sudo apt-get install python

写好stat_log.py脚本文件后对其加上权限。

sudo chmod +x ./stat_log.py

然后分析结果,查看相应的进程(我之前是把stat_log.py脚本放到了hdc/var中,即与process.log在同一个文件夹中,因此以下的代码就不用调路径了,否则要对应好路径)。我用的测试文件process.c中是创建了8个进程,其中每个进程最后都会输出自己的进程ID即"This process’s Pid is %d\n",因此要记住这8个进程ID(进程总数肯定是大于8个的,因为还有一些操作系统一开机就执行的系统进程),然后用python脚本统计这8个进程(注意在模拟器中运行完process后,千万不要忘了加sync,我当时就是忘了加,导致log文件写入的进程不全,我还以为是代码错了,debug了好久…),假设这8个进程的id是 7 8 9 10 11 12 13 14,那么我们打开脚本的方式是

./stat_log.py ./process.log  7 8 9 10 11 12 13 14

在结果中我们可以看到各个进程的周转时间(Turnaround,指作业从提交到完成所用的总时间)、等待时间等,以及平均周转时间和等待时间。

修改时间片:
linux0.11采用的调度算法是一种综合考虑进程优先级并能动态反馈调整时间片的轮转调度算法。 那么什么是轮转调度算法呢?它为每个进程分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程;如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。那什么是综合考虑进程优先级呢?就是说一个进程在阻塞队列中停留的时间越长,它的优先级就越大,下次就会被分配更大的时间片。
进程之间的切换是需要时间的,如果时间片设定得太小的话,就会发生频繁的进程切换,因此会浪费大量时间在进程切换上,影响效率;如果时间片设定得足够大的话,就不会浪费时间在进程切换上,利用率会更高,但是用户交互性会受到影响,举一个很直观的例子:我在银行排队办业务,假设我要办的业务很简单只需要占用1分钟,如果每个人的时间片是30分钟,而我前面的每个人都要用满这30分钟,那我就要等上好几个小时!如果每个人的时间片是2分钟的话,我只需要等十几分钟就可以办理我的业务了,前面没办完的会在我之后轮流地继续去办。所以时间片不能过大或过小,要兼顾CPU利用率和用户交互性。
时间片的初始值是进程0的priority,是在linux-0.11/include/linux/sched.h的宏 INIT_TASK 中定义的,如下:我们只需要修改宏中的第三个值即可,该值即时间片的初始值。

#define INIT_TASK \
    { 0,15,15, 
// 上述三个值分别对应 state、counter 和 priority;

修改完后再次编译make all,进入模拟器后编译运行测试文件process.c,然后运行统计脚本stat_log.py查看结果,与之前的结果进行对比。

实验报告

问题1:单进程编程和多进程编程的区别?
1.执行方式:单进程编程是一个进程从上到下顺序进行;多进程编程可以通过并发执行,即多个进程之间交替执行,如某一个进程正在I/O输入输出而不占用CPU时,可以让CPU去执行另外一个进程,这需要采取某种调度算法。

2.数据是否同步:单进程的数据是同步的,因为单进程只有一个进程,在进程中改变数据的话,是会影响这个进程的;多进程的数据是异步的,因为子进程数据是父进程数据在内存另一个位置的拷贝,因此改变其中一个进程的数据,是不会影响到另一个进程的。

3.CPU利用率:单进程编程的CPU利用率低,因为单进程在等待I/O时,CPU是空闲的;多进程编程的CPU利用率高,因为当某一进程等待I/O时,CPU会去执行另一个进程,因此CPU的利用率高。

4.多进程用途更广泛。

问题2:仅针对样本程序建立的进程,在修改时间片前后,log 文件的统计结果(不包括 Graphic)都是什么样?结合你的修改分析一下为什么会这样变化,或者为什么没变化?(个人理解,有问题请指出)
我设计了时间片为1,15,10000这三个例子,可以看到随着时间片增大,这8个进程的平均周转时间(Turnaround)和平均等待时间是增大的(图中的Throughout不知道是指什么时间?所以暂时先不考虑了。。。)。可能是由于当时间片越大,就越趋近于先进先出的调度模式了,那么可能是先占用CPU的进程,占用了太久的CPU,导致后面仅需要占用一小会CPU的进程等待了太久的时间;时间片小的时候,先占用CPU的进程在时间片用完后就会把CPU让给后面的进程,后面的进程马上执行完,所以后面的进程都不用等待了,所以平均周转时间和等待时间会变小。

如果要总结规律的话,只设计三个梯度明显是不合适的,需要设计一系列梯度才能更精准地说明问题;还有不同时间片对程序的影响,跟程序本身也是有关系的,比如程序中各个进程占用CPU和I/O时间的差异,都会得到不同的结果。

那么从理论上讲:
(1)当时间片非常小的时候,平均周转时间和等待时间应该是非常大的,因为进程切换地太频繁,浪费大量的时间再进程切换上。
(2)当时间片适宜的时候,平均周转时间和等待时间是最低的,因为既不会浪费大量时间在进程切换上,又不会让只需占用一小会CPU的进程等待太多的时间(也就是说对于只占用很短时间CPU的进程而言,它不会等待太多的时间)。
(3)当时间片足够大的时候,就变成了先进先出的调度算法,虽然不会浪费时间在进程切换上,但是用户交互性差,平均周转时间和等待时间可能又会变大(当然这取决于各个进程占用CPU时间长短的差异性以及各个进程的执行次序),比如第一个进程占用CPU一小时,而后7个进程只需占用CPU一分钟,那么后面7个进程每个进程都要等第一个进程一小时,等待时间和周转时间自然变大;如果时间片适宜,让第一个进程占用1分钟CPU后就切换到第二个进程,那么后面7个进程几乎不需要等待多久就可以执行完毕,会大大减小周转时间和等待时间。

  • 10
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值