Linux
下的多进程编程技术基于模板“process.c
”编写多进程的样本程序,实现如下功能:
所有子进程都并行运行,每个子进程的实际运行时间一般不超过30秒;
父进程向标准输出打印所有子进程的id
,并在所有子进程都退出后才退出;
在Linux0.11
上实现进程运行轨迹的跟踪。
基本任务是在内核中维护一个日志文件/var/process.log
,
把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一log
文件中。
在修改过的Linux0.11
上运行样本程序,通过分析log
文件,
统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,
然后计算平均等待时间,平均完成时间和吞吐量。
可以自己编写统计程序,也可以使用python
脚本程序—— stat_log.py
(在/home/teacher/
目录下)进行统计。
修改Linux0.11
进程调度的时间片,然后再运行同样的样本程序,
统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。
1.打开process.c
程序(遇到的问题:为什么在download
目录下),里面实现了一个cpuio_bound()
函数,
//在main函数中调用函数,fork()新建子进程,返回0,调用cpuio_bound函数。
int main(int argc,char * argv[])
{
if(!fork()) cupio_bound(10,0,1);
if(!fork()) cupio_bound(10,1,0);
if(!fork()) cupio_bound(10,1,1);
if(!fork()) cupio_bound(10,5,0);
if(!fork()) cupio_bound(10,0,5);
if(!fork()) cupio_bound(10,4,4);
if(!fork()) cupio_bound(5,0,1);
if(!fork()) cupio_bound(5,1,0);
if(!fork()) cupio_bound(5,1,1);
return 0;
}
2.修改main.c
内容**:为了能尽早开始记录,应当在内核启动时(/init/main.c)就打开日志文件。**
更确切的说是进程0
开始在用户态执行以后就打开日志文件,因为从这个时候就开启了多进程视图。
...
void main(void) //内核的入口
{
...
move_to_user_mode();
//这四句代码是从init()中移动过来的
setup((void *) &drive_info);//完成了文件系统的加载
(void) open("/dev/tty0",O_RDWR,0);//打开标准输入 - stdin
(void) dup(0) //打开标准输出 - stdout
(void) dup(0);//打开标准错误 - stderr
/** 下面的文件open有问题,找不到var目录,导致直接编译内核的时候,遇到main函数直接报错了 */
(void) open("/var/process.log",O_CREAT|O_WEONLY,0666);
if (!fork())
{
/* we count on this going ok */
init();
}
...
}
3.写log
文件,记录下进程的运行轨迹:
在进程的创建、就绪、运行、阻塞、终止这五种状态的时候,输出进程信息到log
日志文件中。
3.1
在printk.c
文件中,添加一个函数fprintk()
,
该函数类似于
fprintf()
,只是后者不能在内核中调用,只能在用户态中调用,
两者的区别:
fprintf()
的第一个参数是文件指针
fprintk()
的第一个参数是文件描述符(fd
-对应于具体文件的句柄)
fprintk.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;
}
要清楚
jiffies
是什么,这个就是记录下进程的运行时间的变量,
其实jiffies
并不是进程运行的真正时间,它记录了从开机到当前时间的时钟中断发生次数,这个数也被称为滴答数,
该全局变量定义在kernel/sched.c
文件中。
(每10ms
产生一次时钟中断,所以jiffies
实际上记录了从开机以来共经过了多少个10ms
)
3.2
寻找进程状态切换点,然后添加fprintk()
打印进程状态到log
日志文件中。
方法:找进程状态改变的点,也就是出现
state
被赋值的地方。
涉及到的文件:
system_call.s
fork.c
sched.c
exit.c
fork()
函数是linux
系统下新建进程需要调用的系统调用函数,
既然是系统调用就可以去system_call.s
中去寻找具体的实现,
system_call.s:
sys_fork:
call find_empty_process
……
push %gs //传递一些参数
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call copy_process //调用copy_process实现进程创建
addl $20,%esp
可以发现
fork()
实际上是调用copy_process
来实现进程的创建,
继续去fork.c
中去寻找copy_process
的实现,
fork.c:
int copy_process(int nr,……)
{
struct task_struct *p;
……
p = (struct task_struct *) get_free_page(); //获得一个task_struct结构体空间,就是指向这个进程PCB的指针。
……
p->pid = last_pid;
……
p->start_time = jiffies; //设置start_time为jiffies
……
p->state = TASK_RUNNING; //设置进程状态为就绪。所有就绪进程的状态都是
//TASK_RUNNING(0),被全局变量current指向的是正在运行的进程。
// 上面state=TASK_RUNNING,也就是表示刚新建的进程被设置为了就绪态,输出信息到log文件
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;
}
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) {
(*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%ld\n",current->pid,'J',jiffies);
}
// 输出运行态信息
fprintk(3,"%ld\t%c%ld\n",task[next]->pid,'R',jiffies);
}
switch_to(next);
}
int sys_pause(void)
{
if(current->state != TASK_INTERUPTIBLE)
// 输出阻塞态信息
fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies);
current->state = TASK_INTERRUPTIBLE;
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;
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;
}
}
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;
}
}
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;
}
}
exit.c:
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 ; ifather == 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 ; ifilp[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;
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 */
}
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;
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;
}
return -ECHILD;
}
bochs
上运行(.\run
),然后进程经过一系列的状态转换(在process.c
使用fork()
创建了进程),就会在process.log
日志文件打印出一堆的进程状态信息。对于此实验遇到的问题:
1.process.c
在哪里,指导书上说在/home/teacher
目录下,但是只能在Download
目录下找到,然后找到了不知道放到哪里。
2.process.log
应该是放在/hdc/usr/var
目录下的,但是连process.c
都运行不了,所以信息根本没有记录下来。(但是其实放在/hdc/usr/root
目录下的hello.c
程序是可以正常的编译+运行的)
3.每次./run
的时候为啥hdc
目录的东西又被卸载了,确实可以用./dbg-asm
+c
来达到同样的运行效果。
5.修改时间片,使得每个进程分配的时间不同于原来的,相当于打印出来滴答数会和以前的不一样。
在process.c
文件中:
#define HZ ...(修改为想改的值即可)
修改完时间片之后,重新运行process.c
,继续查看process.log
里面的内容,并对比不同点。
6.数据统计
为展示实验结果,需要编写一个数据统计程序,它从log
文件读入原始据,
然后计算平均周转时间、平均等待时间和吞吐率。
任何语言都可以编写这样的程序,这里用**python
语言编写了一个stat_log.py
文件来通过日志文件实现对进程信息的统计。**
Ubuntu
上安装python
的环境:
sudo apt-get install python
运行方式:(在命令行的窗口输入命令)
./stat_log.py process.log 0 1 2 3 4 5 -g
(数字表示需要统计的进程的标识符PID)
然后会输出一堆的信息,表示py
根据log
文件统计出来的日志信息。
HIT-OS-LAB参考资料:
1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著
2.《Linux内核完全注释》
3.两个哈工大同学的实验源码
4.Linux-0.11源代码
(上述资料,如果有需要的话,请主动联系我))
该实验的参考资料
网课
官方文档
参考实验报告