Linux内核设计与实现 四、 进程调度

    科技2022-07-20  117

    作用:在可运行态进程之间分配有限的处理器时间资源的内核子系统。

    4.1 多任务

    多任务操作系统

    概念:  同时并发地交互执行多个进程的操作系统

    分类: ●抢占式多任务:  所有Unix的变体和许多现代操作系统一样,Linux提供了抢占式的多任务模式  抢占:由调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行计划,这个强制的挂起动作叫作抢占。  进程在被抢占之前能够运行的时间是预先设置好的,这个事件叫做时间片。(Linux未采用)

    ●非抢占式多任务:  非抢占式多任务模式下,除非进程自己主动停止运行,否则它会一直执行。  让步:进程主动挂起自己的操作称为让步。一个决不做出让步的悬挂进程就能使系统崩溃。

    4.2 Linux的进程调度

    第一版到2.4版本:原始设计

    相当简陋,难以胜任

    2.5版本-2.6.23:O(1)调度程序

    对于大服务器的工作负载很理想,但是在很多交互程序要运行的桌面系统则表现不佳,因为其缺少交互进程。

    2.6.23开始:完全公平调度算法(CFS)

    4.3 策略

    策略决定调度程序何时让什么进程运行

    I/O消耗型和处理器消耗型进程

    I/O消耗型进程通常运行一小会,然后阻塞在等待I/O请求,多数GUI都属于I/O密集型。

    Linux为了保证交互式应用和桌面系统的性能,更倾向于优先调度I/O消耗型进程。

    进程优先级

    两种不同的优先级范围: ●nice值:  默认为0,越大的nice值意味着更低的优先级

    ●实时优先级:  越高的实时优先级数值意味着进程优先级越高。

    时间片

    概念:时间片是一个数值,表明进程在被抢占前所能持续运行的时间。

    长短:过长会导致系统对交互响应迟钝,过短会增大进程切换带来的处理器耗时

    作用:多数操作系统中,是否要将一个进程立刻投入运行(抢占当前进程),是完全由进程优先级和是否有时间片决定的。

    调度策略

    理想的调度器:  假如文本编辑器和视频编码器进程同时运行,有两个目标: 1.文本编辑程序获得更多的处理器时间,因为它属于交互式应用,在需要时应该确保它得到处理器时间。 2.文本编辑器能在被唤醒时抢占视频解码程序

    4.4 Linux调度算法

    CFS

    概念  CFS完全摒弃时间片而是分配给进程一个处理器使用比重。通过这种方式,CFS确保了进程调度中能有恒定的公平性。

    做法  CFS的做法是允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,而不采用时间片的做法。 最小粒度  当可运行任务数量趋于无限时,各自获得的处理器使用比趋于0,会造成不可接受的切换消耗。CFS为此引入了每个进程获得的时间片底线,默认为1ms。

    完全公平?  CFS不是完美的公平,只是近乎完美的多任务。但是确实在多进程环境下降低了调度延迟带来的不公平性。

    4.5 Linux调度的实现

    四个组成部分: 1.时间记账 2.进程选择 3.调度器入口 4.睡眠和唤醒

    时间记账

    调度器实体结构  名为se的成员变量,嵌入在进程描述符struct task_struct内。进程描述符见第三章

    虚拟运行时间  vruntime变量存放进程的虚拟运行时间(加权后的)。CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久。

    进程选择

    进程选择  CFS会挑一个具有最小vruntime的进程来运行,这是CFS调度算法的核心。  CFS使用红黑树来组织可运行进程队列。

    ●挑选下一个任务:利用红黑树迅速找到vruntime值最小的进程 ●向树中加入进程:在进程变为可运行状态(被唤醒)或者是通过fork()调用第一次创建进程,CFS将进程加入rbtree。 ●从树中删除进程:删除动作发生在进程堵塞(不可运行态)或终止时(结束运行)

    调度器入口

     进程调度的主要入口点是函数schedule(),schedule()通常要和一个具体的调度类相关联。

     该函数中唯一重要的事情是调用pick_next_task()。pick_next_task()会以优先级为序,从高到低依次检查每一个调度类,并从最高优先级的调度类中选择最高优先级的进程。

    睡眠和唤醒

    休眠:  进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待队列。

    休眠的两种状态 ●TASK_INTERRUPTIBLE:接收到信号会被提前唤醒并响应该信号 ●TASK_UNINTERRUPTIBLE:忽略信号

    等待队列:  休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。

    过程:代码见书4.5.4 进程把自己放入等待队列中并设置成不可执行状态。当与等待队列相关的事件发生后,队列上的进程会被唤醒。还要注意避免产生竞争条件。

    唤醒: 函数wake_up()唤醒指定的等待队列上的所有进程 虚假唤醒:  一种可能性是条件变量的等待被信号中断  另一种可能性是唤醒后被另一个进程抢先了。

    4.6 抢占和上下文切换

    上下文切换  从一个可执行进程切换到另一个可执行进程。

     有两项基本工作:  ●调用switch_mm(),该函数负责把虚拟内存从上一个进程映射切换到新进程中  ●调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。包括保存、恢复栈信息和寄存器信息等。

    内核调用schedule() 原因:不能只靠用户程序代码显示调用schedule(),可能会永远执行下去。

    标志:need_resched表明是否需要重新执行一次调度。通过检查该标志来判断是否调用schedule()

    用户抢占

    发生情况 ●从系统调用返回用户空间时 ●从中断处理程序返回用户空间时

    如何抢占 返回的时候查看need_resched标志,来判断是否调用schedule()

    内核抢占

    Linux的特点  支持内核抢占,其他大部分Unix变体和大部分操作系统不支持内核抢占,内核代码可以一直执行到完成。

    调度的安全性  只要重新调度是安全的,内核就可以在任何时间抢占正在执行的任务。  只要没有持有锁,内核就可以进行抢占。

    发生情况 ●中断处理程序正在执行,且返回内核空间之前。 ●内核代码再一次具有可抢占性的时候 ●内核中的任务显式调用schedule() ●内核中的任务阻塞

    4.7 实时调度策略

    两种实时调度策略

    静态优先级  两种实时算法都是静态优先级,不实时计算动态优先级。保证给定优先级别的实时进程总能抢占优先级比它低的进程。

    两种策略具体见书 ●SCHED_FIFO:简单、先入先出,不基于时间片,可以一直执行下去,只有更高优先级的任务才能抢占 ●SCHED_RR:带有时间片的SCHED_FIFO

    软实时工作方式  内核调度进程,尽力使进程在它的限定时间来前运行,但内核不保证总能满足这些进程的要求。但是Linux的实时调度算法的性能还是不错的

    4.8 与调度相关的系统调用

    与调度策略和优先级相关

    与处理器绑定有关

    放弃处理器时间

    作用:  sched_yield()系统调用,让进程显式地将处理器时间让给其他进程。

    原理:  将进程从活动队列中移到过期队列中,确保在一段时间内它都不会再执行。

    Processed: 0.010, SQL: 8