实验3:进程运行轨迹跟踪与统计

1 多进程图像

多个进程合理有序推进的过程构成了多进程图像。将多进程组织起来才能保证合理有序的推进。linux0.11中采用的是:PCB + 状态 + 队列

进程状态图用于描述一个进程生存期的状态。其状态包括新建态、就绪态、运行态、阻塞态、终止态。下图是截取自课件的进程状态图:
进程状态图

图1.1 进程状态图

顺便贴上一个进程状态表

内核表示含义
TASK_RUNNING可运行(就绪态或运行态)
TASK_INTERRUPTIBLE可中断的等待状态
TASK_UNINTERRUPTIBLE不可中断的等待状态
TASK_ZOMBIE僵死(僵尸态)
TASK_STOPPED暂停
TASK_SWAPPING换入/换出

PCB是用来记录进程信息的数据结构(是一个结构体),里面有几个成员,这次实验要用到:

struct task_struct {
	long state;   //进程当前运行状态,有TASK_RUNNING、TASK_INTERRUPTIBLE等几种取值
	long counter; //任务运行时间计数,即运行时间片。采用递减方式,counter越大表明任务已经运行的时间越短
	long priority;//运行优先数,用于给counter赋初值。一个进程刚被创建时counter == priority。
	...
}

Linux0.11中定义了一个任务指针数组,用于存放所有任务:

struct task_struct * task[NR_TASKS] = {&(init_task.task), };     //定义任务指针数组

2 实验内容

具体实验内容和实验提示请看实验指导书:操作系统实验手册

3 实验过程

实验过程一言难尽,全靠参考别人的 blog 才完成了实验 ( Ĭ ^ Ĭ )

3.1 编写 process.c

在编写 process.c 时遇到了一个问题,process.c 程序的注释方式要采用 “ /* */ ” ,不要采用双斜杆的方式注释,不然在bochs 中编译时会报错的。

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>

#define HZ	100

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

void main(void)
{
    int pid = -1;    /*进程ID*/
	int i = 0;       /*循环变量*/
    
	for(i = 0; i < 3; i++)
	{   /*利用for循环创建3个子进程。可以通过i来区别子进程:i == 0,1,2*/
		pid = fork();
	    if(-1 == pid)
		{
            perror("error!");
		    exit(1);
		}
		if(0 == pid)
		{   /*子进程直接退出*/
		    break;
		}
	}
    
	if(0 == i)
	{   /*子进程1*/
	    printf("parent id:%d, myself id:%d, i = %d\n", getppid(), getpid(), i);
        cpuio_bound(10, 2, 3);	    
	}
    
	if(1 == i)
	{   /*子进程2*/	
    	printf("parent id:%d, myself id:%d, i = %d\n", getppid(), getpid(), i);
        cpuio_bound(5, 1, 1);	    
	}

	if(2 == i)
	{   /*子进程3*/
	    printf("parent id:%d, myself id:%d, i = %d\n", getppid(), getpid(), i);
        cpuio_bound(20, 5, 3);	    
	}

	if(3 == i)
    {   /*父进程,等待子进程全部退出才能退出*/
	    for(i = 0; i < 3; i++)
		{
		    wait(NULL);
		}
	    printf("parent id:%d, myself id:%d, i = %d\n", getppid(), getpid(), i);
	}
}

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

3.2 创建并打开日志

fprintk函数 可用于写日志,将此函数放入到 kernel/printk.c 中。并且我在 kernel.h 添加了该函数的声明。

/*
 * 'kernel.h' contains some often-used function prototypes etc
 */
void verify_area(void * addr,int count);
volatile void panic(const char * str);
int printf(const char * fmt, ...);
int printk(const char * fmt, ...);
int fprintk(int fd, const char *fmt, ...);   //在这里添加声明
int tty_write(unsigned ch,char * buf,int count);
void * malloc(unsigned int size);
void free_s(void * obj, int size);

fprintk函数 添加到 printk.c 文件中。

//fprintk函数 添加到 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);

    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" /* 注意对于Windows环境来说,是_logbuf,下同 */
            "pushl %1\n\t"
            "call sys_write\n\t" /* 注意对于Windows环境来说,是_sys_write,下同 */
            "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;
}

然后在main函数中添加打开log文件的程序,这里有个知识点 :open dup函数返回的一定是未使用的最小的描述符数值。为了尽早纪录,所以要尽早打开log文件:

void main(void)		/* This really IS void, no error here. */
{			/* The startup routine assumes (well, ...) this */
...
	floppy_init();
	sti();
	move_to_user_mode();
/*********************添加开始**********************************/
	setup((void *) &drive_info);         //加载文件系统
	(void) open("/dev/tty0",O_RDWR,0);   //打开/dev/tty0文件,这里没有接受返回值(fd),不过也不需要接收,因为这是第一次打开文件,返回值肯定是0 -- 标准输入
	(void) dup(0);                       //将文件描述符1与/dev/tty0文件关联。同样这里没有接收返回值,不过返回值肯定是1
	(void) dup(0);                       //将文件描述符2与/dev/tty0文件关联
    (void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY, 0666);  //文件描述符3与/var/process.log相关联
	                                                                 //注意如果没有事先创建"/var/process.log",这里要设置文件访问权限,否则启动不了
/*********************添加结束**********************************/
	if (!fork()) {		/* we count on this going ok */
		init();         //进程1 去执行init()
	}
	for(;;) pause();     //进程0始终执行pause()
}

3.3 寻找切换点

切换点主要存在 exit.c \ fork.c \ sched.c 三个文件中。

exit.c文件修改如下

...
int do_exit(long code)
{
    ···
	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 */
}
...
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
    ···
		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;
}

fork.c文件修改如下

···
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放入任务队列中去
	*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;
    fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'N', jiffies);   //新建态,刚刚记录完进程起始时间,马上新建态
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;
	p->tss.eip = eip;
	p->tss.eflags = eflags;
	p->tss.eax = 0;
	p->tss.ecx = ecx;
	p->tss.edx = edx;
	p->tss.ebx = ebx;
	p->tss.esp = esp;
	p->tss.ebp = ebp;
	p->tss.esi = esi;
	p->tss.edi = edi;
	p->tss.es = es & 0xffff;
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	p->tss.fs = fs & 0xffff;
	p->tss.gs = gs & 0xffff;
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)
		__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
	if (copy_mem(nr,p)) {
		task[nr] = NULL;
		free_page((long) p);
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)
		if ((f=p->filp[i]))
			f->f_count++;
	if (current->pwd)
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
	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, 'J', jiffies);   //就绪态
	return last_pid;
}

sched.c文件修改如下

...
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) { //报警定时值不为0,且当前滴答值已经大于报警定时值(达到报警条件)
					(*p)->signal |= (1<<(SIGALRM-1));   //设置SIGALRM信号
					(*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);   //就绪态
			}
				

		}

/* 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)   //就绪态的任务比较任务counter值(进程已经运行的时间,越大说明运行的时间越短),
				c = (*p)->counter, next = i;                        //counter最大的最为下一个执行任务
		}
		if (c) break;     //c>0,即找到了下一个需要执行的任务,则退出循环去执行下一个进程。
		                  //若c==-1,即队列中全部是空任务,则退出循环去执行任务0(因为此时next==0) 
						  //若C==0, 说明所有就绪态的任务的时间片都用光了,那么接着执行下面语句给所有任务更新时间片
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)
				(*p)->counter = ((*p)->counter >> 1) +
						(*p)->priority;
	}
	if(current->pid != task[next]->pid && current->state  == TASK_RUNNING)
		fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'J', jiffies);        //上一个进程进入就绪态
    if(current->pid != task[next]->pid)
		fprintk(3, "%ld\t%c\t%ld\n", task[next]->pid, 'R', jiffies);        //运行态
	switch_to(next);      //切换到下一个任务(current指向next对应的任务),switch_to() 之后就正式运行了
}

int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	if(current->pid != 0)   //0进程比较特殊
	    fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);   //阻塞态
	schedule();
	return 0;
}

void sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))   //判断当前任务是否是任务0
		panic("task[0] trying to sleep");
	tmp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE;
	fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);   //阻塞态
	schedule();
	if (tmp)
	{
        tmp->state=0;
		fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', 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, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);   //阻塞态
	schedule();
	if (*p && *p != current) {
		(**p).state=0;
		fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);   //就绪态
		goto repeat;
	}
	*p=NULL;
	if (tmp)
	{
        tmp->state=0;
		fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies);   //就绪态
	}
		
}

void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;
		fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);   //就绪态
		*p=NULL;
	}
}
···

3.4 时间片

在第1节介绍PCB的部分成员时里面提到了任务运行时间计数 counter 的初始值是由 priority 决定的,因此我们要找到 priority 的初始值,然后改变它就可以改变时间片了。最后在 sched.h 中可以找到,我们修改 INIT_TASK 的第3个值(15)就可以修改时间片了。

/*
 *  INIT_TASK is used to set up the first task table, touch at
 * your own risk!. Base=0, limit=0x9ffff (=640kB)
 */
#define INIT_TASK \
/* state etc */	{ 0,15,15, \
/* signals */	0,{{},},0, \
···

4 实验结果

需要用 stat_log.py 来统计实验结果,stat_log.py的源码在实验指导书中有。首先将 编好的 process.c 文件放到 oslab/hdc/usr/root 文件夹下,可以通过执行:

 sudo ./mount-hdc 

打开hdc文件夹。然后运行 Linux0.11 ,编译运行 process.c ,下图为 process 的执行结果:
process执行结果

图3.1 process执行结果
从图中可知我们自己创建的进程为12、13、14、15。 **记得在执行完后输入sync命令。**最后从 hdc/var 文件夹下取出 process.log 文件。以下是process.log 的一部分:
···
12	N	14544
12	J	14544
4	W	14544
12	R	14544
13	N	14546
13	J	14546
14	N	14546
14	J	14547
15	N	14547
15	J	14548
12	W	14548
15	R	14548
15	J	14563
14	R	14563
14	J	14578
13	R	14578
13	J	14593
15	R	14593
15	J	14608
14	R	14608
14	J	14623
13	R	14623
···

先给 stat_log.py 加上执行权限(chmod +x stat_log.py),然后在 Ubuntu 中执行 ‘./stat_log.py process.log 12 13 14 15 -g | more’ ,可以看到如下结果(**如果log文件不对的话,即程序中写入log 的记录不对,执行 stat_log.py 将报错提示):

(Unit: tick)
Process   Turnaround   Waiting   CPU Burst   I/O Burst
     12         2730        29           5        2696
     13         1513       493         400         620
     14          907       396         300         210
     15         2726       619        1500         606
Average:     1969.00    384.25
Throughout: 0.15/s

-----===< COOL GRAPHIC OF SCHEDULER >===-----

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

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

14544 -12             
14545 #12             
14546 #   -13 -14     
14547 #   |13 |14 -15 
14548 :12 |   |   +15 
14549 :   |   |   #15 
14550 :   |   |   #   
14551 :   |   |   #   
14552 :   |   |   #   

参考

图1.1 进程状态图,截取自哈工大操作系统课程的课件。
[1] 哈工大操作系统实验手册
[2] HIT-OSLab
[3] 哈工大操作系统试验3 进程运行轨迹的跟踪与统计

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值