实验内容
N、J、R、W 和 E ,分别表示进程新建(N)、进入就绪态(J)、进入运行态(R)、进入阻塞态(W) 和退出(E);
process.c是多进程的样本程序,实现如下功能:
所有子进程都并行运行,每个子进程的实际运行时间一般不超过 30 秒;
父进程向标准输出打印所有子进程的 id,并在所有子进程都退出后才退出;
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>
#define HZ 100
void cpuio_bound(int last, int cpu_time, int io_time);
int main(int argc, char * argv[])
{
if(!fork()){
printf( "i am child [%d] and my parent is [%d]\n",getpid(),getppid());
cpuio_bound(10, 1, 0);
}
if(!fork()){
printf( "i am child [%d] and my parent is [%d]\n",getpid(),getppid());
cpuio_bound(10, 0, 1);
}
if(!fork()){
printf( "i am child [%d] and my parent is [%d]\n",getpid(),getppid());
cpuio_bound(10, 1, 9);
}
if(!fork()){
printf( "i am child [%d] and my parent is [%d]\n",getpid(),getppid());
cpuio_bound(10, 9, 1);
}
wait(NULL);
wait(NULL);
wait(NULL);
return 0;
}
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(¤t_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)模拟1sI/O操作 */
sleep_time=0;
while (sleep_time < io_time)
{
sleep(1);
sleep_time++;
}
last -= sleep_time;
}
}
进程运行轨迹的跟踪:
基本任务是在内核中维护一个日志文件 /var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一 log 文件中。
在修改过的 0.11 上运行样本程序,通过分析 log 文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量。可以自己编写统计程序,也可以使用 python 脚本程序—— stat_log.py(在 /home/teacher/ 目录下) ——进行统计。
修改 0.11 进程调度的时间片,然后再运行同样的样本程序,统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。
实验步骤
要想记录进程的轨迹,需要写一个log文件,开辟一段空间给它,需要修改linux-0.11/init/main.c中的代码
/*main.c中的函数main()中,在代码
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);
/***************添加结束***************/
编写向log文件写东西的函数(不能直接用write,因为write是用户态函数,内核中不能使用),linux-0.11/kernel/printk.c增加内容
#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;
}
寻找状态切换点
修改linux-0.11/kernel/fork.c中的copy_process函数(2处)
/*第一处,进程新建,在创建tss之前*/
//...
//p->start_time = jiffies;
fprintk(3,"%d\tN\t%d\n",p->pid,jiffies);
//p->tss.back_link = 0;
/*第二处,进程就绪*/
//p->state = TASK_RUNNING;
fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies);
//return last_pid;
修改linux-0.11/kernel/sched.c中的sleep_on()和interruptible_sleep_on()函数
/*第一处,进程阻塞,sleep_on()*/
//...
//current->state = TASK_UNINTERRUPTIBLE;
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
//schedule();
//...
/*第二处,进程阻塞,interruptible_sleep_on()*/
//...
//repeat: current->state = TASK_INTERRUPTIBLE;
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
//schedule();
//...
/*第三处,进程就绪,interruptible_sleep_on()*/
//...
//(**p).state=0;
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
//goto repeat;
//...
/*第四处,进程就绪,interruptible_sleep_on()*/
//...
//tmp->state=0;
fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
//}
修改linux-0.11/kernel/sched.c中的wake_up()
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/sched.c中的schedule()
/* switch_to(next)前面新增以下代码:if判断next是不是当前正处于运行态的进程。因为如果二者一样,这种情况下相当于进程的状态没有改变*/
/*编号为next的进程 运行*/
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);
}
修改linux-0.11/kernel/sched.c中的sys_pause()和sys_waitpid()
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;
}
/*linux-0.11/kernel/exit.c中的sys_waitpid()*/
//...
//current->state=TASK_INTERRUPTIBLE;
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
//schedule();
//...
修改linux-0.11/kernel/exit.c中的do_exit()函数
//...
//current->state = TASK_ZOMBIE;
fprintk(3,"%d\tE\t%d\n",current->pid,jiffies);
//current->exit_code = code;
//...
挂载文件系统,将oslab/process.c复制到~/oslab/hdc/usr/root 下
sudo ./mount-hdc
cp ./process.c ~/oslab/hdc/usr/root
编译修改过的内核,启动bochs
cd ~/oslab/linux-0.11
make clean
make all
../run
bochs下编译process.c,并运行
gcc -o process process.c
./process.c
查看生成的process.log字节数
ls -l /var
关闭bochs,挂载文件系统,将hdc/var/process.log拷贝到~/oslab/ 下,并浏览
sudo ./mount-hdc
cp ./hdc/var/process.log ./
more process.log
将teacher目录下的stat_log.py这个python程序拷贝到~/oslab/ 下
cd ~/oslab
cp /home/teacher/stat_log.py ./
#统计PID为0 1 2 3 4 5的进程
./stat_log.py ./process.log 0 1 2 3 4 5 -g