实验三 进程运行轨迹的跟踪与统计
一、提高CPU
使用率
CPU
可以用一句话概括:取指执行
提高CPU
使用率的方法就是让CPU
忙起来,减少不必要的等待
二、一个进程的运行轨迹
对于Linux 0.11
内核来讲,系统最多可有64
个进程同时存在。除了第一个进程手动建立以外,其余的都是现有进程使用系统调用fork
创建的新进程,被创建的进程称为子进程,创建者,则称为父进程。
1. 日志文件
要记录进程运行轨迹,需要将状态输入到日志文件中,以待分析。
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_RDWR, 0666);
if (!fork()) { init(); }
|
2. 新建进程
static inline _syscall0(int,fork)
|
参考实验二
# kernel/system_call.s # 实际的fork函数 sys_fork: # 获取新进程号 call find_empty_process testl %eax,%eax js 1f push %gs pushl %esi pushl %edi pushl %ebp pushl %eax # 复制父进程 call copy_process addl $20,%esp
|
新建进程的主要函数就在copy_process
中,在这里记录轨迹N(新建)
和J(就绪)
到日志文件
fprintk(3, "%ld\tN\t%ld\n", p->pid, jiffies); fprintk(3, "%ld\tJ\t%ld\n", p->pid, jiffies); return last_pid; }
|
3. 调度进程
schedule
函数为调度函数,这里找到下一个可运行的进程
void schedule(void) { int i,next,c; struct task_struct ** p;
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, "%ld\tJ\t%ld\n", (*p)->pid, jiffies); } }
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\tJ\t%ld\n", current->pid, jiffies); fprintk(3, "%ld\tR\t%ld\n", task[next]->pid, jiffies); } switch_to(next); }
|
4. 进程睡眠
在手动建立0
进程后,会运行for(;;) pause();
,不断调用调度函数即下面的系统调用
int sys_pause(void) { if (current->state != TASK_INTERRUPTIBLE) fprintk(3, "%ld\tW\t%ld\n", current->pid, jiffies); current->state = TASK_INTERRUPTIBLE; schedule(); return 0; }
|
下面函数把当前任务置为可中断的或不可中断的睡眠状态,并让睡眠队列头指针指向当前任务。函数参数p
是等待任务队列头指针。指针是含有一个变量地址的变量。这里参数p
使用了指针的指针形式**p
,这是因为C
函数参数只能传值,没有直接的方式让被调用函数改变调用该函数程序中变量的值。但是指针*p
指向的目标(这里是任务结构)会改变,因此为了能修改调用该函数程序中原来就是指针变量的值,就需要传递指针*p
的指针,即**p
。
这个函数设计的很巧妙,使用临时变量tmp
保存上一个等待进程,每次调度都会更新等待队列头,构成一个隐藏的等待队列
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; current->state = TASK_UNINTERRUPTIBLE; fprintk(3, "%ld\tW\t%ld\n", current->pid, jiffies); schedule(); if (tmp) { tmp->state=0; fprintk(3, "%ld\tJ\t%ld\n", tmp->pid, jiffies); } }
|
下面函数与sleep_on
函数的区别 可中断睡眠状态
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\tW\t%ld\n", current->pid, jiffies); schedule(); if (*p && *p != current) { (**p).state=0; fprintk(3, "%ld\tJ\t%ld\n", (**p).pid, jiffies); goto repeat; } *p=NULL; if (tmp) { tmp->state=0; fprintk(3, "%ld\tJ\t%ld\n", tmp->pid, jiffies); } }
|
唤醒不可中断等待任务。*p
是任务等待队列头指针。由于新等待任务是插入在等待队列头指针处的,因此唤醒的是最后进入等待队列的任务。
void wake_up(struct task_struct **p) { if (p && *p) { (**p).state=0; fprintk(3, "%ld\tJ\t%ld\n", (**p).pid, jiffies); *p=NULL; } }
|
5. 进程退出
进程资源的释放在kernel/exit.c
中
case TASK_ZOMBIE: current->cutime += (*p)->utime; current->cstime += (*p)->stime; flag = (*p)->pid; code = (*p)->exit_code; fprintk(3, "%ld\tE\t%ld\n", (*p)->pid, jiffies); 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; if (current->pid != 0) fprintk(3, "%ld\tW\t%ld\n", current->pid, jiffies); schedule(); if (!(current->signal &= ~(1<<(SIGCHLD-1)))) goto repeat; else return -EINTR; } return -ECHILD; }
|
6. 时间片修改
修改include/linux/sched.h
中的频率,可以修改时钟中断的时间
#define NR_TASKS 64 #define HZ 200
|
PC 机8253
计数/定时芯片的输入时钟频率约为1.193180MHz
LATCH
是设置8253
芯片的初值
#define LATCH (1193180/HZ)
|
实验代码