🐧 第四章 进程调度器
📄 一句话概括

想在真系统上看调度,看到的全是噪声。所以作者自己写了一个只留三个要素的模拟程序:剩余 CPU 时间、IO 等待、时间片。实验设计的三问是:观察什么、控制什么、排除什么。

📝 本节和前面几节的关系

第三章把进程讲清楚了:怎么造、怎么换、怎么结束。这一章开始讲【一堆进程抢几个 CPU】,而讲调度不能靠描述,只能靠实验——这一节先立实验的方法。

一、为什么不能在真系统上做这个实验

真系统上你量到的每个数字,都是几千个进程一起作用的结果:后台服务在跑、缓存冷热不定、内核线程在刷盘、你上一个实验的残留还在。 你要看的是【时间片变大 1 毫秒会怎样】这种细差别,而真系统的抖动比这个差别还大——信号被噪声埋掉。 所以作者的做法是:把调度器抽出来,写一个只有三个要素的模拟程序。代价是失真,收益是可控。

二、三个要素,多一个都不要

一、剩余 CPU 时间。每个进程还要跑多久才算完。没有它就没有终点。 二、IO 等待。跑一段就要停一段。没有它,CPU 永远不会空闲,多道程序设计这个题目就不存在了。 三、时间片。每次跑多久就换人。没有它,看不到切换的代价。 四样东西里最容易被忽略的是【时间片】——因为真实系统里它不是一个固定值,而是一个由负载动态调整的量。实验里必须把它定死,否则没法比较。

三、两个指标:吞吐量与延迟

吞吐量:单位时间里干完了多少活。它关心的是【总量】。 延迟(这里用平均周转时间):每件事从开始等到结束平均多久。它关心的是【个体】。 这两个指标经常打架,这是全章的主线: 1. 时间片调大 → 切换少、开销小 → 吞吐量好一点;但每件事等得久 → 延迟差。 2. 时间片调小 → 短作业能挤进来 → 延迟好;但切换多 → 吞吐量差。 没有【两个都好】的选项,只有【在这个场景下哪个更重要】。

四、这套方法能推广到别处

抽小模型 → 定死变量 → 看两个矛盾指标,这套方法不只用于调度器。 测数据库、测网络、测缓存命中,都是同一套:把要观察的东西单独拎出来,把干扰掐掉,然后盯住一对互相拉扯的指标。 判据很实用:如果你的实验只能得出【这样更快】而说不出【代价是什么】,那多半是没找对第二个指标。

五、动手 Demo:设计一个能看见调度器的实验

这一节没有代码要跑,只有一张【设计清单】和一张【要素表】。先想清楚要量什么,再动手写。

# 实验设计三问
# 1. 要观察什么?  -> 完成一批活儿要多久、每件事平均等多久
# 2. 控制什么?    -> 只让【时间片】与【核数】变,别的不动
# 3. 排除什么?    -> 用模拟程序,不用真系统(噪声比信号大)

# 模拟器必须有四样:剩余 CPU 时间 / IO 等待 / 就绪队列 / 时间片
class Proc:
    def __init__(self, name, cpu, io_each=0, io_len=0, weight=1):
        self.remain = cpu        # 还要跑多久
        self.io_each = io_each    # 每跑几拍要停一下
        self.io_len = io_len      # 停多久

实跑输出:

--------------------------------------------------------------------
设计一个能看见调度器的实验
--------------------------------------------------------------------
① 三个问题,决定实验有没有用

  问题             这一章怎么答                       答错的后果
  -------------------------------------------------------------------------
  要观察什么       完成一批活儿要多久、每个等多长     数字一堆,说明不了事
  控制什么         只有时间片与核数在变               变量混在一起,归因错
  怎么排除干扰     用模拟,不用真系统                 噪声盖住要看的信号

  **为什么不用真系统做实验**:真系统上一万个进程在抢、缓存冷热不定、
  后台还在跑别的服务——**你要看的那点差别,会被噪声盖掉。**
  **所以作者自己写了一个调度器模拟程序**:只保留【CPU 时间、IO 等待、时间片】三个要素。

② 模拟器里必须有哪几样东西

  要素              作用                 缺了会怎样
  --------------------------------------------------------
  剩余 CPU 时间     跑多久算完           没有终点,跑不完
  IO 等待           跑一段就要等         看不到 CPU 空闲
  就绪队列          谁在等着被挑         没法讲调度
  时间片            每次跑多久就换人     看不到切换的代价

  **四样齐了,才能同时量【吞吐量】与【延迟】——**
  **前者看干完了多少活,后者看每件事等了多久。这两件事经常打架。**

三问里最容易被跳过的是第三问。很多人搭完环境就开始量,量出来的数字自己都解释不了——因为干扰没掐掉。 四样要素里【就绪队列】最容易被漏:只写【选谁跑】是不够的,还得有个地方装着【谁在等】。 这一节的产出不是结论,是方法:后面九节全用这一套跑实验,你看懂了这张表,后面九节只是换参数。

30 秒实验

  1. 列出你手上任何一个性能问题的【吞吐量指标】与【延迟指标】各是什么。如果一个都列不出来,说明还没找到该量什么。
  2. time ls -R / 与 time find / -name x 各跑一次,想一想这两条命令的吞吐量与延迟分别是什么。
  3. man 7 sched——内核自己的调度文档,看它怎么描述策略与优先级。

常见误区

  1. 【模拟实验不算实验】。模拟恰恰是看清机制的常用手段:变量可控、结果可复现、可以跑极端的参数。真系统做不到这三点。
  2. 【吞吐量高就是好】。吞吐量高可能是靠【让少数活干完、多数活饿着】换来的。必须两个指标一起看。
  3. 【实验只要跑一次就能下结论】。一次结果可能刚好撞上抖动。重复多次、取中位、看分布,这是最低要求。
自测三问(点开对答案) 1. 为什么调度实验要用模拟程序,而不是在真系统上量? —— 真系统的抖动比要观察的差别还大,**信号会被噪声埋掉**。模拟程序变量可控、结果可复现、能跑极端参数。 2. 模拟器必须有哪几样要素?哪一样最容易被漏? —— 剩余 CPU 时间、IO 等待、时间片、**就绪队列**。就绪队列最容易被漏——只写【选谁跑】不够,还得有地方装【谁在等】。 3. 吞吐量与延迟为什么会打架? —— 时间片大→切换少、吞吐好,但每件事等得久;时间片小→短作业能挤进来、延迟好,但切换多、开销大。**两者是同一笔账的两面。**

相关笔记