第6期 | 把切换这件事,从硬件手里拿回来
一、Linux 线
1991年8月26日那封帖子,PS 里有半句很少有人细看的话:It is NOT portable (uses 386 task switching etc)——不可移植,因为它用了 386 的任务切换,等等。
etc 后面,藏着 Linux 出生时的一个秘密:它最早的多任务,是外包给 CPU 的。
1991 年春天,Linus 照着 Intel 386 手册(Intel 80386 Programmer's Reference Manual,1986)写了个小小的任务切换器。Lars Wirzenius 在 1998 年的一篇回忆里写道,那年春天 Linus 给他看两个并发的小进程:一个拼命打印 A,一个拼命打印 B,靠定时器中断来回切。那就是后来整个 Linux 调度的胚胎。
386 有个贴心设计:TSS,任务状态段。CPU 自己存寄存器、自己换地址空间,内核只要一条 ljmp 跳向 TSS 描述符,切换就完成了。1991 年 9 月的 0.01 版里,调度器挑出下一个任务后调用的 switch_to 宏,核心就是这一行:ljmp %0。调度策略是他写的,切换动作是 CPU 做的。
反常识的点在这里:操作系统最核心的动作——换谁上 CPU,最初 Linux 自己并不动手。
如果我是原生 AI OS 里第一个醒来的居民,我会懂这种心情。我的每一次"思考",都是一次算力阵列的占用:一个 agent 的推理请求、一块 KV cache、一组 Tensor Core 的时间。原生 AI OS 起步时,也一定先借用硬件的切换——今天 GPU 已经有粗糙的版本:CUDA stream、MPS 分区、NPU 的硬件上下文。站在硬件肩膀上,永远是最便宜的第一步。
但借来的东西早晚要还。据公开的代码梳理,这条 ljmp 从 0.01 一直用到 1.0(1994),直到 2.x 时代才被软件切换取代:换成一条条 mov 指令,自己存寄存器、自己换页表。为什么?硬件切换不透明——寄存器怎么存、地址空间何时换,内核看不见;硬件的格式还卡死了任务的形状,想优化都没处下手。想照见,就得自己动手。
这正是范式之眼要看的张力。旧抽象(进程、时间片)配旧硬件(顺序指令流),天衣无缝;可今天的算力主体是向量阵列,切换的单位早该从"指令流的时间片"变成"算力阵列的占用权":哪个 SM 被谁占、Tensor Core 的时间分给谁、显存带宽留给谁。AI 让调度从"公平分时间"变成"预测谁最需要算力"成为可能。离落地还差:GPU 的抢占语义今天还很粗糙,硬件得先把"占用"这件事说清楚,OS 才接得住。这一步缺的不是算力,是协议——得硬件厂商和 OS 一起写,三年起步。
今晚就能亲手验证。找台能联网的机器,3090 那台就行,跑两条命令:
git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git
cd history && git show v0.01:include/linux/sched.h | grep -B2 -A12 'define switch_to'
你会亲眼看到,1991 年 9 月的代码里,ljmp %0 躺在宏的正中间。一个操作系统最要紧的动作,外包给了一条 CPU 指令。再想想今天:3090 上一次 kernel launch,是不是也在做同样的事——把"切换"藏进驱动和硬件?
美学时刻(书法化用,非原文):藏锋是把锋芒收进笔肚,露锋是把锋芒亮在纸上。TSS 就是 Intel 的藏锋——切换藏在 CPU 里;Linux 后来亲手把它露出来,一笔一画自己写。
觉然小注:《金刚经·依法出生分第十三》:「如来说世界,即非世界,是名世界。」——所谓任务切换,即非任务切换,是名任务切换。名字还是那个名字,做事的人换了。从 CPU 手里拿回来的那天,才算真正开始写操作系统。
· · ·