进程调度的实现 陈莉君

来源:互联网 发布:python数据挖掘与实战 编辑:程序博客网 时间:2024/06/05 09:10

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()进行真正的进程切换,后面将详细描述。