Jul 31, 2025
有关Sys、初心者的奇妙之旅。
这篇记录的语境接续此前的一篇文章,因为内容比较多,且相对独立,所以特拆开放在这里。
……
再说回Coding吧。
我喜欢计算机,喜欢写代码,我想至少到现在,都是很纯粹的喜欢。很大一部分原因,是我喜欢创作。我不想用「创造」这个字眼,它听起来毕竟还是和「创作」不太一样。大概是因为,创作带着一些情感色彩吧。
不过在相当一段时间里,我还处于比较困惑的状态。因为很喜欢游戏,所以我在初中的时候有自己学过一点点C(完全是因为听说「想学编程就必须先学C语言」之类的说法去学的),C#(因为后来了解到Unity可以使用C#)和Unity,做过一点简单的游戏,尝试搭建过一个blog(很早就废弃了),所以呢,大一最初我仍然继续使用着C#。国庆的时候,我基于WPF写了一个三千行代码左右的音乐播放器,当时还是有点开心的。
可是我并不大清楚,我究竟学到了些什么。
当时的我呢,对计算机的领域并没有什么清晰的认知,基本上只知道Web,GUI,游戏这种其实并不能并列的分类。并且有逐渐意识到,再这样下去也仅仅是记下更多的API如何使用而已,而并没有真正理解背后的原理;此外,这样的知识几乎不具备可迁移性,它们对我的帮助是线性甚至更低阶的,我不应该花费太多时间才对。
我尝试学习一些类似「道」的东西,比如我感觉我的代码非常混乱,所以有在学习MVVM。可是,我很快感觉这仍然不是我想要的。当时的我莫名地认为,这东西太虚,太软;我该学习算法吗?我有在LeetCode写过一些算法题,但是我还是固执地认为,这和我想象的计算机不一样。
这是个有点偏颇的观点。CS本来就是个比较庞大的领域,没有人能否认算法不包含在内。不过,这确实使我走向了一个我认为正确的方向:Systems Programming。
于是我开始学习计算机底层。
最开始,我以为底层就是OS。所以我找到了OSTEP,开始阅读。
于是不出所料地失败了。毕竟我之前一直在C#的环境开发,对内存、线程之类的东西的了解太肤浅了。
然后因为看到周边人有的在玩计算机网络,所以我也想试试。这次我吸取了点教训,并没有从头就读Top-Down这种大部头,而是先读了比较轻松的《网络是怎样连接的》。可惜的是,也没什么进展的样子,除了让我对网络有了一个大概的、比较肤浅的认知。
至此,我仍然没有发现我希望追求的东西。啊,所谓的底层的、不变的事物,究竟是什么样的呢。这样的困惑时常会有。
然后出于对Linux的好奇,我开始阅读TLPI,这也就是转机。
TLPI是一本很棒的书。它依托于Linux,比较系统地介绍了操作系统的各个方面。我在十五天内快速通读了上册,跟着做了一些章节后的习题,获得了对系统级编程的初步认知。
我慢慢开始喜欢C语言了。它虽然很原始,但是简洁、直接,几乎每一步我都能清晰地知道我在做什么;而更高级的语言往往隐藏了很多细节,我不太喜欢对感知的缺失。这是优点还是缺点呢?我不清楚。
但是,当我草草读罢上册,我很快就收手了。我意识到,我大概具备了一些基本的知识,而TLPI更多关注的是OS API/ABI的应用层,而非底层原理,且很多都聚焦在Linux的特殊性质上,这仍然不具备普适性。该拐弯了,我对自己说。
某天,我回想起了社团(啊对了,我加入了学校的开源技术协会)里的一位前辈推荐的一本书:CSAPP。稍作查询,我发现这大概正是我接下来需要的东西,所以我开始阅读。阅读的过程很痛苦,这实在不是一本轻松的读物,信息密度相当高,很多概念对我而言必须反复琢磨才能理解。
第一次阅读,我主要学习了X64汇编,对内存、编译流程、缓存具备了基本认知。其实我大概也就认真读了对应这些内容的几章,别的并没有涉足。当然,这样的收获我已经很满足了。
由于CSAPP的第七章(关于链接)比较晦涩,所以我选择了另一本书,《程序员的自我修养》。这本书虽然比较古旧,但是内容其实至今仍然比较适用。我很快在一周内就读完了我需要的东西(基本上,舍去了Windows相关的内容,对动态链接的阅读相对粗略),然后写了一个相当简单的C运行时。亲自进行系统调用的感觉让我很开心哦。
话说回来,这样四处看看,其实不太符合我的期望,我预期的是一个比较系统的认知。某个时刻,我意识到我仍然缺乏一些各个领域都需求的基础知识。我需要建立某种整体感知。
System的核心离不开OS,所以,我决定重新挑战OSTEP了!这时候到了寒假。
有个小插曲。此前我发现了一本很酷的书,Nand2Tetris。从基本的布尔代数开始,基于最基本的与非门,一步步构建出一个计算机系统,最后实现一个简单的操作系统。所以在寒假的最初三天,我完成了本书的前半部分,也就是硬件相关的内容;构建了一台相当原始的计算机,并配备了一个汇编器。虽然是相当玩具的级别,不过也很有趣。
然后我开始阅读OSTEP,同时开始学习MIT6.S081的课程。这次的切入相对顺利不少。不过,在我完成了前一半的实验以后,我再次遇到了困难。我发现,我对硬件的知识太匮乏了,以至于相关的代码几乎完全无法理解。
所以我再次终止了低效率的深入。在此之前,我已经了解了一些内存管理、进程调度的基本原理。我决定暂时放下OS。
然后,我转向一门新的语言:Rust。这实在是一门相当漂亮的语言,我一下就对它爱不释手了。寒假剩下的日子里,我阅读了The Book和Progamming Rust。接着,一直延续到开学开头一个星期,我阅读了Crafting Interpreters的前半部分,最后,使用Rust实现了一个简单的遍历AST的解释器:Rlox。
然后我就对编译器产生了不小的兴趣。我找来了Engineering a Compiler,只是稍微读了一些,我就发现我暂时不具备阅读的能力,所以搁置了。
接下来的事情就没太多波折了,因为基本上我已经明确了暂时的方向。
首先,我编写了Chip-8的一个模拟器。然后,参考8080 Programmers Manual以及一些其余信息,编写了一个Intel 8080微处理器的模拟器。这样,我对代码背后发生的事情有了相对充足的理解。
接着,我阅读了An Introduction to Assembly Programming with RISC-V。由于此前6.S081的经验以及对RISC架构的兴趣,我决定首先学习RISC-V。这本书很轻松,收获也挺大的。
然后,我决定自己开始使用Rust编写一个简单操作系统了。似乎跨度非常大,但是我认为我的理论储备已经足够了,如果再仅仅阅读下去,收益大概会逐步递减。
我主要依托于这些资料:
- OSTEP
- xv6
- 清华的RCore
- PopOS (Writing an OS in Rust - Philipp Oppermann’s blog)
- Stephen Marz的OS Blog
- RISC-V关于Privileged Architecture的文档
大概过了两个月,CafOS(Caf?Kaf!……)的开发就完毕了。这是一个运行在RISC-V单核处理器上的简单类Unix操作系统,支持简单的轮转调度,多级页表,用户态和内核态的切换,一个基本的文件系统。内核使用Rust,用户态的shell等程序使用C。总计大概13k行代码。我想一定产生了很多的冗余,因为其实这个内核的功能还是相当简单啦。
虽然是第一次编写,但是我很满足。因为我终于有了一个比较完整的系统级编程的认知。
接着其实就没什么特别重要的事情了。广泛阅读了一些书籍,并回过头补充越读了一些我之前没能理解的事物。值得一提的是,我阅读了DDCA。对硬件开发有了一点基本的认知,使用SystemVerilog编写了简单的RV32I单周期处理器和五级流水线处理器。
然后,我想,我还差最后一片拼图了。OS,CPU,我还差什么?答案很清晰了:Compiler。
这次我再次做了一些理论补充。我主要使用了三本书:
- Engineering a Compiler 主要参考资料,类似百科的地位
- Writing a C Compiler 给出了比较清晰的伪代码和一些设计思路
- Practical Compiler Construction 分析了一个简单C编译器的源代码,给出了一些思路和启示
然后大概三周时间,我编写了一个简单的、无依赖的C编译器,drcc。仅仅支持相当小的C子集。也尝试做了一些优化。它遵循这样的流程:Source Code -> AST -> HIR (AST with semantic information) -> MIR (Three-Address Code) -> LIR (Concrete RISC-V assembly with virtual registers) -> ASM (Valid RISC-V assembly code)。
至此,第一年的学习基本上告一段落。写了这么多,我还是很喜欢系统编程。我感觉我现在才算刚刚入门,很多此前完全不了解的东西,现在才稍微有了一点点能力深入。
期待之后的旅程啦。