本次实验包括如下内容:
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;
}
修改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();
}
//……
在/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;
}
分别在kernel/fork.c,kernel/sched.c和/kernel/exit.c中状态切换点添加输出命令,修改部分的程序如下所示。
/kernel/fork.c
...
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
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;
}
}
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;
}
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);
}
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
...
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 */
...
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;
}
使用以下命令将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
将stat_log.py拷贝到~/oslab下,先使用以下命令修改权限,在运行程序进行统计。
~/oslab$ chmod +x stat_log.py
~/oslab$ ./stat_log.py process.log
在 include/linux/sched.h 中修改时间片,根据测试的步骤依次执行,可获得以下结果。
#define HZ 200
//从1/100秒一次中断改为1/50秒一次中断
结合自己的体会,谈谈从程序设计者的角度看,单进程编程和多进程编程最大的区别是什么?
多进程编程能够使得并发性更好。
你是如何修改时间片的?仅针对样本程序建立的进程,在修改时间片前后,log 文件的统计结果(不包括 Graphic)都是什么样?结合你的修改分析一下为什么会这样变化,或者为什么没变化?
通过修改中断频率,修改之后,进程切换的间隔加倍了。
这个实验展现了进程切换的动态过程,有助于对进程切换的理解。同时这也是第一次比较系统地完成“框架准备–细节拿捏–整体总结”的学习流程,还算比较顺利。