第3讲:进程调度

多任务系统根据任务调度的模式不同,可以分为两类:

  • 抢占式多任务(preemptive multitasking)。“抢占”就是强制挂起正在执行的进程。
  • 非抢占式多任务(coorperative multitasking)。除非进程自己主动停止(让步,yielding),否则会一直执行。

进程可分为:IO消耗型、CPU消耗型。对于交互式应用和桌面系统,IO型的优先级要高,保证其能快速响应;同时也要兼顾“最大系统利用率”(高吞吐量)。

多数用户图形界面程序(GUI)都属于IO密集型,即便它们从不读取或者写入磁盘,它们也会在多数时间里都在等待来自鼠标或者键盘的用户交互操作。

Linux 调度程序很多,比如 O(1) 调度(更适合大服务器,而不适合存在很多交互程序的桌面系统)。本书主要讲解的是 CFS 完全公平调度算法。

一、调度方式

(1)优先级调度

  • nice值(-20~19):值越大、优先级越低。
  • 实时优先级(0-99)。值越大、优先级越高。任何实时进程的优先级都高于普通的进程。

(2)时间片

  • 调度策略必须规定一个默认的时间片,这是进程运行的“基本单位”。
  • CFS调度分配的是在一个调度周期内、处理器使用比例,而非时间片。

二、调度算法

Linux调度器是模块化实现的,“调度器类”允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程。CFS 便是一个针对普通进程的调度类 SCHED_NORMAL

nice 值映射到时间片存在的问题?

也就是让不同 nice 值对应到绝对时间片,比如 0(nice)-100(time), 1-95...

  • 无法同时适应 CPU 负载高和低两种情况。如果 CPU 负载较低,可能进程需要频繁切换,开销太大。
  • nice 值越靠近边界、时间片波动越大。比如 18-10, 19-5 就变化了一倍。

就是说:“把进程的 nice值减小1”所带来的效果极大地取决于其 nice 的初始值。

  • 由于时间片必须是定时器节拍的整数倍。这导致不同时间片之间最少差一拍(1ms 或 10ms),做不到细粒度控制。
  • 某些特殊进程可能通过篡改优先级,打破公平原则,获得更多时间片。

二、CFS 调度

对于单核处理器来说没法做到完美调度(多个进程没法同时使用 cpu 资源)。CFS的做法是允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,而不再采用分配给每个进程时间片的做法了。

CFS在所有可运行进程总数基础上计算出一个进程应该运行多久,而不是依靠 nice 值来计算时间片:越高的nice值(越低的优先级)进程获得更低的处理器使用权重

CFS 综合考虑了所有因素后,不断计算当前调度周期内每个进程的vruntime,实现动态调度。

CFS 的调度周期称为“目标延迟”,越小的调度周期将带来越好的交互性,同时也更接近完美的多任务。但是你必须承受更高的切换代价和更差的系统总吞吐能力。

关键:绝对的 nice 值不再影响调度决策:只有相对值才会影响处理器时间的分配比例。

nice=5 的进程的权重将是 nice=0 进程的 1/3。如果我们的目标延迟是20ms,那么这两个进程将分别获得15ms和 5ms 的处理器时间。比如我们的两个可运行进程的 nice 值分别是10和15,它们分配的时间片将是多少呢?还是15和 5ms!

CFS 调度的实现关键在于四个部分:
(1)时间记账。所有的调度器都必须对进程运行时间做记账。CFS 调度器实现为 struct sched_entity。经过所有可运行进程总数的标准化得到虚拟运行时 vruntime。

虚拟时间是以 ns 为单位的,所以 vruntime 和定时器节拍不再相关。虚拟运行时间可以帮助我们逼近 CFS 模型所追求的“理想多任务处理器”:所有进程都被公平调度。

(2)进程选择。运行队列是红黑树(自平衡二叉搜索树 BST),利用快速查找特性可以迅速找到最小 vruntime 值的进程。

  • 挑选下一个任务。在建树时便将最小 vruntime 缓存在 rb_leftmost 变量中,可以直接使用。
  • 向 rb_tree 加入进程。当进程变为可运行状态(被唤醒)或通过 fork 调用第一次创建进程。利用 rb_tree “左节点永远小于右节点”的特性进行插入和缓存 rb_leftmost 的操作。
  • 删除进程。当进程变为不可运行状态或终止(结束运行)时。

(3)调度器入口:

(4)睡眠与唤醒:

进程切换大致由这几步组成:

  • 进程的调度是由内核管理的,因此首先会进行用户态到内核态的切换。
  • 保存被中断进程的上下文。
  • 修改被中断进程的状态信息,并加入到相应的状态队列。
  • 调度一个新的进程,加载其上下文。
posted @   7hu95b  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示