第 1 章 · 计算机底层:从晶体管到进程
“所有的高级抽象,最终都要落回这块硅片上。你逃不掉。“
本章覆盖的课程
| 编号 | 课程 | 本章对应的产出 |
|---|---|---|
| 131 | 操作系统实战45讲 | 手写引导程序 + 内核初始化 + 进程调度 |
| 069 | 趣谈Linux操作系统 | 系统调用、内存管理、进程模型全景 |
| 161 | 深入 C 语言和程序运行原理 | 从 C 源码到机器指令的完整链路 |
| 156 | 编程高手必学的内存知识 | 虚拟内存、分页、局部性、缓存行 |
| 050 | 编译原理之美 | 词法/语法/语义分析的理论基础 |
| 086 | 编译原理实战课 | 用 Go 实现一个完整的 C 子集编译器 |
本章要解决的问题
小林在一家做实时数据平台的公司,负责一个 Go 写的流式计算服务。某天凌晨三点,服务开始周期性卡顿——P99 从 8ms 飙到 900ms,持续 40 秒,然后自己恢复。监控上 CPU、内存、GC 全正常。
运维重启了机器,问题消失。一周后又出现。
小林查了一周,把 Go 代码翻了个底朝天,profile 打了几十份,什么都没找到。最后是一个老同事路过说了一句:
“你这个机器的 NUMA 节点是不是跨了?”
那一刻小林意识到:他一直在 Go 这一层找问题,但问题在 Go 下面第 4 层。
这一章,就是要帮你建立那个”第 4 层”的视角。
章节结构
| 文档 | 内容 | 预计字数 |
|---|---|---|
00-设计推演:从零造一个内存分配器.md | ★ 建议先读:mmap → 批发bump → 空闲链表 → size class → 三层缓存 → tiny/large 的七次失败演进 | 6,000 |
01-开篇-一次查不出的P0事故.md | 从 NUMA 事故切入:你以为的”内存”是什么 | 5,000 |
02-程序是怎么跑起来的.md | go build 到 CPU 取指,完整链路拆解 | 5,500 |
03-内存的本质.md | 虚拟地址、页表、TLB、缓存行、NUMA | 6,000 |
04-动手-用Go写内存分配器.md | 从 mmap 到 size class 到 thread cache | 6,500 |
05-编译原理-从字符到语法树.md | 词法、语法、语义三阶段 | 6,000 |
06-动手-用Go写C编译器.md | 递归下降 + 代码生成 + 目标文件 | 6,500 |
07-操作系统-进程调度与系统调用.md | 特权级、中断、上下文切换、调度器 | 6,000 |
08-动手-从引导扇区到内核.md | 写一个能启动的最小内核 | 5,500 |
09-链接装载与程序启动.md | 静态链接、动态链接、ELF、进程诞生 | 5,000 |
10-章节总结与能力自测.md | 知识图谱 + 30 道自测题 | 4,000 |
合计约 5.6 万字(超出 3 万字要求,因为这一章是地基)。
学完这一章,你应该能
- 画出从 C 源码到 CPU 执行的完整数据流,指出每一步的中间产物是什么。
- 解释一次
malloc(8)背后,内核态和用户态分别做了什么。 - 说清楚为什么 false sharing 能让多线程程序慢 5 倍,并写出复现代码。
- 手写一个能工作的内存分配器,性能达到 glibc malloc 的 60% 以上。
- 用 Go 实现一个能编译 C 子集(含指针、结构体、函数、控制流)的编译器。
- 写出一个能从 BIOS 引导、进入保护模式、打印字符的最小内核。
- 解释
go build出来的二进制文件里,每一个 section 是干什么的。
这一章为什么最难
因为它要求你同时在四个抽象层级思考:
┌─────────────────────────────────────┐
│ 层级 4:语言 Go/C 的语义 │ ← 你习惯在这里
├─────────────────────────────────────┤
│ 层级 3:运行时 编译器、链接器、ABI │
├─────────────────────────────────────┤
│ 层级 2:OS 虚拟内存、进程、调度 │
├─────────────────────────────────────┤
│ 层级 1:硬件 CPU、缓存、总线、MMU │ ← 问题往往根在这
└─────────────────────────────────────┘
大多数工程师只在层级 4 工作,遇到怪问题就束手无策。 能下到层级 2 的人,已经能解决 90% 的疑难杂症。 能下到层级 1 并回来的人,才有资格设计系统。
阅读建议
- 不要跳。这一章的内容是强依赖的,跳过 03 直接看 04 会看不懂。
- 代码一定要跑。特别是 04 和 06,光看代码没用,跑起来加个
fmt.Println看看中间态。 - 思考题先自己答。参考答案是”老陈的答案”,不是”标准答案”,你有更好的答案就说明你真的懂了。
开始阅读:00-设计推演:从零造一个内存分配器
两种读法:想先建立”设计感”的,从
00开始(它用一整篇演示了怎么从失败里推出 Go 分配器的每个设计点); 想先看真实事故的,直接跳01。两条线在03-内存的本质汇合。