引起进程调度的原因有以下几类

  • Post author:
  • Post category:其他


http://baike.baidu.com/view/1380355.htm?fr=aladdin
引起进程调度的原因有以下几类
进程调度发生在什么时机呢?这与引起进程调度的原因以及进程调度的方式有关。
(1)正在执行的进程执行完毕。这时,如果不选择新的就绪进程执行,将浪费

处理机

资源。
(2)执行中进程自己调用阻塞原语将自己阻塞起来进入睡眠等状态。
(3)执行中进程调用了P

原语

操作,从而因资源不足而被阻塞;或调用了v原语操作激活了等待资源的进程

队列

(4)执行中进程提出I/O请求后被阻塞。
(5)在

分时系统

中时间片已经用完。
(6)在执行完

系统调用

等系统程序后返回用户进程时,这时可看作系统进程执行完毕,从而可调度选择一新的用户进程执行。
以上都是在

可剥夺方式

下的引起进程调度的原因。在CPU执行方式是可剥夺时.还有
(7)就绪队列中的某进程的优先级变得高于当前执行进程的优先级,从而也将引发进程调度。
两种占用CPU的方式
可剥夺式 (可

抢占式

preemptive):就绪队列中一旦有优先级高于当前执行

进程优先级

的进程存在时,便立即发生进程调度,转让

处理机

不可剥夺式 (不可

抢占式

non_preemptive):即使在就绪

队列

存在有优先级高于当前执行进程时,当前进程仍将占用

处理机

直到该进程自己因调用原语操作或等待I/O而进入阻塞、睡眠状态,或时间片用完时才重新发生调度让出处理机。

下面来了解一下



主要的调度算法及其基本原理。

1.

时间片轮转调度算法

时间片(Time Slice)就是分配给进程运行的一段时间。

在分时系统中,为了保证人机交互的及时性,系统使每个进程依次地按时间片轮流的方式执行,此时即应采用时间片轮转法进行调度。在通常的轮转法中,系统将所有的可运行(即就绪)进程按先来先服务的原则,排成一个队列,每次调度时把CPU 分配给队首进程,并令其执行一个时间片。时间片的大小从几ms 到几百ms 不等。

当执行的时间片用完时,系统发出信号,通知调度程序,调度程序便据此信号来停止该进程的执行,并将它送到运行队列的末尾,等待下一次执行。然后,把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片

。这样就可以保证运行队列中的所有进程,在一个给定的时间(人所能接受的等待时间)内,均能获得一时间片的处理机执行时间。

2.优先权调度算法

为了照顾到紧迫型进程在进入系统后便能获得优先处理,引入了最高优先权调度算法。当将该算法用于进程调度时,系统将把处理机分配给运行队列中优先权最高的进程,这时,又可进一步把该算法分成两种方式。

(1)

非抢占式优先权算法

(又称不可剥夺调度,Nonpreemptive Scheduling)

在这种方式下,系统一旦将处理机(CPU)分配给运行队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事件使该进程放弃处理机时,系统方可将处理机分配给另一个优先权高的进程。这种调度算法主要用于批处理系统中,也可用于某些对实时性要求不严的实时系统中。Linux 2.4 之前 kernel is nonpreemptive

(2)

抢占式优先权调度算法

(又称可剥夺调度,Preemptive Scheduling)

该算法的本质就是

系统中当前运行的进程永远是可运行进程中优先权最高的那个

。在这种方式下,系统同样是把处理机分配给优先权(weight,goodness()函数求出)最高的进程,使之执行。但是只要一出现了另一个优先权更高的进程时,调度程序就暂停原最高优先权进程的执行,而将处理机分配给新出现的优先权最高的进程,即剥夺当前进程的运行。因此,==在采用这种调度算法时,每当出现一新的可运行进程,就将它和当前运行进程进行优先权比较,如果高于当前进程,将触发进程调度。==这种方式的优先权调度算法,能更好的满足紧迫进程的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系统中。Linux 2.6开始也实现了这种调度算法。

3.多级反馈队列调度

这是时下最时髦的一种调度算法。其本质是:综合了时间片轮转调度和抢占式优先权调度的优点,即:优先权高的进程先运行给定的时间片,相同优先权的进程轮流运行给定的时间片。

4.实时调度

最后我们来看一下实时系统中的调度。什么叫实时系统,就是系统对外部事件有求必应、尽快响应。在实时系统中存在有若干个实时进程或任务,它们用来反应或控制某个(些)外部事件,往往带有某种程度的紧迫性,因而对实时系统中的进程调度有某些特殊要求。

在实时系统中,广泛采用抢占调度方式,特别是对于那些要求严格的实时系统

。因为这种调度方式既具有较大的灵活性,又能获得很小的调度延迟;但是这种调度方式也比较复杂。

(二)、



进程调度的时机

Linux 调度时机主要有:

(1)进程状态转换的时刻:进程终止、进程睡眠;

(2)当前进程的时间片用完时(current->counter=0);

(3)设备驱动程序;

(4)进程从中断、异常及系统调用返回到用户态时。

时机1,

进程要调用sleep()或exit()等函数进行状态转换,这些函数会主动调用调度程序进行进程调度。

时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4 是一样的。

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

时机4,如前所述,不管是从中断、异常还是系统调用返回,最终都调用ret_from_sys_call(),由这个函数进行调度标志need_resched的检测,如果必要,则调用调度程序。那么,

为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。

(三)、



进程调度的依据

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

need_resched、nice、counter、policy 及rt_priority

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

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

(3)nice: 进程的“静态优先级”,这个域决定counter 的初值。只有通过nice()、POSIX.1b sched_setparam() 或 5.4BSD/SVR4 setpriority()系统调用才能改变进程的静态优先级。

(4)rt_priority: 实时进程的优先级

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

(四)、进程可运行程度的衡量

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

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

#define SCHED_OTHER 0

#define SCHED_FIFO 1

#define SCHED_RR 2

#define SCHED_YIELD 0x10

static inline int goodness(struct task_struct

p, 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;

break;

/* 普通进程

/

case SCHED_OTHER:

{


weight = p->counter;

if(!weight)

goto out;

/

做细微的调整*/

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

weight = weight + 1;

weight += 20 – p->nice;

break;

}

default:

break;

}

out:

return weight; /

返回权值

/

}

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

(1)为什么进行细微的调整?如果p->mm 为空,则意味着该进程无用户空间(例如内核线程),则无需切换到用户空间。如果

p->mm=this_mm,则说明该进程的用户空间就是当前进程的用户空间,该进程完全有可能再次得到运行。对于以上两种情况,都给其权值加1,算是对它们小小的“奖励”。

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

(五)、进程调度的实现

调度程序在内核中就是一个函数schedule().函数所做的事解释如下:

• 如果当前进程既没有自己的地址空间,也没有向别的进程借用地址空间,那肯定出错。另外,如果schedule()在中断服务程序内部执行,那也出错。

• 对当前进程做相关处理,为选择下一个进程做好准备。当前进程就是正在运行着的进程,可是,当进入schedule()时,其状态却不一定是TASK_RUNNIG,例如,在exit()系统调用中,当前进程的状态可能已被改为TASK_ZOMBE;又例如,在wait()系统调用中,当前进程的状态可能被置为TASK_INTERRUPTIBLE。因此,如果当前进程处于这些状态中的一种,就要把它从运行队列中删除。

• 从运行队列中选择最值得运行的进程,也就是权值最大的进程。

• 如果已经选择的进程其权值为0,说明运行队列中所有进程的时间片都用完了(队列中肯定没有实时进程,因为其最小权值为1000),因此,重新计算所有进程的时间片,其中宏操作NICE_TO_TICKS 就是把优先级nice 转换为时钟滴答。

• 进程地址空间的切换。如果新进程有自己的用户空间,也就是说,如果next->mm 与next->active_mm 相同,那么,switch_mm()函数就把该进程从内核空间切换到用户空间,也就是加载next 的页目录。如果新进程无用户空间(next->mm 为空),也就是说,如果它是一个内核线程,那它就要在内核空间运行,因此,需要借用前一个进程(prev)的地址空间,因为所有进程的内核空间都是共享的,因此这种借用是有效的。

• 用宏switch_to()进行真正的进程切换。

三、进程切换

由于i386 CPU 要求软件设置TR 及TSS,Linux 内核只不过“走过场”地设置TR 及TSS,以满足CPU 的要求。但是,内核并不使用任务门,也不使用JMP 或CALL 指令实施任务切换。内核只是在初始化阶段设置TR,使之指向一个TSS,从此以后再不改变TR 的内容了。也就是说,每个CPU(如果有多个CPU)在初始化以后的全部运行过程中永远使用那个初始的TSS。同时,内核也不完全依靠TSS 保存每个进程切换时的寄存器副本,而是将这些寄存器副本保存在各个进程自己的内核栈中(task_struct中的thread_struct 结构)。

这样以来,TSS 中的绝大部分内容就失去了原来的意义。那么,当进行任务切换时,怎样自动更换堆栈?我们知道,新任务的内核栈指针(SS0 和ESP0)应当取自当前任务的TSS,可是,Linux 中并不是每个任务就有一个TSS,而是每个CPU 只有一个TSS。Intel 原来的意图是让TR 的内容随着任务的切换而走马灯似地换,而在Linux 内核中却成了只更换TSS 中的SS0 和ESP0,而不更换TSS 本身,也就是根本不更换TR 的内容。这是因为,改变TSS 中SS0 和ESP0 所化的开销比通过装入TR 以更换一个TSS 要小得多。因此,在Linux内核中,TSS 并不是属于某个进程的资源,而是全局性的公共资源。在多处理机的情况下,尽管内核中确实有多个TSS,但是每个CPU 仍旧只有一个TSS。


上面来自博客园中的参考来源