(四)进程调度
1. Linux调度的概念
进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统。
Linux提供了抢占式的多任务模式。
进程可以被分为I/O消耗型和处理器消耗型。I/O消耗型通常都是运行短短的一会儿,因为它在等待更多的I/O请求时最后总会阻塞;处理器耗费型进程把时间大多用在执行代码上,调度策略往往是尽量降低它们的调度频率。
调度策略通常要在两个矛盾的目标中间寻找平衡:进程响应迅速和最大系统利用率。Linux更倾向于优先调度I/O消耗型进程。
时间片是一个数值,它表明进程在被抢占前所能持续运行的时间。时间片过长会导致系统对交互的响应表现欠佳,让人觉得系统无法并发执行应用程序;时间片太短会明显增大进程切换带来的处理器耗时。I/O消耗型不需要长的时间片,而处理器消耗型的进程则希望越长越好。
2. Linux调度的实现
Linux给进程分配一个给定的处理器使用比。假如文本编辑器和视频解码程序是仅有的两个运行进程,并且又具有相同的nice值,那么处理器的使用比将都是50%————它们平分了处理器时间。但因为文本编辑器将更多的时间用于等待用户输入,因此它肯定不会用到处理器的50%。同时,视频解码程序无疑将能有机会用到超过50%的处理器时间,以便它能更快速地完成解码任务。在上述场景中,一旦文本编辑器被唤醒,CFS注意到给它的处理器使用比是50%,但是其实它却用得少之又少。特别是,CFS发现文本编辑器比视频解码器运行的时间短得多。这种情况下,为了兑现让所有进程能公平分享处理器的承诺,它会立刻抢占视频解码程序,让文本编辑器投入运行。
Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性地选择调度算法。这种模块化结构被称为调度器类,它允许多种不同地可动态添加地调度算法并存,调度属于自己范畴的进程。每一个调度器都有一个优先级,每次遍历调度类之后拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那一个程序。
现代进程调度器有两个通用的概念:进程优先级和时间片。时间片是指进程运行多少时间,进程一旦启动就会有一个默认时间片。具有更高优先级的进程将运行得更频繁,而且也会被赋予更多的时间片。
这本书在进程调度这一块主要介绍了linux2.6中的CFS公平调度器算法,重点关注了它的四个部分:时间记账、进程选择、调度器入口、睡眠和唤醒。
时间记账:CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久。
进程选择:CFS使用红黑树来组织可运行进程队列,当CFS需要选择下一个运行进程时,它会挑一个具有最小vruntime的进程。
调度器入口:进程调度的主要入口点是函数schedule(),它选择哪个进程可以运行,何时将其投入运行。它会调用pick_next_task(),以优先级为序,从高到低,依次检查每一个调度类,并且从最高优先级的调度类中,选择最高优先级的进程。
睡眠和唤醒:休眠有两种相关的进程状态:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。它们唯一的区别是处于TASK_UNINTERRUPTIBLE的进程会忽略信号,而处于TASK_INTERRUPTIBLE的进程如果接收到一个信号,会被提前唤醒并响应该信号。唤醒操作通过函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。通常哪段代码促使等待条件达成,它就要负责随后调用wake_up()函数。
3. 抢占和上下文切换
上下文切换:从一个可执行进程切换到另一个可执行进程。
每当一个新的进程被选出来准备投入运行的时候,schedule()就会调用context_switch()函数,它完成了两项基本工作:(1)调用switch_mm(),该函数负责把虚拟内存从上一个进程映射到新进程中;(2)调用switch_to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态。
4. 实时调度策略
Linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR,而普通的、非实时的调度策略是SCHED_NORMAL。
SCHED_FIFO: 它不使用时间片,处于可运行状态的SCHED_FIFO级的进程会比任何SCHED_NORMAL级的进程都先得到调度。一旦一个SCHED_FIFO级进程处于可执行状态,就会一直执行,直到它自己受阻塞或显式地释放处理器为止,只有更高优先级的SCHED_FIFO或SCHED_RR才能抢占SCHED_FIFO任务。只要有SCHED_FIFO级进程在执行,其他级别较低的进程就只能等待它变为不可运行态后才有机会执行。
SCHED_RR:SCHED_RR与SCHED_FIFO大体相同,只是SCHED_RR级的进程在耗尽事先分配给它的时间后就不能再继续执行了。当SCHED_RR任务耗尽它的时间片时,在同一优先级的其他实时进程被轮流调度,时间片只用来重新调度同一优先级的进程。对于SCHED_FIFO进程,高优先级总是立即抢占低优先级,但低优先级进程绝不能抢占SCHED_RR任务,即使它的时间片耗尽。