5.3.5 进程调度的实现
调度程序在内核中就是一个函数,为了讨论方便,我们同样对其进行了简化,略其对SMP的实现部分。
asmlinkage void schedule(void)
{
struct task_struct *prev, *next, *p; /* prev表示调度之前的进程,
next表示调度之后的进程 */
struct list_head *tmp;
int this_cpu, c;
if (!current->active_mm) BUG();/*如果当前进程的的active_mm为空,出错*/
need_resched_back:
prev = current; /*让prev成为当前进程 */
this_cpu = prev->processor;
if (in_interrupt()) {/*如果schedule是在中断服务程序内部执行,
就说明发生了错误*/
printk("Scheduling in interrupt\n");
BUG();
}
release_kernel_lock(prev, this_cpu); /*释放全局内核锁,
并开this_cpu的中断*/
spin_lock_irq(&runqueue_lock); /*锁住运行队列,并且同时关中断*/
if (prev->policy == SCHED_RR) /*将一个时间片用完的SCHED_RR实时
goto move_rr_last; 进程放到队列的末尾 */
move_rr_back:
switch (prev->state) { /*根据prev的状态做相应的处理*/
case TASK_INTERRUPTIBLE: /*此状态表明该进程可以被信号中断*/
if (signal_pending(prev)) { /*如果该进程有未处理的
信号,则让其变为可运行状态*/
prev->state = TASK_RUNNING;
break;
}
default: /*如果为可中断的等待状态或僵死状态*/
del_from_runqueue(prev); /*从运行队列中删除*/
case TASK_RUNNING:;/*如果为可运行状态,继续处理*/
}
prev->need_resched = 0;
/*下面是调度程序的正文 */
repeat_schedule: /*真正开始选择值得运行的进程*/
next = idle_task(this_cpu); /*缺省选择空闲进程*/
c = -1000;
if (prev->state == TASK_RUNNING)
goto still_running;
still_running_back:
list_for_each(tmp, &runqueue_head) { /*遍历运行队列*/
p = list_entry(tmp, struct task_struct, run_list);
if (can_schedule(p, this_cpu)) { /*单CPU中,该函数总返回1*/ int weight = goodness(p, this_cpu, prev->active_mm);
if (weight > c)
c = weight, next = p;
}
}
/* 如果c为0,说明运行队列中所有进程的权值都为0,也就是分配给各个进程的
时间片都已用完,需重新计算各个进程的时间片 */
if (!c) {
struct task_struct *p;
spin_unlock_irq(&runqueue_lock);/*锁住运行队列*/
read_lock(&tasklist_lock); /* 锁住进程的双向链表*/
for_each_task(p) /* 对系统中的每个进程*/
p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);
read_unlock(&tasklist_lock);
spin_lock_irq(&runqueue_lock);
goto repeat_schedule;
}
spin_unlock_irq(&runqueue_lock);/*对运行队列解锁,并开中断*/
if (prev == next) { /*如果选中的进程就是原来的进程*/
prev->policy &= ~SCHED_YIELD;
goto same_process;
}
/* 下面开始进行进程切换*/
kstat.context_swtch++; /*统计上下文切换的次数*/
{
struct mm_struct *mm = next->mm;
struct mm_struct *oldmm = prev->active_mm;
if (!mm) { /*如果是内核线程,则借用prev的地址空间*/
if (next->active_mm) BUG();
next->active_mm = oldmm;
} else { /*如果是一般进程,则切换到next的用户空间*/
if (next->active_mm != mm) BUG();
switch_mm(oldmm, mm, next, this_cpu);
}
if (!prev->mm) { /*如果切换出去的是内核线程*/
prev->active_mm = NULL;/*归还它所借用的地址空间*/
mmdrop(oldmm); /*mm_struct中的共享计数减1*/
}
}
switch_to(prev, next, prev); /*进程的真正切换,即堆栈的切换*/
__schedule_tail(prev); /*置prev->policy的SCHED_YIELD为0 */
same_process:
reacquire_kernel_lock(current);/*针对SMP*/
if (current->need_resched) /*如果调度标志被置位*/
goto need_resched_back; /*重新开始调度*/
return;
}
以上就是调度程序的主要内容,为了对该程序形成一个清晰的思路,我们对其再给出进一步的解释:
· 如果当前进程既没有自己的地址空间,也没有向别的进程借用地址空间,那肯定出错。另外, 如果schedule()在中断服务程序内部执行,那也出错.
· 对当前进程做相关处理,为选择下一个进程做好准备。当前进程就是正在运行着的进程,可是,当进入schedule()时,其状态却不一定是TASK_RUNNIG,例如,在exit()系统调用中,当前进程的状态可能已被改为TASK_ZOMBE;又例如,在wait4()系统调用中,当前进程的状态可能被置为TASK_INTERRUPTIBLE。因此,如果当前进程处于这些状态中的一种,就要把它从运行队列中删除。
· 从运行队列中选择最值得运行的进程,也就是权值最大的进程。
· 如果已经选择的进程其权值为0,说明运行队列中所有进程的时间片都用完了(队列中肯定没有实时进程,因为其最小权值为1000),因此,重新计算所有进程的时间片,其中宏操作NICE_TO_TICKS就是把优先级nice转换为时钟滴答。
· 进程地址空间的切换。如果新进程有自己的用户空间,也就是说,如果next->mm与next->active_mm相同,那么,switch_mm( )函数就把该进程从内核空间切换到用户空间,也就是加载next的页目录。如果新进程无用户空间(next->mm为空),也就是说,如果它是一个内核线程,那它就要在内核空间运行,因此,需要借用前一个进程(prev)的地址空间,因为所有进程的内核空间都是共享的,因此,这种借用是有效的。
· 用宏switch_to()进行真正的进程切换,后面将详细描述。