marp: true theme: default paginate: true _paginate: false header: '' footer: '' backgroundColor: white

第八讲 多处理器调度

第三节 Linux O(1) 调度



向勇 陈渝 李国良 任炬

2024年春季

课程幻灯片列表


提纲

1. SMP 和 早期Linux 内核

  1. Linux O(n)调度器
  2. Linux O(1) 调度器

Linux调度器的演进

  • O(n) 调度器:内核版本 2.4-2.6
  • O(1) 调度器:内核版本 2.6.0-2.6.22
  • CFS 调度器:内核版本 2.6.23-至今

w:650


调度器需要考虑的关键问题

  • 采用何种数据结构来组织进程
  • 如何根据进程优先级来确定进程运行时间
  • 如何判断进程类型(I/O密集、CPU密集型;实时、非实时)
  • 如何确定进程的动态优先级:影响因素
    • 静态优先级、nice值
    • I/O密集型和CPU密集型产生的优先级奖惩
  • 如何适配多处理器情况

SMP 和 早期Linux 内核

  • Linux 1.2
    • 环形队列 + Round Robin调度策略
  • Linux 2.0
    • SMP 支持由一个“大锁”组成,“大锁”对内核访问串行化
    • 在用户态支持并行,Linux 内核本身并不能利用多处理器加速
  • Linux 2.2
    • 引入调度类(real-time, non-real-time)

提纲

  1. SMP 和 早期Linux 内核

2. Linux O(n)调度器

  1. Linux O(1) 调度器

Linux 2.4 内核:Linux $O(n)$调度器

w:700


Linux $O(n)$调度器

  • 使用多处理器可以加快内核的处理速度,调度器是复杂度为 $O(n)$
    • $O(n)$ 这个名字,来源于算法复杂度的大$O$表示法
    • 字母$n$在这里代表操作系统中的活跃进程数量
    • $O(n)$ 表示这个调度器的时间复杂度和活跃进程的数量成正比

bg right:43% 90%


Linux $O(n)$ 调度算法的思路

  • 把时间分成大量的微小时间片(Epoch)
  • 每个时间片开始时
    • 计算进程的动态优先级
    • 将进程优先级映射成缺省时间片
    • 然后选择优先级最高的进程来执行
  • 进程被调度器切换执行后,可不被打扰地用尽这个时间片
  • 如进程没有用尽时间片,则剩余时间增加到进程的下一个时间片中

$O(n)$ 调度算法的复杂度

O(n)调度算法的复杂度为$O(n)$

  • 每次使用时间片前都要检查所有就绪进程的优先级
  • 检查时间和进程中进程数目$n$成正比

Linux O(n)调度器数据结构

  • 只用一个 global runqueue放置就绪任务
  • 各个 core 需要竞争同一个 runqueue 里面的任务

bg right:52% 90%


Linux $O(n)$调度器的缺点

  • $O(n)$的执行开销
    • 当有大量进程在运行时,这个调度器的性能将会被大大降低
  • 多处理器竞争访问同一个 runqueue 里面的任务
    • $O(n)$调度器没有很好的可扩展性(scalability)

bg right:40% 100%


提纲

  1. SMP 和 早期Linux 内核
  2. Linux O(n)调度器

3. Linux O(1) 调度器


Linux O(1) 调度器

Linux 2.6 版本的调度器是由 Ingo Molnar 设计并实现的。

  • 为唤醒、上下文切换和定时器中断开销建立一个完全 O(1) 的调度器 bg right:52% 90%

Linux O(1) 调度器的思路

  • 实现了per-cpu-runqueue,每个CPU都有一个就绪进程任务队列
  • 采用全局优先级
    • 实时进程0-99
    • 普通进程100-139

bg right:45% 90%


Linux O(1) 调度器的思路

  • 活跃数组active:放置就绪进程
  • 过期数组expire:放置过期进程 bg right:62% 90%

Linux O(1) 调度器的思路

  • 每个优先级对应一个链表
  • 引入bitmap数组来记录140个链表中的活跃进程情况 bg right:65% 90%

常用数据结构访问的时间复杂度

  • 满足 O(1) 的数据结构?
  • 常用数据结构的四种基本操作和时间复杂度
    • access:随机访问
      • array: 平均情况和最坏情况均能达到 O(1)
      • linked list 是 O(N)
      • tree 一般是 O(log N) bg right:53% 100%

常用数据结构的搜索操作

  • search:搜索
    • hash table 时间复杂度是 O(1),但它最坏情况下是 O(N)
    • 大部分 tree(b-tree / red-black tree)平均和最坏情况都是 O(log N) bg right:53% 100%

常用数据结构的插入和删除操作

  • insert/deletion:插入和删除
    • hash table 时间复杂度是 O(1),但它最坏情况下是 O(N)
    • linked list,stack,queue 在平均和最坏情况下都是 O(1)

bg right:43% 100%


Linux O(1) 调度器的时间复杂度

  • 进程有 140 种优先级,可用长度为 140 的数组去记录优先级。
    • access 是 $O(1)$
  • 位图bitarray为每种优先级分配一个 bit
    • 如果这个优先级队列下面有进程,那么就对相应的 bit 染色,置为 1,否则置为 0。
    • 问题简化为寻找位图中最高位是 1 的 bit(left-most bit),可用一条CPU 指令实现。

bg right:37% 100%


Linux O(1) 调度器的时间复杂度

  • 每个优先级下面用一个FIFO queue 管理这个优先级下的进程。
    • 新来的插到队尾,先进先出,insert/deletion 都是 $O(1)$

bg right:43% 100%


Linux $O(1)$活跃数组和过期数组

活跃数组(Active Priority Array, APA)过期数组(Expired Priority Array, EPA)

  • 在 active bitarray 中寻找 left-most bit 的位置 x;
  • 在 APA 中找到对应队列 APA[x];
  • 从 队列APA[x] 中取出一个进程;

bg right:40% 100%


Linux $O(1)$活跃数组和过期数组

  • 对于当前执行完的进程,重新计算其优先级,然后 放入到 EPA 相应的队列EPA[priority];
  • 如果进程优先级在 expired bitarray 里对应的 bit 为 0,将其置 1;
  • 如果 active bitarray 全为零,将 active bitarray 和 expired bitarray 交换;

bg right:40% 100%


Linux O(1) 调度器的多核/SMP支持

  • 按一定时间间隔,分析各CPU负载
    • 在每个时钟中断后进行计算CPU负载
    • 由负载轻的 CPU pulling 进程而不是 pushing进程 bg right:52% 100%

小结

  1. SMP 和 早期Linux 内核
  2. Linux O(n)调度器
  3. Linux O(1) 调度器

参考文献

  • http://www.wowotech.net/process_management/scheduler-history.html
  • https://courses.engr.illinois.edu/cs423/sp2018/slides/13-linux-schedulers.pdf
  • https://www.cnblogs.com/vamei/p/9364382.html
  • https://cloud.tencent.com/developer/article/1077507?from=article.detail.1603917
  • https://www.eet-china.com/mp/a111242.html
  • https://loda.hala01.com/2017/06/linux-kernel.html
  • https://jishuin.proginn.com/p/763bfbd2df25