时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查need_resched的值,如果必要,则调用调度程序schedule()主动放弃CPU。

时机4,如前所述,不管是从中断、异常还是系统调用返回,最终都调用ret_from_sys_call),由这个函数进行调度标志的检测,如果必要,则调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。

每个时钟中断timer interrupt)发生时,由三个函数协同工作,共同完成进程的选择和切换,它们是:schedule)、do_timer)及ret_form_sys_call)。我们先来解释一下这三个函数:

schedule):进程调度函数,由它来完成进程的选择调度);

do_timer):暂且称之为时钟函数,该函数在时钟中断服务程序中被调用,被调用的频率就是时钟中断的频率即每秒钟100次简称100赫兹或100Hz);

ret_from_sys_call):系统调用返回函数。当一个系统调用或中断完成时,该函数被调用,用于处理一些收尾工作,例如信号处理、核心任务等等。

这三个函数是如何协调工作的呢?

前面我们看到,时钟中断是一个中断服务程序,它的主要组成部分就是时钟函数do_timer),由这个函数完成系统时间的更新、进程时间片的更新等工作,更新后的进程时间片counter作为调度的主要依据。在时钟中断返回时,要调用函数ret_from_sys_call),前面我们已经讨论过这个函数,在这个函数中有如下几行:

cmpl $0, _need_resched

jne reschedule

……

restore_all:

RESTORE_ALL

reschedule:

call SYMBOL_NAME(schedule)

jmp ret_from_sys_call

这几行的意思很明显:检测 need_resched 标志,如果此标志为非0,那么就转到reschedule处调用调度程序schedule)进行进程的选择。调度程序schedule)会根据具体的标准在运行队列中选择下一个应该运行的进程。当从调度程序返回时,如果发现又有调度标志被设置,则又调用调度程序,直到调度标志为0,这时,从调度程序返回时由RESTORE_ALL恢复被选定进程的环境,返回到被选定进程的用户空间,使之得到运行。

Linux 调度程序和其他的UNIX调度程序不同,尤其是在“nice level”优先级的处理上,与优先权调度priority高的进程最先运行)不同,Linux用的是时间片轮转调度Round Robing),但同时又保证了高优先级的进程运行的既快、时间又长both sooner and longer)。而标准的UNIX调度程序都用到了多级进程队列。大多数的实现都用到了二级优先队列:一个标准队列和一个实时“real time”)队列。一般情况下,如果实时队列中的进程未被阻塞,它们都要在标准队列中的进程之前被执行,并且,每个队列中,“nice level”高的进程先被执行。

3.3 进程调度的依据

调度程序运行时,要在所有处于可运行状态的进程之中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct结构中有这么五项:

need_resched、nice、counter、policy 及rt_priority

need_resched: 在调度时机到来时,检测这个域的值,如果为1,则调用schedule() 。

counter: 进程处于运行状态时所剩余的时钟滴答数,每次时钟中断到来时,这个值就减1。当这个域的值变得越来越小,直至为0时,就把need_resched 域置1,因此,也把这个域叫做进程的“动态优先级”。

nice: 进程的“静态优先级”,这个域决定counter 的初值。只有通过nice(), sched_setparam()系统调用才能改变进程的静态优先级。

rt_priority: 实时进程的优先级

policy: 从整体上区分实时进程和普通进程,因为实时进程和普通进程的调度是不同的,它们两者之间,实时进程应该先于普通进程而运行,可以通过系统调用sched_setscheduler( )来改变调度的策略。对于同一类型的不同进程,采用不同的标准来选择进程。对于普通进程,选择进程的主要依据为counter和nice 。对于实时进程,Linux采用了两种调度策略,即FIFO先来先服务调度)和RR时间片轮转调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个实时进程是否应该运行,Linux采用了一个比较固定的标准。实时进程的counter只是用来表示该进程的剩余滴答数,并不作为衡量它是否值得运行的标准,这和普通进程是有区别的。

3.4 进程可运行程度的衡量

函数goodness)就是用来衡量一个处于可运行状态的进程值得运行的程度。该函数综合使用了上面我们提到的五项,给每个处于可运行状态的进程赋予一个权值weight),调度程序以这个权值作为选择进程的唯一依据。函数主体如下:

static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)

{     int weight;     /* 权值,作为衡量进程是否运行的唯一依据 *

weight=-1;

if (p->policy&SCHED_YIELD)

goto out; /*如果该进程愿意“礼让yield)”,则让其权值为-1 */

switch(p->policy)

{

/* 实时进程*/

case SCHED_FIFO:

case SCHED_RR:

weight = 1000 + p->rt_priority;

/* 普通进程 */

case SCHED_OTHER:

{     weight = p->counter;

if(!weight)

goto out

/* 做细微的调整*/

if (p->mm=this_mm||!p->mm)

weight = weight+1;

weight+=20-p->nice;       /*在剩余counter一样的情况下,短进程优先*/

}

}

out:

return weight;   /*返回权值*/

}

其中,在sched.h中对调度策略定义如下:

#define SCHED_OTHER             0

#define SCHED_FIFO              1

#define SCHED_RR                2

#define SCHED_YIELD             0x10

这个函数比较很简单。首先,根据policy区分实时进程和普通进程。实时进程的权值取决于其实时优先级,其至少是1000,与conter和nice无关。普通进程的权值需特别说明两点:

为什么进行细微的调整?如果p->mm为空,则意味着该进程无用户空间例如内核线程),则无需切换到用户空间。如果p->mm=this_mm,则说明该进程的用户空间就是当前进程的用户空间,该进程完全有可能再次得到运行。对于以上两种情况,都给其权值加1,算是对它们小小的奖励。

进程的优先级nice是从早期Unix沿用下来的负向优先级,其数值标志“谦让”的程度,其值越大,就表示其越“谦让”,也就是优先级越低,其取值范围为-20~+19,因此,20-p->nice)的取值范围就是0~40。可以看出,普通进程的权值不仅考虑了其剩余的时间片,还考虑了其优先级,优先级越高,其权值越大。

根据进程调度的依据,调度程序就可以控制系统中的所有处于可运行状态的进程并在它们之间进行选择。
 


相关内容