调度的本质是选一个当前最值得运行的 task 运行,而如何选 task,要看当时的背景,而不是 task 的特性,同一个 task,当它处理键盘输入时,它就重要,当它从广域网下载时,相对调度时间尺度,它就不重要,可是 Linux 内核调度器引入的极少的启发式调整却非常勉强。
四象限工作法可以极简逻辑打破这种僵化,仅用重要性和紧急性定义任务特征。类比医院的工作流程:
- 紧急救护通过急诊服务;
- 普通就诊通过门诊服务;
- 大慢病通过长期病房服务;
- 小慢病通过复查随访服务;
- 大急病通过急诊服务;
- 小急病通过护士台服务;
- 紧急病情平稳后转入普通门诊;
- 紧急手术平稳后转入普通病房;
完全根据病情的轻重缓急安排医疗服务,而不是通过病人的特征来安排(虽然也有特需,专家号等类似 Linux 内核的弊病,但大体还好),这天然避免了同一个病人长期占据资源的情况,而同一个病人长期病重在概率统计的意义上就是病人死亡。
Linux 内核虽无四象限概念,但处理重要且紧急(Q1)任务的核心的是拆分特权而非强化特权,通过上下半部机制,将 Q1 任务拆分为紧急上半部与非紧急下半部,杜绝其长期独占 CPU,兼顾响应与稳定,这是类似医疗服务的好实例,Linux 对紧急的中断上半部的处理恰恰就不记名,不挂失,编程的人称作匿名任何上下文。
只有按背景拆分特权成为通用范式,四象限调度才能被 Linux 内核支持,回归调度本质,兼顾 Q1 响应与系统稳定。换句话说,需要在 Linux 内核中支持一种机制完成特权拆分,在 task 切换等任意时刻设置紧急程度和重要性,而不是在调度时计算 task 动态优先级,也就是取消掉 task 优先级,用场景优先级取而代之。
举几个例子:
- 当 task 将做网络传输时,降低紧急性和重要性,因为网络太慢了;
- 当网络传输完成时,提升其紧急性;
- 当数据来自连接 RTT 小于 40us 时(如果能取到,比如 TCP),提升紧急性;
- 当响应鼠标键盘时,提升关联 task 的紧急性;
- 当被调度 task 的 cache 命中超过阈值,提升重要性;
- ...
全是情景驱动而不是 task 优先级驱动,如此似乎可自适应所有背景了。
虽然不具备普遍意义,Windows,MacOS 这方面做得很像回事,这也是它们作为桌面系统保持流畅不卡顿的原因。
与多数人认为的这只是一个提供给业务的高级 API 不同,我认为这需要对 Linux 底层架构做颠覆,而这首先非常难,其次如何做,以及有无必要也是另说。
和 Unix 老式传统不同,像 Windows 一开始就是基于背景的,诸如中断请求级别 IRQL,而在调度器看来,Windows task 远没有 Linux task 那种统一的优先级调度的描述。
从最早的传统寻找这种根本性不同的根源,Unix/Linux 一开始就是多任务处理系统,它追求公平和高吞吐,响应延时是后来加入的,直到最后对响应度和低延时的支持都只是在 "基于优先级调度" 的微调,所谓启发式 "抢占",但即使再抢占,也要兼顾哪怕最不重要的 task 防饿死。
Windows 系统反其道,它最初衍生自单任务交互系统,追求高响应低延时,可将其视作单独任务内部的处理流分发,鼠标中断直接关联到特定任务处理,这个任务就 "挂" 在如此底层以很高的优先级准备抢占,而不像 Linux 内核先统一 wakeup,再做调度。当然,有得必有失,Windows 虽响应及时,但也因为响应太及时,造成依赖性饥饿而经常死机。
在去掉这些毛边后,最终会看到 Linux 调度的目标是 "全局的,实时的,完全的加权公平",而 Windows 调度的目标则是 "高优先级立即抢占",它们的适应性和扩展性因此而不同。
强抢占调度并不适合大规模协作场景,首先,抢占点颇具主观性,也不具备持续性,试想在 256 核心系统强抢占,优先级反转依赖,cache 乒乓会让系统很快卡死,分级处理事件是好的,分级医疗是又一个典型的例子。而 Linux 的加权公平方式虽然响应性差一些,但它能保持长期的以公平和稳定为前提的高吞吐,而高吞吐和低延时不可兼得,不能既要还要。
so?Linux 分两边,作为 server,它需要在多核心保持高吞吐,而在 client 端,它需要高响应性,为此,Linux client 需要引入诸如四象限工作流程的调度器,但从扩展性的角度,为了支持这种调度器,正如 Linus 早年所说,要放弃在太多核上并行,太多核上并行是为 server 准备的,而不是 client。
但凡涉及规模扩展,为避免复杂性随规模的幂律,就不能依赖和交错,所以加权公平调度和四象限调度简直位于跷跷板的两端。所谓在小规模系统,严格排列守序,而在大规模系统,反而需要简单规则,混乱守序了。
浙江温州皮鞋湿,下雨进水不会胖。