Aug 27, 2026
课余时在做什么?有没有空?可以来参加内核赛吗?
大概就是说,本学期,出于种种原因——一厢情愿也好、环境压力也好——我参加了某个叫CSCC的比赛。
嗯,具体而言,应该称之为「2026年全国大学生计算机系统能力大赛-操作系统设计赛(全国)-OS内核实现赛道」。
真是不太好记的名字。又臭又长。
无疑,就是这家伙——夺走了我惬意的日常生活。
我讨厌你。无论是技术力低下的赛方,还是完全没有责任心的赛方,抑或是设计出几乎等价于Linux Cosplay、罔顾真正重要的系统设计能力的赛题的赛方。
上述所谓三个「赛方」,的确都是指一位。像这样遣词造句不大自然吧。但我是刻意的。
——换句话说,我只是想要用力表达我对赛方的不满。
不过怨言姑且抛开。没办法。我还是打算好好回顾这次比赛。
「憧憬是距离理解最遥远的距离」
大二初。我了解到了“内核赛”的存在。
彼时,我刚自己写完一个简单的内核,以及一些七七八八的系统软件,比如编译器,比如异步运行时。也做过CPU之类的东西。
同时也是有在写另外一个微内核。
简单讲:我正和所谓「Systems Programming」处于蜜月期。我很喜欢理解计算机底层原理的感觉。
所以我很自然会想要参加内核赛。
开什么玩笑。给你七个月时间,让大家开发建设自己的内核,然后通过各种测例和现实负载来检测系统设计的能力。太酷了吧。
——我得说,我是这样想的。在那个时候。
以及,我对我很自信(严肃脸)。
因为我了解到,本校往届参赛队伍基本都是备赛期才开始学习rCore甚至Rust的。
虽然我没怎么研究过rCore。但是我对Rust相当熟练。同时我也具备C的相当的经验。以及一些工程上的东西,比如构建系统。
同时正如一开始所说,我不是早就写过两个内核了吗?这可是从零写哦,不是修修补补别人的东西。
而且一个是宏内核,一个是微内核。虽然都比较浅显,这样一来我不就是双修,没有短板了吗?(严肃脸*2)
而且我一直对我的代码品味比较自恋。我掌握不少奇奇怪怪的代码小技巧,能写出我自认为很漂亮的小玩意。
“嗯嗯,这样下去的话,不是几乎赢定了吗?”像这样狂妄的话,我也不是不会偶尔自言自语(咳咳)。
不过,最后事实证明,我的两个想法都落空了。
- 这个比赛,CSCC,一点也不酷。相反,它迫使我们写出丑陋的代码,以此换取比赛竞争力。
- 「几乎赢定」什么的,完全没有!倒不如说,差点初赛都没能通过,狼狈地一败涂地……
「ヰ世界電子開發部」,参战。
十二月末是本校进行比赛招新的时候。大概流程貌似就是大家自我介绍然后互相匹配的样子。
但是按照我对自己的了解,如果想要靠那次招新来找到我的队友,是肯定会颗粒无收的。
我不想接受冒险者小队都无法组建的悲惨事实。
所以我想要尽可能提前找到队友,这样不至于最后寄希望于招新会。
然后我想起来,在本校的开源技术协会群聊中,我有看到过一个大一的新生,稍微提到过一些他有在学习内核开发的事情。
那家伙虽然是大一,但是就有在研究这样的东西。技术力且不论,但想必很具备主观能动性。(什)(不要用这种俯视视角大到爆的傲慢说法啊)(原谅我的贫瘠语言组织能力)。
不过,要说更关键的,也就是最能回答「喂别人明明只是说过几句话你就记下来了未免也太恐怖了吧」「就算他提过几句内核你就决定了吗不要这么草率」之类的问题的答案,其实很随便。
——大概是因为他的头像是凉前辈吧。嗯,那个传说中的,活跃于下北泽的、自称摇滚乐队的「結束バンド」的,贝斯手,山田凉。
「这家伙,竟然用这种头像……怎么看也很靠谱啊。」想着这样奇怪的东西,以及受到比赛压力的驱使,我甚至能战胜自己——哪怕明明没说过话,也能够直接冒犯地发起QQ聊天然后邀请!……什么的。
啊啊,扯远了。总之,我最后收获了一个新队员。后来事实证明这家伙确实帮了大忙。
几天后,一位同学有在招新会上加入了队伍,但此后出于一些原因,他退出了我们的队伍。
也就是说,抛开中途的波折,这位也就是本次比赛我正式的、唯一的队友了。
最后,出于我的私心,我将队伍命名为「ヰ世界電子開發部」。内核的名字则是Anemone。
这就是,故事的开始……(语气突然深沉)
我独自开发
一月末,我们开始建设内核。
说是「我们」,实际上最初只有我一个人;队友对内核的了解尚不足够立即进入开发,他正处于自学的阶段。
要说我干了什么,其实也基本就是确定了仓库的骨架,定好了内核的一些基本的大框架,完成了从RISC-V的裸机自举,引入了基本的构建系统,配置好了CI和开发容器以方便队友,之类的铺路性工作。
关于内核,也许值得一提的架构决策:1. 相对完备的设备驱动模型,定义了设备、驱动,总线三大核心对象,同时允许真正的平台自探测;2. 组合式的、遵循依赖反转原则的(由上层子系统定义架构接口)的架构抽象层;3. 与构建系统配合的自动化配置文件读写、代码生成能力。目前只能想起这些。
还有就是一些比较技术细节的、不太重要的东西:比如往届内核普遍依赖的是rcore-os开发的这个buddy分配器,但这东西内部反向依赖堆分配,会造成依赖环,因此我做了一个基于侵入式结构的buddy分配器打破了这个依赖;又比如写了一个将dtb解展平到kernel提供好的arena内的crate,同样不依赖堆分配。
这些工作大部分比较底层,但是又不太简单。我的打算是由我直接将整个项目的架构组织和内核的大框架以及核心代码确定,不应该在后续开发时随意生长;我也不大放心交给队友。
顺带一提。我也没有参考任何的往届内核。正如此前所放下的话:我有一点点自信。我感觉大部分往届内核项目不论是在内核内部的架构设计上还是整个项目的源码及外围设施的组织上都不太优雅。如果你不同意,请不要留情批评我的自大(呜呜呜)。总之,我是从头自己设计的。
说起来这实际上是我第三次写内核了。不过尽管吸取了此前很多经验,我仍然保持着相对缓慢的速度。因为最初的代码精度要求会比较高,而我不希望留下历史包袱。
这个时期持续到三月初。设定上我们此时开学。我的队友在这个时期内自己有在写自己的内核。往后,就也加入了正式开发。
本校本届四支队伍参加内核赛。这段时间,大概也就是唯一的、我们队伍的开发进度比较领先的时期了,真是可喜可贺。
再往后,一直到比赛结束,我们就很不幸地、始终占据着「无论怎么看都毋庸置疑」的吊车尾的位置了,真是令人悲伤。
Only My 「Artisanal Programming」Can Shoot It
我和我的大一队友都是比较传统派的家伙。至少在最初。
基本上,我们我们都对自己的开发能力比较自信。同时,我们——或者说至少我——这样认为:哈,你说AI能写内核?怎么可能。这可是内核开发,某种意义上可是传统软件开发的一个高峰欸。我才不信你说的话呢。
所以一直持续到初赛结束前一个月,我们都保持着匠人的手作精神。LLM只用来讨论一些技术决策之类的东西。
我的队友最初负责LoongArch架构的适配,以及进程管理、调度和用户态内存管理,而我负责除此之外的所有内容。
按道理讲,我姑且算是队长,是不该放手将上述三个领域交给队友的。这些东西是内核的核心对吧。但是我还是这么做了,因为我的想法是:我相对更熟这些子系统,所以我能审代码;而我对文件系统,设备驱动模型等领域的掌握度不如前者,因此这些东西我不能让别人做,否则我很难审阅;必须我自己做。
到大约四月中,我们跑完了大部分basic测例。这是一系列相当简单基本的syscall测试。此时我们的进度已经略现颓势。有的队伍已经完成busybox测试了。
中途,队友将mmap测试交给了我。因为这东西比较复杂,所以让我来做。
然后我就发现了问题。简言之就是,现有的用户内存管理架构不合适。无法自然地扩展以支持mmap的能力。换句话说,它的表达力不足。要想坚持现有的设计,就只能接受工程上的缝缝补补——无非一堆特判。
我不能接受这个选择。所以我将用户内存管理从队友那里接手过来,做了重构。然后支持了mmap族syscall。
此外,当我需要实现更全面的clone,以支持用户态多线程时,我又发现当前调度子系统和进程管理子系统同样存在表达力不足的缺陷。无需多言的判断是:眼下处于内核开发早期,我们不应吝啬重构。
于是我又把这些东西接手过来了,完成了系统上的重构。这样,整个架构就能自然支持线程组这一语义。
这段时间内,我的队友主要负责PCIe总线及驱动。此前我已经完成过Platform总线和VirtIO总线,但都与PCIe不同。后者是物理总线,协议上也相当复杂(真的真的很复杂),牵涉相当多技术细节。
简单概括这段时间的开发的话,大概就是:系统架构和外围工程设施(比如构建系统)随着我们的开发稳步推进,架构的扩展基本都是自然的,没有引入工程上的hack。唯一的缺陷就是——我们的进展太慢。
如果不是比赛的压力越来越大,我想我们可能就会一直这样走下去了。也就是说:「手写一辈子代码」什么的。()
「这虽然是 Vibe Coding,但可不是闹着玩的。」
五月末,我们向硅基智能投降了。也许是六月初,我记不清。
要问为什么我们会愿意放弃坚持至此的“匠人精神”呢,答案比较简单:我们的进度太慢了,至此甚至没有开始LTP测例。
五月中旬大概是比赛报名的时间。排行榜一开,很多队伍立刻拿下了相当高的分。最初有不少一两万的。而我们经过乐观计算,最终发现自己最多才几百分(擦汗)。
索性,拿到勉强体面一点的分数之前,干脆就不提交了(说什么呢这家伙)。
初赛最初那段时间,LTP测例的分数还没有进行对数压缩和有界裁剪,因此很多队伍的分数相当夸张。这给我带来了极大的心理压力。
我最开始不是很理解为什么大家做得这么快。我知道我对代码质量存在比较高的要求,但是为什么会差这么多呢。明明我们也有在每天努力写代码,什么的。
总之,很快就发现答案是AI了。大家原来都在很夸张地vibe coding啊。
那时候我们犹豫了一阵子,没有立即开始vibe。我们主要顾虑的是,这东西短期内能产生大量进展,但是它是否会破坏系统的可维护性,是否会破坏架构的通用性,是否会带来不自然的设计或实现,导致代码扭曲,诸如此类的问题。
此外,我之前有过一些不成系统的vibe经验,给我带来的感受很恶劣。
我认为AI写出来的代码相当丑陋,各种方面不符合我的品味。
简直是在污染我的作品。
当然,我也有感觉这是我自己的问题。我对agentic coding的了解很浅显。
总之事已至此。为了通过比赛,我们别无选择的样子。
迄今已经接近50k loc,但是功能覆盖面却相当狭窄。我们花了大量时间在架构打磨上。「照这样子下去,没准初赛都无法通过啊」——想到这一点,一定任谁也按捺不住吧。
于是将迄今的内核项目打上Tag,名字叫做Ashes Of Utopia,以此祭奠死去的纯洁。
现在想来或许有点矫情。不过当时我可是失落了很一阵子呢。
最后,我们很快开始使用AI。
这个过程是渐进性的。最初我小量使用,代码还是仔细审;之后普通使用,开始让AI做一些比较独立的功能,代码不那么细审;最后大量使用,甚至直接让AI负责某个独立的子系统,代码也就看个大概的形态和源码组织。过程中,workflow也在不断收到反馈,并根据信号改进。
从此,直到决赛结束,我形成了一套比较体系化的工作流。我想我最后两个月手写的代码量不超过两千行的样子。
简单来讲就是将工作分为三类,按照语义划分,粒度由低到高:
- Patch。比如说实现某个不要求基础设施 or 系统框架扩展的syscall,又或者修复某个完全处于确定Owner内的bug。
- 小迭代。引入/修改系统不变量,但是,全程只局限于一个Owner内部。
- RFC。工作流程上比较繁琐。但总之就是同样会引入/修改系统不变量,而且可能横跨多个Owner。
我不该提到Owner这种细节。它是我们的工作流中具备特殊含义的一个概念,但是为了精确表述还是这么说了。基本上,它是指第一方拥有某些数据/控制的存在。大到某个子系统,小到某个具体的数据结构。不过我想这东西大概不影响我的表意。
RFC这东西有不少值得深入提及的内容。我在这东西上的设计做了不少取舍。在决赛中,我通过几份RFC,完整实现了整个网络子系统(尽管这个人一点也不懂计算机网络)。
简单概述一下就是,先讨论出共识文档,可以不成体系,比较碎片化之类的,基本上是能力边界,验收标准等内容。然后再撰写RFC正文,比如不变量文档。最后审阅后接受为R0,再撰写实现文档。
关于实现文档,我做过几次大改。最初是直接给出整条路线各个阶段的具体内容,要求精确的write set;后来改为write set滚动解析,只要求给出当前正在实现的write set;最后将整个实现文档改为滚动解析,同时不再要求write set,而是implementation boundary,允许实现范畴内的自然延伸;最初,我们只给出各个阶段的outline,每次实施一个阶段时,首先解析本阶段的具体路线,这称为Stage Resolution。这些改动的核心很清晰:工程是不确定的,我们必须保留松弛的空间,不能提前定死路线。
以及与上述工作流配套的一系列基础设施,比如skill,transaction log,标准的工程原则,等等。
有一点比较重要的,大概就是要有基础设施和上层应用的区分意识。要分清内核内部哪些东西应该让基础设施提供,哪些东西应该让上层建筑自己负责。比如很多同届/往届内核基础设施相当薄,很多能力完全堆在syscall层强行实现,这是相当差劲的设计。具体如何区分呢,还是看自己的架构经验。或者就去参考其他内核项目吧,比如Linux,Zircon,等。
另外就是,给agent接好线。至少在本文写就前,2026年内核赛这段时间内的LLM能力来看,我们需要做的工作还有很多。比如说持续维护的skill,便于agent使用的构建系统CLI,端到端的测试/运行脚本,扩展性良好、方便自定义的构建系统、为TDD准备的一系列设施,等等。决赛那段时间里,我的工作流已经完全自动化了。
嗯,具体的技术细节就不在此展开了。关于工作流,我想有很多可以说的。但是本文并非技术分享,就姑且不再深入了。
总之,AI给我们带来的生产力提升是相当大的。我们很快就开始了LTP测试,并实现了一大堆功能。
决赛阶段,因为用了AI,我们进度追赶地相当快,至少在功能这一方面。
整个决赛这段时间,我的队友一直在负责所有的物理板适配,也包括各种驱动和设备子系统。一直到比赛结束,我都完全不知道如何在物理板上跑通。所以说真是多亏了这家伙呢。我的识人术真不赖啊。(自豪)
最后我认为我们的功能应该是领先的。不论是通过现场赛直接跑通的keydb来看,还是通过我们在本地跑通的一大票程序。比如OpenJDK25 + MC Server 26.2——对,我们最后甚至跑通了两个人一起联机哦。(叉腰)
No Optimization No Life
前面提到功能领先。但很不幸,性能上我们并不领先。
决赛线上赛阶段,主要产生差距的一个赛题叫做BuildStorm。简单而言,它要求在我们的内核上编译arceos-helloworld,利用这个仓库提供的环境。这不简单哦。但是我们第一次尝试时,解决了一些无足轻重的小问题后,就直接通过了。所以我说我们的功能比较完善可不是虚言。
不妙的是,我们一共花了五十分钟(我,没有,打错字)。
「话说内核到底是不是早就卡死了」这种想法,在编译过程中,我翻来覆去地想着。最后成功了真是吓了我一跳。
不过这样,我们连保底的编译通过分都拿不到,就会因为超时被评测机提前停掉。真是太残忍了……
于是我开始优化。
想法很简单。首先我们不能完全靠简单的临时日志。我们需要系统性的方案。我为内核开发了一套原生的性能观测框架,允许方便地在内核内部插桩,收集各种种类的统计数据。比如per-syscall的耗时,比如某条代码路径的时间占比,比如kernel-user时间差分,等。此外还需要针对性的用户态bench,以及相应的rootfs配置和端到端的脚本,方便直接收集数据。
Bench这块我们是把libcbench fork下来进行再维护的。这东西可以用来制造一些针对性的micro bench。此外我们还有一个bench,就是cargo build一个helloworld级别的项目,用来测试并统计内核的综合性能;这东西只能测单核,我本来还打算做一个存在多个crate的package以测试多核并发性能的,但是最后不了了之。构建rootfs,启动内核,跑bench,日志后处理,整套流程还是比较耗时的。
我们主要两种思路优化:
- 先统计某次cargo build的各类时间占比,然后尝试定位某个潜在的单点优化。
- 寻找Common Path优化点。比如syscall的string参数转换器。这个主要看个人的感觉,或者说对codebase的了解。
利用临时代码测试发现优化收益成立后,再在正式分支给出系统性优化。
总之做了不少优化。比较重要的就是内核slab分配与页分配的并行缓解,通过引入percpu的本地缓存之类的。
按照公开的baseline,本地最后是可以做到满分的。数值上,大概就是50min优化到20min。可惜赛方的评测机性能相当烂,达不到我们本地的一半,分数也就拿不满了。此外评测机很不稳定,同样的代码几次提交分数差距可以相当大。
逆转大局吧!现场赛
该怎么说。现场赛大概是我们柳暗花明的时刻。
初赛排名三十一,决赛线上阶段二十五,虽然分数和前列都贴得比较近,但是差距客观存在。
但是,现场赛我们拿到了全场第二。再加上赛方分配给现场赛的比重相当大的缘故,以及我们的答辩和复审都相当顺利,最后拿到了国一。
现场赛大概是说给了四道题。其中有一道就是BuildStorm,大概是赛方给的保底。要想拉开差距还是得依赖前三道题。
具体题目就不必提。总之其中两题是非Linux语义的东西,要求我们现场实现;一道题是现实负载(KeyDB),要求我们的内核能顺利运行并通过一些测试点。
然后我们很惊讶地发现,这个测试直接通过了。我们没有做任何修改。这说明我们内核的功能的确比较完善。
同时,现场似乎几乎没有别的队伍最后拿到了这题的分。令人感叹。
此外我们也有努力做了第一题和第三题,但前者因为测试平台很诡异最终没能适配好,第三题则是我没有写出来。真抱歉!
接着就是答辩。按赛方的要求,我们事先录好了答辩视频,然后回答评委的问题就是了。
具体一点,现在还能回想起来的事情主要是:评委先说我们视频讲太快了,让我们再仔细讲讲一些创新点之类的内容。之后问了我们说从零设计指的是什么。此后有问我们所谓的内核动态扩展机制是指什么,还问了我们是不是大部分代码都是AI实现的,我们写了哪些部分;此外还问了板子适配过程中遇到过什么困难。
这里还是多亏了那几天帮我们模拟演练的qiu老师。对了,本校对此赛事一直很看重的样子,其实从三月以来,就一直有在开周会呢。
(虽然那段时间,对于我们队伍而言,几乎每次周会都是坐牢)
此后还有一个复核环节。此前的答辩只是对着PPT讲,但是复核要求根据代码论述。似乎有一支队伍因为表现不好被降级了,从国一变成了国二。总之就是非常不幸。
下午听了颁奖典礼。至此,比赛就告一段落了。
南开闪景
现场赛在南开大学津南校区举行,为期三天。我逗留了四天。
南开的食堂真好啊。
南开的校园好大,明明是分校区。
南开的建筑也有特色。风格上很和谐喔。
我想这很可能是唯一一次(至少很长一段时间)与南开大学相遇了。也算一期一会。
我在开头说过我很想表达我对赛方的不满,可是一路写下来却没写出多少赛方的可恶行为。倒也不是我想要粉饰这段时间,只是写着写着,发现那些话都被我忘记了。
我没有不生气,也不想故作豁达。我还记得很多赛方自己做的荒唐事。比如测试平台文档和GitHub公开测例文档不自洽或者干脆说自相矛盾;赛方对各个队伍的反馈一直爱答不理;无视不少队伍的针对测例编程;性能烂到爆的测评机和代码质量堪比草履虫的OJ;现场赛不允许使用AI要求古法编程……
但我不想写了。回忆是很累的一件事,尤其是主题是这些东西时。简直是一件苦差。
总之,我的大二伴随着这次体验不尽人意的竞赛告一段落了。
大二的课很繁重啊。再加上内核赛,以及我们队伍奇怪的配置,真受不了。
不过好在一切都结束了——我的、因内核赛和其余琐事而分崩离析、支离破碎的、本该轻松写意的日子。
「Aber das ist erst der Anfang. 」可以这么说么?请让我稍微期待一下此后的生活吧。