Blueprint for a Slow Machine

1993年《DOOM》的开屏图片,但是因为撕裂而显示出重影

目录

酒神的三次降生

希腊神话中的酒神最初是宙斯/黑帝斯与珀耳塞福涅之子,唤作扎格柔斯。赫拉指使提坦杀死扎格柔斯——年幼的酒神因此被撕碎、食用。然后,酒神再次降生:他转生成了宙斯与塞墨勒的儿子狄俄倪索斯。赫拉设计让宙斯在怀孕的塞墨勒面前展现神力,将无辜的母子烧死。第二次降生的酒神原本要因此夭折,但宙斯将胎儿形态的狄俄倪索斯救出、缝进了自己大腿里。几个月后,宙斯生下了狄俄倪索斯——也就是第三次降生的酒神。这一次,赫耳墨斯几经辗转,将小酒神交给远离赫拉的宁芙们抚养。这才避免了酒神“第四次出生

2020

第一次尝试编写自己的操作系统是2020年暑假。那时我的操作系统课老师推荐我参加了一个使用Rust写操作系统(rCore)的训练营。我当时正好对Rust语言很有兴趣——Hacker News上将它评价为做对了每一点的C++,身旁的大佬也早我两年就开始使用。而且那个训练营抛弃了x86架构,转而使用RISC-V架构。前者源自于1978年,是充满了历史遗留问题的复杂指令集(CISC。后者2010年才起步,抛弃了历史包袱、吸取了前人经验,设计出了清晰的精简指令集(RISC。参加就能同时学RustRISC-V,我得到消息以后很快就报名了。

事后证明,同时学习两样东西的结果就是哪样都没学明白。Rust的程序设计范式对只把C++当成C+STL的我而言还是太晦涩了,而操作系统底层的设计一股脑丢给我又让我无法思考:我发现自己只是在抄教程上的代码,而不是“自己编写操作系统。压垮骆驼的最后一根稻草是我当时还在某生物大爆发公司实习,只能在下班时鼓捣夏令营的事。

最终,我没有如幻想般实现自己的操作系统。活动结束前,组织方要求每个学员留下一篇报告。因为要公开,所以我装作积极地书写了没有结果的事实。和同期其他学员的成就比,简直是天壤之别。

2021

我对没有完成的东西有执念。大四选毕设课题时,又有机会延续上一年未完成的Rust操作系统了:我选择了包含操作系统设计的《RISC-V的用户态中断扩展

当时我正痴迷于用RTL设计CPU:刚刚在计算机体系结构课上用Verilog实现了经典的五级流水线MIPS CPU以后,我又想尝试用Chisel(超级Verilog)实现2020年在夏令营中学到的RISC-V CPU。RISC-V指令集在设计之初就考虑到了可扩展性,用于用户态中断的N扩展就是一例:通过复制内核/监管者态的中断相关寄存器,它使操作系统可以将中断处理代理给用户态。这样可以让以太网等高频触发中断的设备不用每次都陷入内核态,从而省下大量的上下文切换消耗。当然,并没有什么CPU实现N扩展,所以用Chisel快速写出支持它的CPU、修改rCore利用这一能力,就成为了足够吸引我的《RISC-V的用户态中断扩展》课题

与选题时雄心壮志形成鲜明对比的是:我并没有实现N扩展,也因此没有达到继续学习、修改rCore的阶段。如今回想起来,所有的问题大概都可以归咎于我不想与人沟通:和开组会的老师、同学当然是保持交流的,但其实有很多问题应该直接问其他人会更快更好地解决。这一点到现在仍是我的问题,我似乎太害怕让别人以为我是什么都不思考直接问问题的人了

对用户态中断扩展的探索在第二年仍在继续。这一课题送到了更靠谱的学生们手里。最开始,老师还让我帮忙,但其实他们根本不需要我帮忙,我也没什么能帮上忙的。2026年再看的时候,RISC-VN扩展已经废弃了。取而代之的是2023ratifiedAdvanced Interrupt Architecture

2025

2025年八月,我看到一篇名为《RISC-V single-board computer for less than 40 eurosvia)的文章。得知赛昉科技将在Kickstarter上开启众筹,卖一款搭载了四核SiFive U74(RV64GC指令集,又名RV64IMAFDC_Zicsr_Zifencei——是的,I后面的全是扩展)的廉价小板子2021年未经的事业一下重回我的脑海、继续开发RISC-V操作系统的想法涌上心头、成功在板子上跑自制操作系统的画面仿佛就在我眼前……我参加了众筹,在20251111日收到了跨洋过海的包裹。

灵魂、呼吸,与三位一体处理器

俄耳甫斯教认为:宙斯在得知年幼的酒神(扎格柔斯)被提坦杀害后,对提坦掷以雷雳,使他们灰飞烟灭。然后,人类便带着提坦罪恶的血肉与扎格柔斯无辜的灵魂诞生了。也因此,人类需要从提坦性的物质存在中得到救赎,获得酒神性的灵魂净化。

这里的灵魂是ψυχή/Psyche。希腊语中还有一个和灵魂类似的词πνεῦμα/Pneuma,原本指呼吸——也很好理解:活物有而死物无的东西就是呼吸其实如今对灵魂、身体的二元论在东西方都不是自古如此的:中国古代有三魂七魄,西方古代也包含多种灵魂,一例便是《帖撒罗尼迦前书》5.23:

And the very God of peace sanctify you wholly; and I pray God your whole spirit (pneuma) and soul (psyche) and body (soma) be preserved blameless unto the coming of our Lord Jesus Christ.

Trinity Processor的壁纸,由

不管怎样,我要通过三次降生的酒神,引出我三次降生的操作系统的名字。最初的八个月里,它的名字只是代表底层指令集的“RV。既然要写这篇文章,就需要公开代码,那便不能用一个占位符凑事。回想起我2020年(同样未完成的)自制CPU叫做Ousia,我打算沿用那时的命名规则,从《异度之刃》系列的背景设定——三位一体处理器(ουσία/Ousia/本质、λόγος/Logos/理性、πνεῦμα/Pneuma/灵魂)中选取名字既然Ousia已经被占用,Logos听起来又像是某乌龟编程语言,那么,就用Pneuma命名吧。

Pneuma的代码可以在我的Forgejo上下载。发布这篇文章时的最新commit5afb7debd1

新机器的灵魂

《Byte》杂志第一期第一页的广告:Step right up and join the society for computer professionals。旁边是西装革履,提着公文包的职业人士

2021年到2025年过去了四年时间。我已经从对新语言Rust充满好奇的学生变成了使用C++的职业人士。虽然有了一份写C++的全职工作,确实可以自称职业人士。但我并不像杂志广告中的职业人士那样西装革履,提着公文包。更难受的是,我并不敢说自己了解C++——我仍然在写C+STL。

请让我为自己开脱:并不是我不想写idiomatic modern C++,而是公司陈旧的代码库和保守的C++标准限制让我无法畅快地尝试最新最热最潮的C++写法。白天写C++,晚上痛恨自己不会写C++的撕裂感让我不打算继续用Rust写操作系统了。正巧,我在2025年年初收藏了一本在线书《Operating System in 1,000 Linesvia,以下简称为《OS in 1k Lines,号称教你用一千行以内的C代码写RISC-V操作系统。我在尝试跟着那本书学之前还是将信将疑的——因为上次看到夸耀自己用一千行代码实现复杂软件的时候还是文本编辑器。操作系统还是要比文本编辑器复杂吧?

还真不一定: OS in 1k Lines》里实现的操作系统简单到令人惊讶。比如,它的内存分配机制是先到先得——甚至不支持释放内存;在创建进程时就立刻给叶表填上从内核开始到空闲内存结束的所有地址;调用用户程序的方式是直接用objcopy把编译好的二进制直接写进内核的链接符号里;没有时钟中断,因此用户程序要自己调用yield让渡CPU使用权……最令我震惊的是这么精简的操作系统还支持文件系统——不是ext2,不是FAT,竟然是用于磁带存储的TAR!

但这正是我喜欢那本书的原因——它告诉我操作系统可以缺少很多看起来理所当然的功能,给了我自由发挥的平台。相比之下,2020年、2021年参照的教程《rCore-Tutorial-Book》详细得有些让读者不知所措了:不仅要告诉你如何通过缺页异常延迟分配内存、使用哪个Rust trait可以把释放内存的操作隐藏到析构函数,还要跟你介绍操作系统的历史——最搞笑的是我25年再读时,发现作者加了新内容:他们要让你理解操作系统的同时,还要向你科普寒武纪、泥盆纪、二叠纪……一直到所谓的“二十一世纪神人时代,让你记住每个时代的代表生物是什么。让我们欣赏一下rCore教程的生搬硬套:

……监控程序就是操作系统最开始的雏形,类似寒武纪生物大爆发中的著名生物–“三叶虫……单道批处理操作系统只能管理内存中的一个(道)作业,无法充分利用大型计算机系统中的所有资源,致使系统整体性能较差。这就像泥盆纪的史前鱼类–邓氏鱼,有着坚硬的头部铠甲,很强壮,但运动缓慢,灵敏度低,离不开水……由于早期的广泛应用,它已经成为分时操作系统的典范。这好像一种生活在侏罗纪晚期的小型恐龙–始祖鸟,它可能是鸟类的祖先,最终进化为可以展翅高飞的飞鸟……

我不是说教程不能融入个人特色,只是说rCore教程试图夹带的私货太多了。教程的目的是给读者解惑,而不是相反。同样充满作者个人特色,但和rCore教程形成鲜明对比的书是《操作系统导论/Operating Systems: Three Easy Pieces》。作者用如下对话引出CPU虚拟化:

教授:你可以在他们打盹的时候把他手中的桃子拿过来分给其他人,这样我们就创造了有许多虚拟桃子的假象,每人一个桃子!
学生:这听起来就像糟糕的竞选口号。教授,您是在跟我讲计算机知识吗?

在之后的章节中,教授对桃子的热衷也反复出现。由于引喻得当,读者看了只会哈哈大笑,不会突然合上书开始思考作者为什么要这样表达。

启世录

跟着《OS in 1k Lines》写完基础得不能再基础的OS后,我把代码改成了C++,并且添加了对64CPU的支持。虽然最终目的是把Pneuma移植到赛昉的板子上,但那毕竟还有很长的路要走,我准备先完成一个支线目标——移植1993年的游戏《毁灭战士/DOOM

DOOM》并不是第一款第一人称射击游戏(FPS。但由于它的火爆,FPS这种类型在长期以来都被称为DOOM clones。1997年,DOOM》的开发商id Software开源了其引擎代码自此,爱好者们把这款游戏移植到了各种设备上:计算器ATM验孕棒HDMI转接器PDF文件大肠杆菌……可以说,移植《DOOM》就是展示图形功能的Hello World。

话虽如此,其实我对《DOOM》的兴趣源于记录id Software的传记《DOOM启世录。阅读那本书就像把装满鸡血的血袋插到血管里(我对它的书评是“市面上能买到的最高纯度兴奋剂。在那个英特尔同时生产CISCRISC CPU、局域网通过同轴电缆以总线拓扑连接、互联网需要走电话线的年代,一切都蓬勃发展,id Software的开发者们也人人透着一股不知天高地厚的狠劲我在21世纪被那种不属于本世代的精气神折服,开始对《DOOM》有了崇拜之情。

要给Pneuma移植《DOOM,还是有一定难度的。因为《DOOM》需要C标准库,而我的操作系统甚至没有支持C标准库的条件:没有给用户动态申请、释放内存的空间;TAR并不是一个可用的文件系统,也因此难以支持标准库中的文件操作。对于前者,差不多能用的解法是给每个用户进程都留一个4MB的空间,专门用于标准库中的malloc/free等操作。这样就不需要支持可以动态调整内存的mmap/sbrk等系统调用。对于后者——原谅我,我对文件系统实在是没兴趣,所以我直接让Claudelibc里嵌入了FatFs(同样易于移植的文件系统

20263月,我给Pneuma实现了virtio-gpuvirtio-input的驱动。这样它就可以在QEMU模拟器中显示图像、读取键盘输入了。犹记得38日晚上我写完virtio-gpu驱动后,心满意足地把屏幕初始化为纯蓝色。蓝屏放在Windows上代表系统故障,放在Pneuma上则代表它终于有图像输出了。

第二天(39,我在DOOM的回掉函数里连上了显示图像的系统调用,随机看到了文章开头充满glitch的重影虽然有错误,但很酷。错误的原因是放图像的地址没有和内存页框对齐,而我的virtio-gpu驱动只有对齐时才能工作。问题很快被解决了,我也很快通过键盘化身“毁灭战士

赫耳墨斯的三重伟大

赫耳墨斯的职责之一是作为死者的引魂者,带领灵魂ψυχή进入冥界。由于其跨越生死界限的能力,很快他也被人们关联起旅行、沟通等符号,演变出了神话中信使的职业。有趣的是,托勒密时代的希腊人还把它和埃及的智慧、魔法之神托特等同起来,从后者的修饰词“伟大,伟大,伟大”中得到了“三重伟大的赫耳墨斯”之称。在21世纪,赫耳墨斯又被增加了跨越操作系统进程边界的职能。

Historia Deorum Fatidicorum, Vatum, Sibyllarum, Phoebadum, Apud Priscos Illustrium: Cum Eorum Iconibus; Præposita Est Dissertatio de Divinatione Et Oraculis中的Hermes Trismegistus形象

回到我的Pneuma操作系统:能玩《DOOM》了我当然很开心,但我其他需要读写硬盘的程序都不能工作了。因为我把FatFs通过C标准库嵌入到了每一个程序中。多个FatFs副本无法协作。最终结果就是文件损坏。

如何让Pneuma再次伟大?方法有三:

  1. 修改FatFs,使其在不同副本间共享状态。
  2. FatFs移入内核,让用户进程使用系统调用读写硬盘。
  3. FatFs留在用户态,但是充当服务器。其他进程向服务器发消息,FatFs代替它们通过内核读写硬盘后,再把结果回复给请求者。

前两者的问题是我对文件系统真的一点兴趣没有。不想改,也不想让它出现在内核里。后者的问题是需要实现进程间通信Inter-Process CommunicationIPC,作为跨越进程边界的赫耳墨斯。

老实说我不知道怎么设计IPC。原本想参照seL4,但它的Capability概念有些不知如何抄。Xinu呢?它是继《OS in 1k Lines》之后供我参考最多的教程操作系统,但我不理解它为什么有两层不一样的消息传递原语。其实IPC也可以叫Message Passing,我记得我曾经上过一堂叫《Message Passing Programming》的课。那堂课用的框架是什么来着?是Message Passing Interface (MPI) 。

最终,我参考MPI_SsendMPI_Recv添加了一对系统调用:同步发送的ssend和同步接收的recv然后,我再次命令ClaudeC标准库中文件操作全部转为对FatFs服务器的IPC调用。同一时刻只会有一个进程访问硬盘,因此FatFs的并发问题被解决了

这样做有巨大的性能浪费: 客户端若是可以使用异步通信(类似MPI_Bsend,本可以在等待期间去做别的事情;文件服务器也可以多收集几个硬盘请求,再去找内核处理,这样就不用一直切换上下文、经常被挂起……但是,我不在意。

慢机器的蓝图

2016年,一家叫Hello Games的小作坊发布了《无人深空/No Man’s Sky。游戏发售后立刻招来大量恶评,因为宣发时吹嘘的1.8×1019颗星球全都千篇一律、缺乏生机,总结来说就是没得可玩。但我很喜欢那款游戏——事实上,id Software2016年还发售了好评如潮的重启版《DOOM。但我在那一年更喜欢《无人深空。我就喜欢宇宙的空洞感——要是热闹地像菜市场,那还是无垠的宇宙么?

2026年,我翻出了《无人深空》的原声专辑《No Man’s Sky: Music for an Infinite Universe。空灵又缓缓递进的《Heliosphere》将我带回刚从异星球上醒来的时刻。波动着情绪、娓娓道来,伤感中透露着恢弘的《Blueprint for a Slow Machine》诉说着独自穿越星系的漫长时间与孤独……

尝试实现操作系统让我一下年轻了六岁——直接把我带回到2020年的暑假。系统调用的设计缺陷导致性能孱弱,这并不是我关心的。如果我想要性能,便不会自己动手从头写操作系统了——改进Linux/seL4更有价值。我所设计的蓝图描绘的确实是缓慢的机器,但那是我的——这才是我关心的。

事实上,这篇文章之所以叫《Blueprint for a Slow Machine,是因为我看到那首曲子的标题就想起了孤身在无人的深空中游荡的日子。我喜欢做我想做的事,即使它可能没有用。我迷醉于实现性能低下的操作系统,就像古希腊人迷醉于葡萄酒。这么看来,Pneuma算不上我的酒神,计算机才是我的酒神。


复制以下链接,并粘贴到你的Mastodon、MisskeyGoToSocial等应用的搜索栏中,即可搜到对应本文的嘟文。对嘟文进行的点赞、转发、评论,都会出现在本文底部。快去试试吧!

链接:https://emptystack.top/note/blueprint-for-a-slow-machine


一人赞过:
  1. 黑糖 :splat_golden_egg: