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
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值