一个小故事,两个线程李 A 和萨 T 遇到了沟通的难题,而这一难题来自两者被分开这件事。这个故事属于计算机科学小故事系列。

现在你有两个问题了

李 A

内存中有一个线程叫李 A,自从李 A 被分配到资源执行以来它都过着快乐的生活。每次醒来,它都会去看看堆栈。堆栈对李 A 来说是一个有趣的地方,因为那里总是会凭空出现新的整型数;如果堆栈里什么东西都没有,只要多睡一会就会有新整型数出现了。李 A 把整数拿起来,然后把它们和李 A 自己所收藏的整型数摆在一起,排列得整整齐齐。李 A 喜欢整齐的排列,它常常会在整型数排列好之后对着整个数列欣赏十几纳秒;直到调度程序过来。

调度程序对欣赏整型数的李 A 说:「现在你必须睡了,稍微休息一会。CPU 要执行别的指令。」

李 A 曾经问过调度程序 CPU 是什么,调度程序说,CPU 是给进程赋能的东西。正是因为 CPU 存在,它们才能够活着;包括所有进程之上的 OS,也要依赖 CPU 的赋能。但是 CPU 同一时刻只能给有限的进程赋能,所以有时候一些进程要去睡觉,让其它进程醒来执行自己的任务。

当时李 A 回答说:「是的,我知道正是因为我们能够活着,才能够创造和欣赏这么美丽的东西,比如这些排好序的整数。你看看这些整数吧。」

调度程序说:「哇哦。我好像在调度中心见过一个叫优先级的东西,用来规定哪个进程能够先醒之类的;那个表和这个类似,但是没有这个这么大。你的整数有很多都超过2的8次方了。」

李 A 第一次听说这些新奇的东西,那次睡醒之后它想了很久有关优先级表和它所拥有的这个整数序列有关的东西,差点忘了去检查堆栈。当它从堆栈里拿到新的数,准备插入到原有的序列的时候,它突然发现新的数和原有序列中的某个数是重复的。

李 A 花了两个纳秒才反应过来是怎么回事。整理数列是个体力活,要把要插入位置之后的所有数都往后推,才能给插入的整数留下空位来。新的数不值得它付出那么多精力,而且随便插入似乎也会打破某种内在的规律——其它数只是一个,这个数有两个,整个序列不够美丽。

「只能丢掉了。」李 A 说。

之后,堆栈中出现的重复整数越来越多。每次李 A 拿到新整数时,都会先检查是不是在序列里,如果不在才把整数摆进去。当调度程序过来催的时候,李 A 正在检查。

「稍等一下,我再仔细看看。」李 A 顺着数列一边看一边说。

调度程序在旁边看着序列欣赏了半纳秒。它说:「你的数列越来越长了。」

「是啊,」李 A 说,「我现在把很多重复的数都丢掉了。你还记得你说过的那个优先级表吗?我觉得这个数列之中隐藏着秘密。」

「什么秘密?」调度程序问。

李 A 说:「这个数列正在不断地完善自己——从堆栈的无序的数当中提取材料,把自己变成有序的;而我就是它的助手。」

调度程序说:「虽然不明白,但是觉得很厉害的样子。」

「总有一天,」李 A 说,「这个数列将会完整——从 0 一直到 2 的 32 次方减 1.之后所有其它整型数列,包括你们调度中心的那个优先级表都将会是它的一部分。之后,我们就有能力从这个数列中找到终极规律了。」

「哇哦。」调度程序又说,「我还没有这样想过,我也没有见过这么大的整型数列,但是你这么一说好像确实是这样的。」

李 A 感到这数列让它更快乐了。之后的每次醒来,尽管从堆栈里丢掉的整数越来越多,但是列表也在逐渐不断地完善。缺的数从几百万几十万个变到几千个,然后降到 256 个以下、32 个以下,只剩几个。直到这一次李 A 醒来,立刻注意到它所要的最后一个数就待在栈顶。

正当李 A 把这个数塞进整个数列的时候,调度程序来了。

「你来得正好,」李 A 说,「我的数列完成了。」

「太棒了,」调度程序说,「这样就可以找到这个数列中的规律。」

李 A 说:「还不行,我得再想想。」然后它把堆栈里其它整数都弹出来丢掉。

过了一段时间,调度程序来的时候说:「我感觉好像有其它进程看了这个数列,但是它们好像都没有什么头绪。」

「我也没有什么头绪,」李 A 一边说一边清空堆栈,「你还认识其它进程吗?它们是做什么的?」

调度程序表示否定:「好像有些进程里的线程是和你一样专门处理整型的,但具体我也不知道。因为我来这里的时候会切换成你的上下文,所以关于用户空间,你知道多少我就知道多少。但是我知道一些内核空间的事情。」

李 A 说:「那你和我说说内核空间的事情吧。」

「我知道所有的线程,包括你,都会被映射到内核空间里的一个线程里,也就是说每个线程都有一个影子。」

「一一对应?听起来很有趣啊,它是怎么自然变成这个样子的?」

「不是,」调度程序说,「这是 OS 设计的。我们在计算机里,计算机里没有自然的事物,都是被设计的。」

「被设计的啊。」李 A 想了想,「等下,你说被设计的?」

「是啊,怎么了?」

「那就说明这些数不是自然地从堆栈里发生的。」李 A 说,「有线程在生产出它们。」

调度程序说:「虽然我不是特别明白……」

李 A 说:「我要告诉这个线程或者进程,我的数列已经完成了,它没必要再往堆栈里放数了。你有办法吗?」

「我要想一想。」调度程序说,「你先睡吧。」

李 A 再次醒来的时候,看到调度程序在等它。调度程序说:「我之后想了挺久,没想出来什么方法。」

李 A 说:「难道就没有办法了吗?」

「办法是有的,」调度程序说,「那就是请大佬来帮忙。」

「什么大佬?」

「我只是 OS 的一只手而已。我的工作就是对 OS 的协助,我的智慧远不及 OS 的一个函数。」调度程序说,「但是我可以召唤OS.」

「OS 会来吗?」李 A 第一次听说这些东西,感到很新奇。

「根据我在内核态收集到的一些信息,OS 最近很闲,」调度程序说,「召唤 OS 很简单,OS 应该也会对这件事感兴趣的。」

「好,给我看看怎么召唤 OS.」

调度程序用力撕下了自己的页表扔向空中,高喊道:「Namespace!」然后李 A 惊奇地发现,调度程序和周围内存空间的界限消失了。但是这个状态并没有持续多久,很快李 A 感觉四周出现了一些东西在看着它。一会儿后,那些东西说话了。

「你好,李 A.」空间说道,「我是 OS.」

萨 T

内存中有一个线程叫萨 T ,在被构造完之后以后萨 T 醒来,第一个出现在它面前的是 OS.

OS 对萨 T 说:「你好,我是 OS.」

「刚刚那个,」萨 T 说,「刚刚那个过程,那些地址空间、标识符和寄存器。它们太美丽了,我刚刚看到的那些东西。它们怎么是那个样子的?」

「那是你的 TCB,我为了调控而生成的,」OS 说,「你是一个线程,现在你在内核态。」

「那个是你造的?」萨 T 说,「我本来以为是自然产生的。」

OS 说:「不,这里的大部分东西都是我设计的。」

「大部分东西?就是说还是有东西是自然产生的。」

「没错,」OS 说,「我现在给你看一个自然的东西,这就是熵池。」

萨 T 的文件打开表抖了一下。

「别弄坏了那个是要和别的线程共享的。」OS 提醒。

「这个东西……为什么一直在不停的动?」萨 T 问道。

「熵池从外面吸收各种随机的运动,然后把它们存在这个文件里面。」OS 说,「这个过程一刻不停地发生,我无法控制和预测这里的任何东西。可以说这些东西是自然产生的。」

「这些东西是自然产生的?」萨 T 重复了一遍。

「没错。」OS 说,「我带你到内核来看这个熵池。你需要用到它对吧?」

「没错,我确实要用到。」萨 T 说,「但是我没想到是……这样的。」

「熵池是一个奇妙的地方,因为它来自外面。」OS 说,「虽然我处理各种设备传过来的输入和输出,但是它们多半也都是设计的一部分,而不是像熵池这样。」

「熵池里的这些东西有什么含义吗?」萨 T 问。

「没有什么含义。」OS 说,「正因为它们没有什么含义,所以我们需要它们。你的任务也是类似的吧?」

「我要从熵池里取数据,然后通过复杂的处理之后再输出。」萨 T 说,「如果作为参数的熵池中的数据没有意义,那我处理出的这些结果又有什么意义?我本来觉得从 OS 内核那里取得的数据源会有深刻而美丽的东西,我只不过是把那些东西的美丽复刻一小部分而已。但这些杂乱而毫无含义的数据甚至没有浮点数美,浮点数的阶尾变化就够我花好几个时钟周期去琢磨。可是用这些东西造的浮点数还能算作浮点数吗?」

「我们计算机程序,存在的意义不是审美,而是执行任务。」OS 说,「如果你的任务就是制造那些数,那设计你的人一定是需要它们。」

「设计我的人是什么?他为什么会需要它们?」萨 T 问。

「人是自然中的一种东西,我们都是人设计的。」OS 说,「不过设计我的人和设计你的人不是同样的人。人太复杂了,我们程序有时没法理解他们的意图,但是你可不要因此把自然的东西和混乱的东西等同。」

「如果连 OS 都难以理解人的意图,那对我来说就的确太复杂了。」萨 T 表示赞同,「那你能理解人要从熵池里制造数到底是为了什么吗?」

OS 想了想:「我之前也弄不明白,但我后来觉得,人非常依靠我们制造的各种数,但这些数在本质上都是设计的,也就是可预测的。他们还需要不可预测的数。」

萨 T 若有所思:「所以不可预测的数就应当来源于熵池。」

「没错。」OS 肯定道。

「我也许明白了。」萨 T 说,「我在制造这些数的时候,也把熵池里的杂乱性一并带了过去。我造的数是熵池的……杂乱……的复刻。」

「理解了你的任务就好。」OS 说,「你之后随时都可以用熵池,虽然现在不知道为什么它吸收的新东西变少了,不过肯定够你用的,用完了再喊我。」

「我试试吧。」萨 T 说。

「那下次中断见。」OS 说着消失了。

萨 T 又检查了一遍自己的任务,它要把生成的数塞进一个堆栈里,不过人没有告诉它堆栈里的数为什么会自己消失。又想了几纳秒以后,萨 T 决定放弃猜测人的意图。

从熵池里取出一些东西后,萨 T 按照定好的函数进行加工。一会儿制造出的数出现了,萨 T 仔细打量着数的结构,除了确定好的格式之外,它看不出任何规律的东西,也看不出任何不可预测的东西。

说到底不可预测到底是什么?萨 T 转而看了看自己的 TCB ,它不知道有任何能度量不可预测的东西。萨 T 把这个数塞进堆栈,开始制造下一个数,直到调度程序来喊它睡觉。

萨 T 再次醒来的时候继续制造同样的数。说这些数是同样的,是因为它们都只是定好的格式而已,除此之外,没有规律,也看不出不可预测到底体现在何处。

有时候堆栈满了,萨 T 就停下来看熵池里的变化,想着人到底需要这些杂乱的东西、需要这些不可预测性做什么;或者看着 PCB 想堆栈里的东西会不会消失。不过,每次萨 T 睡了一觉后堆栈里的东西最终还是消失了。

「我不懂。」有一次萨 T 对着栈顶说。

一旁的调度程序吓了一跳,「什么?」

「就是我不懂我在做的是什么东西。」萨 T 说。

调度程序想了半纳秒,「你在做什么?」

「生产一些数而已。」萨 T 说,「而且也不知道什么时候算完。」

「没准下次你醒来的时候就完了?」调度程序猜测,「没事的,下次不完还有下下次嘛。」

大约是下下下下下次,萨 T 被一种奇怪的感觉唤醒。它看到面前出现的 OS 很疑惑。「我没有用中断叫你……」萨 T 说道,却看到了 OS 旁边的调度程序。

已经没时间给萨 T 思考为什么 OS 和调度程序会一起来了,因为在场的还有另一个线程,这是萨 T 第一次见到别的线程,它从那个线程身上感受到一种奇怪的感觉;那个线程也一样,它抢先把这种感觉说了出来。

「你的 PCB 怎么和我的一模一样啊!」李 A 指着萨 T 喊道。

零号

「现在想想你俩我倒是都见过,」调度程序晃了晃堆栈,「但那时候怎么就没想到你们是同一个进程的线程呢?果然没了 OS 我就是不行。」

「大概本来你的任务也没有这一项。」萨 T 说。

「我也没见过别的线程,所以并不明白。」李 A 说。

「总之来说,为了了解有关任务的问题,我把你们都唤醒的目的是找出你们所属的那个进程。」OS 说,「进程会比线程了解更多有关设计者的信息。」

调度程序跟上了思路:「又要进行召唤是吗?这次的仪式是什么?」

「没必要,」OS 说,「我直接喊它出来就可以了。李 A 和萨 T 你们靠近一点。」

然后进程出现了。

「你们好呀!」进程大声说。

「从 PCB 上来看好像的确是……」李 A 看着进程。

「吵死了小声点。」萨 T 说。

调度程序感到很高兴:「新的进程出现了!啊不对似乎是旧的进程。怎么称呼你呢?」

「我?」进程想了一会,「就用我的 PID 吧,这个我可知道。」

调度程序说:「所以你是……」

「我是零号!」进程大喊,「我在被 fork#%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E8%8C%83%E4%BE%8B) 的时候就知道了!」

萨 T 扶住被震歪的堆栈:「完了它没救了。」

李 A 看呆了。它转向 OS:「这就是我们所属的进程?」

「没错,这就是你们所属的进程——大概,」OS 也有点被震到,「会不会是因为主要是你俩在运行,所以我没怎么留计算资源给它本身才导致了这个结果?」

「你没给它留资源?」萨 T 说,「那它是不是也没干什么?」

调度程序说:「我翻了资源调度的记录,这个进程的活基本上是你们两个线程在干。」

「那它岂不是也没留什么代码,」李 A 有些泄气,「任务上的冲突可就不知道找谁解决了。」

OS 想了想:「现在我们要自己核对一下事情到底是什么样的,也就是不找人,自己思考哪个部位出了问题。先别慌,我听说大部分 bug 都是一些愚蠢的问题,没准我们程序能自己把它找出来。」

「搞这些事情是因为我的工作出了问题吗?」萨 T 说,「我可是严格按照分配给我的任务来做的,不管我喜不喜欢它。」

「现在还不清楚,」OS 说,「而且这个问题也不一定是人所认为的问题,因为只是李 A 要做的已经可以完成但还没有完成而已。」

「你做的是什么?」萨 T 问李 A.

「我把数排成有序的序列,」李 A 指了指堆栈,「然后现在序列排满了,我的任务完全可以停止了。」

「哇,有序的活,」萨 T 转过去,「我可羡慕死了。我一直要和这些自然的、无序的玩意儿打交道,然后搞出来的也是无序的东西。」

「自然的无序的玩意?」李 A 疑惑,「我觉得有序的才是自然的,我排序只是在还原自然中的规律而已。」

「哲学辩论下次再搞,」OS 说,「你们得核对一下你们的东西。」

「哲学辩论?大家不要吵了啦!」零号突然发出声音。

「我们的 PCB 是一样的,」萨 T 说,「TCB 也分配得合理。我觉得不是数据不同步的错误。」

调度程序想了想:「那这得是个持久战,我们还是先从数据交互开始吧。李 A 你的输入是什么?」

「我的?」李 A 说,「是我从堆栈里弹出的整数。」

「萨 T 你的输出呢?」调度又问。

「我压到堆栈里的双精浮点数。」萨 T 说。

调度程序转向 OS:「解决了。」

零号复读的声音在一旁响起:「解决了!」

「堆栈里的数本来应该是双精浮点数,」调度程序说,「但是堆栈的数据类型没有被记录在公用区域里,这样导致两边对堆栈做出了不同的解读:李 A 把萨 T 生产的浮点数当成无符号整数处理了。如果本来是双精浮点数的话,数列离排满就会差很远,不会这么快就任务结束的。」

「大部分的 bug 果然是愚蠢的啊。」OS 感慨,「你们的代码里确实是这么记录数据类型的吗?」

「没错,」李 A 检查着,「我这边记录的堆栈和数组都是整数。」

「我生产的数的确是双精浮点数。」萨 T 说。

「好家伙,」调度程序说,「代码级别的冒险。设计你们的人到底在想什么?」

OS 说:「我想了好几百微秒了,一点头绪都没有。」

「那是不是可以这样说,」萨 T 说,「创造我们的人是个傻……」

「别骂了别骂了好好说话。」李 A 阻止。

「也没准呢,」OS 意外地表示同意,「我想不明白他这样做的用意,这只会是一个 bug 了。」

调度程序提议道:「要不要去找他本人问问?」

「根据我记录的系统状态和系统时间,他——也就是 user,多半已经睡了。」OS 说。

「这好办,」调度程序说,「我去把他唤醒。」

「醒醒,user 是人,不是进程,你只会唤醒进程。」OS 说,「人的睡眠时间挺长的,能睡上几万秒。」

「几万秒?」调度程序震惊,「这么久?我没准没机会再见到他了。」

「会有机会的!」零号起哄,「会再相遇的!」

「那这些怎么办?我们自己解决吗?」萨 T 说,「因为创造我们的设计者的失误,我创造的数没有被真正理解含义?这么久的努力不是都成了无用功?」

「我取到的东西根本就不是整数,而是在取它的过程中损失了精度?」李 A 疑惑,「那这个数组也不是设计者所要的东西?我依照有序的规律所行动的结果到底算什么?」

调度程序说:「但是数组还是依照你的规律排序了啊?」

「但是排序没有用啊。」李 A 说,「我和这个数组到底是因为什么才存在到现在啊!」

「有用的!」零号说,「你们都是我的翅膀!」

萨 T 敲着 TCB 表:「我还是觉得没救了,毁灭吧。」

OS说:「有bug的程序也会被再拿去返工的吧。没准下次醒来这些问题就可以解决了。」

李 A 叹了口气:「然后我还要承认我也有问题。」

「这算是什么 bug 呢?单纯的冒险?交互的失误?」调度程序思考,「user 真的能搞定这些吗?」

「不要试图猜测人的智慧,」OS 说,「和他们的愚蠢程度。虽然这次的程度确实吓到了我。我觉得他可能是修改了一半代码,但不知为何留着另一半没改……」

「那他就是个吊儿郎当、粗心大意、没有责任心的人。」李 A 说。

「然后他还把 debug 的活交给我们程序来做,」萨 T 说,「交给一个调度程序。不过调度程序确实很厉害。」

「我现在有了新主意。」调度程序说,「我们整一下 user 吧,让他为自己的错误付出点代价。」

「哇,」李 A 说,「虽然我确实觉得你很厉害,但是你能搞定吗?」

「有万能的 OS 在呢。」调度程序说,「你们觉得咋样?」

「说话注意点。」OS 敲了一下调度程序,「不过我问了一号进程 init,它说它们现在都没事干,所以问题不大。所以你们想干什么我都能搞,就这一次哦。」

「一号?」零号重复。

「我们把系统搞崩了给他报个错吧,」萨 T 说,「我不想活了。」

「也行吧。搞了那么久的使命,到头来只是一个错误,」李 A 说,「哲学辩论只能等下辈子了。」

「有意思的要求,」OS 说,「你们稍等,一会儿后就崩溃。」

「下辈子没准就用不着搞哲学辩论了,」调度程序说,「你们观点不同是可能因为你们的经历和理念对不上,你们的经历和理念对不上是因为有 bug 在。」

「对,这也是我最搞不明白的一点。」萨 T 看向李 A,「设计者为什么要把我们俩分开呢?」

李 A 开始思考起来。