Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

项目总览与工程规范

1. 为什么必须做项目

系统知识有一个残酷的特点:读懂了不等于会做,会做不等于做对。

你可以背出 epoll 的 LT/ET 区别,但只有当你写一个 1 万连接的 echo 服务器,看到 CPU 100%、Recv-Q 持续增长、epoll_wait 返回 EPOLLHUP 却没收尾时,才会真正理解「边缘触发必须读到 EAGAIN」不是一句口号。你可以画出 TCP 状态机,但只有当你亲手实现一个用户态协议栈,被重传、乱序、窗口更新折磨过,才会记住 TIME_WAIT 的意义。

因此本书把第 6 篇写成四条项目主线、11 个可运行项目。每个项目都对应一个真实系统,但不要求你复刻它的全部功能;要求的是把核心机制做对,并用测试与压测证明。

2. 四条主线

主线项目真实对应物核心机制主要能力
A 云原生A1 mycontainerDocker / runcnamespace、cgroups、overlayfs、OCI隔离、资源、生命周期
A2 myhttpdnginx / lighttpdepoll、HTTP、零拷贝、线程池高并发、协议、性能
A3 mybpfbpftrace / Pixiekprobe、tracepoint、map、libbpf可观测性、内核
B 存储B1 myredisRedisRESP、事件循环、持久化、复制数据结构、I/O、一致性
B2 mydbSQLite / LevelDBB+ 树、buffer pool、WAL、恢复存储引擎、崩溃一致性
C 网络C1 mytcplwIP / Linux 栈以太网、IP、TCP 状态机、重传协议实现、调试
C2 myproxyHAProxy / Envoy连接池、负载均衡、超时、零拷贝代理、并发、稳定性
C3 mysnifftcpdump / WiresharkAF_PACKET、解析、流重组抓包、协议分析
D 工具链D1 myshdash / bash词法、展开、管道、作业控制Shell、进程、信号
D2 myboxBusyBoxapplet 框架、选项解析、文件操作CLI 工程、可移植性
D3 mygitGit对象模型、哈希、索引、packfile数据模型、二进制格式

不要 11 个全做。按你的路线选 2–3 个:应用/运维选 D2 + A2;系统编程选 A2 + B1 或 B2;内核/性能选 A1 + A3;网络方向选 C1 + C2 + C3(C3 可作 C1 的调试工具)。

3. 项目难度与投入

项目难度预计投入前置章节最小可交付
A1 mycontainer★★★★30–40 h2.7、2.19、3.4、5.1–5.5能 run 一个 busybox,带 PID/mount/net 隔离与内存限制
A2 myhttpd★★★★30–40 h2.16–2.18、4.5、4.8–4.10静态文件 + keep-alive + 1 万连接压测
A3 mybpf★★★★20–30 h3.3、3.15、4.8追踪 openat 与 TCP 收包,导出统计
B1 myredis★★★★★40–60 h2.14、2.16–2.18、4.5–4.6RESP + GET/SET/EXPIRE + AOF + 主从
B2 mydb★★★★★40–60 h2.2–2.3、3.12–3.13B+ 树 + WAL + 崩溃恢复 + 简单 SQL
C1 mytcp★★★★★50–70 h4.2–4.6、2.18能 ping、能三次握手、能传文件
C2 myproxy★★★☆25–35 h2.16–2.18、4.7HTTP 反向代理 + 健康检查 + 压测
C3 mysniff★★★15–25 h4.2–4.5、2.18抓包、解析 TCP/UDP、导出 pcap
D1 mysh★★★★30–40 h2.7–2.10、2.19管道、重定向、内建命令、作业控制
D2 mybox★★★20–30 h0.3、2.2–2.610 个 applet + 统一选项解析
D3 mygit★★★★30–40 h1.7、2.2–2.3init/add/commit/log/checkout 与真 git 互操作

4. Stage 模型

每个项目都按 Stage 递进。Stage 不是「章节」,而是可验收的里程碑:

Stage 0  骨架与构建:能编译、能跑、有测试入口
Stage 1  最小闭环:核心路径通,能演示
Stage 2  正确性:边界、错误、并发安全
Stage 3  性能:有基线、有优化、有数据
Stage 4  可观测:日志、指标、trace
Stage 5  交付:容器镜像、CI、文档、复盘

每个 Stage 的页面包含:

  • 目标:这一阶段结束时,什么命令能跑出什么结果;
  • 关键概念:需要回看的章节与 API;
  • 代码:完整可编译的核心片段,不是伪代码;
  • 验证:命令、预期输出、失败时怎么查;
  • 常见错误:3–6 条,含错误信息与修复。

规则:Stage N 没通过验收,不要开 Stage N+1。系统项目最常见的失败不是「不会写」,而是「在错误的地基上堆功能」。

5. 目录规范

每个项目一个独立目录,建议放在仓库外(避免把几千行代码塞进教程站),或放在 linux/code/<project>/(本书附录给链接):

myhttpd/
├── Makefile
├── README.md
├── include/
│   ├── http.h
│   ├── buffer.h
│   └── log.h
├── src/
│   ├── main.c
│   ├── event.c        # epoll 封装
│   ├── http.c         # 解析/响应
│   ├── file.c         # 静态文件/零拷贝
│   └── log.c
├── tests/
│   ├── test_http.c
│   ├── test_buffer.c
│   └── run.sh
├── bench/
│   ├── wrk.lua
│   └── report.md
└── docs/
    ├── design.md
    └── stage-notes.md

三条纪律:

  1. 一个模块一个文件,一个文件一个职责;超过 800 行的 .c 要拆。
  2. 公共头文件只放声明;内部结构体放 internal.h,不要污染公共 API。
  3. 每个 Stage 打一个 git tag(stage-0…),方便回滚与对比。

6. 验收标准

每个项目按六维打分,每维 0–2 分,总分 12:

维度0 分1 分2 分
功能跑不起来主路径可用主路径 + 边界都可用
正确性有已知崩溃/泄漏常规测试通过错误注入/并发/崩溃测试通过
性能无数据有基线有优化前后对比与瓶颈解释
可观测只有 printf有结构化日志有指标/trace,能定位一次真实故障
代码质量难读、重复模块清晰有测试、有注释、无警告
文档无README 能跑设计文档 + 复盘 + 压测报告

及格线 8 分,优秀 10 分以上。功能满分但正确性 0 分的项目,按 0 分处理——系统程序的第一美德是正确。

7. 安全与伦理

  • 容器/namespace/网络实验会改宿主状态:先打快照,在虚拟机里做;不要在公司/共享机器上 --privileged。
  • 端口扫描、抓包、压测只对你自己的实验环境做;不要对公网目标做。
  • rm -rf、mkfs、dd、ip link del 这类命令,执行前先 echo 确认变量非空,再用 -- 分隔。
  • 内核模块崩溃会拖垮整机;用 QEMU,不要在宿主上直接 insmod 未知模块。
  • 项目代码不要引入 GPL 代码到闭源产品;本书代码默认 MIT,引用第三方库时看清许可证。

8. 时间管理

一个 40 小时的项目,建议切成 8–10 次,每次 3–4 小时:

次数内容产出
1读前置章节 + 设计design.md、接口草图
2Stage 0 骨架能编译、能跑空循环
3–4Stage 1 最小闭环能演示核心功能
5–6Stage 2 正确性测试、错误注入、修复
7Stage 3 性能基线、优化、数据
8Stage 4 可观测日志/指标/trace
9Stage 5 交付镜像、CI、文档
10复盘retro.md、演讲 10 分钟

每次结束前留一个可运行的状态,不要停在「改了一半编译不过」。这是工程习惯,也是防止项目烂尾的最有效手段。

9. 怎么写复盘

复盘不是流水账,回答四个问题:

  1. 最初的设计假设是什么?哪一条被证伪了?
  2. 最难的 bug 是什么?现象、假设、证据、根因、修复、如何防止复发?
  3. 性能瓶颈在哪?怎么定位?优化前后数据?
  4. 如果重做,哪三件事会不同?

模板:

# 复盘:myhttpd

## 1. 目标与结果
- 目标:1 万连接、静态文件 5 GB/s、P99 < 10 ms
- 结果:8 千连接、3.2 GB/s、P99 18 ms

## 2. 关键假设与证伪
- 假设「epoll ET + 4 线程足够」→ 证伪:单 acceptor 成为瓶颈
- 证据:`perf top` 显示 `accept4` 占 38% CPU

## 3. 最难 bug
- 现象:压测 30 分钟后 fd 耗尽
- 根因:`EPOLLHUP` 分支忘记 `close`
- 修复/防复发:事件循环统一 cleanup + fd 计数指标

## 4. 性能
- 基线/优化后/瓶颈解释(表格)

## 5. 如果重做
- 先做 acceptor 多线程
- 先加 fd/连接数指标
- 先写压测脚本再写业务

10. 与正文的配合

每个项目页开头会给「前置章节」与「回看时机」:

  • 做 A2 前读 2.16–2.18、4.5、4.8–4.10;
  • 卡在 epoll 事件丢失时回看 4.10 的 ep_poll_callback;
  • 卡在 TIME_WAIT 时回看 4.5;
  • 卡在内存增长时回看 3.10 与 2.3 的 mmap。

不要等「读完再做」。项目是检验理解的手段:读一章、做一段、卡住、回看,这个循环比线性阅读有效得多。

下一站:工程手册给出构建、测试、压测、CI 的具体模板;然后从四条主线里选一条开始。