哈工大操作系统实验课——进程运行轨迹的跟踪与统计(lab 4)

I 实验题目

本次实验包括如下内容:

  • 基于模板 process.c 编写多进程的样本程序,实现如下功能: + 所有子进程都并行运行,每个子进程的实际运行时间一般不超过 30 秒; + 父进程向标准输出打印所有子进程的 id,并在所有子进程都退出后才退出;
  • 在 Linux0.11 上实现进程运行轨迹的跟踪。 + 基本任务是在内核中维护一个日志文件 /var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一 log 文件中。
  • 在修改过的 0.11 上运行样本程序,通过分析 log 文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量。可以自己编写统计程序,也可以使用 python 脚本程序—— stat_log.py(在 /home/teacher/ 目录下) ——进行统计。
  • 修改 0.11 进程调度的时间片,然后再运行同样的样本程序,统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。

II 操作步骤

1 编写样本程序

cpuio_bound函数的语义如下所示 。

/*
 * 此函数按照参数占用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,直到总运行时间超过last为止
 * 所有时间的单位为秒
 */
cpuio_bound(int last, int cpu_time, int io_time);

在/home/teacher/中修改process.c文件作为样本程序,修改部分的程序如下所示。

int main(int argc, char * argv[])
{
    if (!fork())
        cpuio_bound(10,0,1);
    if (!fork())
        cpuio_bound(10,1,0);
    if (!fork())
        cpuio_bound(10,1,1);
    if (!fork())
        cpuio_bound(10,5,0);
    if (!fork())
        cpuio_bound(10,0,5);
    if (!fork())
        cpuio_bound(10,4,4);
    if (!fork())
        cpuio_bound(5,0,1);
    if (!fork())
        cpuio_bound(5,1,0);
    if (!fork())
        cpuio_bound(5,1,1);
    return 0;
}

2 修改main函数

修改init文件夹中的main.c文件,使得文件描述符在进程0中创建,修改部分的程序如下所示。

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

3 写log文件

在/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);
/* 如果输出到stdout或stderr,直接调用sys_write即可 */
    if (fd < 3)
    {
        __asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
        /* 注意对于Windows环境来说,是_logbuf,下同 */
            "pushl $logbuf\n\t"
            "pushl %1\n\t"
        /* 注意对于Windows环境来说,是_sys_write,下同 */
            "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的描述符都与文件关联。事实上,还存在很多其它情况,这里并没有考虑。*/
    {
    /* 从进程0的文件描述符表中得到文件句柄 */
        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;
}

4 寻找状态切换点

分别在kernel/fork.c,kernel/sched.c和/kernel/exit.c中状态切换点添加输出命令,修改部分的程序如下所示。
/kernel/fork.c

  • copy_process
...
    set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
    set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
    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;
...

/kernel/sched.c

  • sleep_on
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",(**p).pid,'W',jiffies);    
    }
    current->state = TASK_UNINTERRUPTIBLE;
    schedule();
    if (tmp){
        if(tmp->state!=0){
            fprintk(3,"%ld\t%c\t%ld\n",tmp->pid,'J',jiffies);        
        }
        tmp->state=0;
    }
}
  • interruptible_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;
    }
}
  • sys_pause
int sys_pause(void)
{
    if(current->state != TASK_INTERRUPTIBLE)
        fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies);
    current->state = TASK_INTERRUPTIBLE;
    schedule();
    return 0;
}
  • 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){
                fprintk(3,"%ld\t%c\t%ld\n",(*p)->pid,'J',jiffies);
                (*p)->state=TASK_RUNNING;
            }
        }

/* 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(task[next]->pid!=current->pid){
        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);
}
  • 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;
    }
}

/kernel/exit.c

  • do_exit
...
    current->state = TASK_ZOMBIE;
    current->exit_code = code;
    tell_father(current->father);
    fprintk(3,"%ld\t%c\t%ld\n",current->pid,'E',jiffies);
    schedule();
    return (-1);    /* just to suppress warnings */
...

  • sys_waitpid
if (flag) {
        if (options & WNOHANG)
            return 0;
        if(current->state!=TASK_INTERRUPTIBLE)
            fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies);
        current->state=TASK_INTERRUPTIBLE;
        schedule();
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
            return -EINTR;
    }

5 测试

使用以下命令将linux-0.11挂载在Ubuntun上,以交换文件,将process.c拷贝到/hdc/usr/root/中,接着卸载hdc。

$ cd ~/oslab/
# 启动挂载脚本
$ sudo ./mount-hdc
# 卸载
$ sudo umount hdc

在~/oslab下执行run命令,进入root#命令行,执行样本程序并查看/var/process.log是否建立。

# 编译运行process.c
[/usr/root/]# gcc -o process process.c
[/usr/root/]# sync
[/usr/root/]# ./process

# 查看/var/process/log
[/usr/root/]# ll /var
total 9
-rw-r--r-- 1 root   root   8225 ??? ?? ???? process.log
[/usr/root/]# sync

回到~/oslab下,将/var/process.log复制到oslab下查看。

$ cd ~/oslab/
# 启动挂载脚本
$ sudo ./mount-hdc
$ cp ./var/process.log ~/oslab

哈工大操作系统实验课——进程运行轨迹的跟踪与统计(lab 4)_第1张图片

6 统计数据

将stat_log.py拷贝到~/oslab下,先使用以下命令修改权限,在运行程序进行统计。

~/oslab$ chmod +x stat_log.py
~/oslab$ ./stat_log.py process.log

哈工大操作系统实验课——进程运行轨迹的跟踪与统计(lab 4)_第2张图片

7 修改时间片及测试

在 include/linux/sched.h 中修改时间片,根据测试的步骤依次执行,可获得以下结果。

#define HZ 200  
//从1/100秒一次中断改为1/50秒一次中断

哈工大操作系统实验课——进程运行轨迹的跟踪与统计(lab 4)_第3张图片

III 实验报告与总结

1 实验报告

  • 结合自己的体会,谈谈从程序设计者的角度看,单进程编程和多进程编程最大的区别是什么?
    多进程编程能够使得并发性更好。

  • 你是如何修改时间片的?仅针对样本程序建立的进程,在修改时间片前后,log 文件的统计结果(不包括 Graphic)都是什么样?结合你的修改分析一下为什么会这样变化,或者为什么没变化?
    通过修改中断频率,修改之后,进程切换的间隔加倍了。

2 实验总结

这个实验展现了进程切换的动态过程,有助于对进程切换的理解。同时这也是第一次比较系统地完成“框架准备–细节拿捏–整体总结”的学习流程,还算比较顺利。

你可能感兴趣的:(哈工大操作系统)