读书笔记 · 约 15,669 字 · 预计推送 2 部分

Julia Evans 博客精选:3 篇图解系统概念文章精读 未开始

目录

题记与说明

精读日期:2026-11-05 | 条目前缀:knowledge-a98e_u9b-3 | 类型:主题研究(多来源)
精读对象:Julia Evans(jvns.ca)博客中三篇用图解讲 Linux 系统概念的文章
本次共检索/精读 6 个来源:3 篇正文精读 + 1 篇方法论佐证精读 + 2 个背景来源(归档页、Wizard Zines)
所有引述均为本次 curl 抓取原文后核对的原句(下载于 2026-11-05)

0. 选题与来源清单(检索过程)

从 jvns.ca 首页全站归档(按分类列出全部博文)的 "How a computer thing works"、"Computer networking"、"Kubernetes / containers" 等分类中,按「①讲系统概念 ②以图示/结构化视觉手段为主要教学载体 ③三篇合起来覆盖不同系统层」三条标准,选出以下三篇:

#文章发布日期系统层图解形式(本次抓取核实)
1What happens when you start a process on Linux?2016-10-04进程生命周期(OS)ASCII 进程树 + 拟人叙事(无 img 图片)
2Async IO on Linux: select, poll, and epoll2017-06-03内核 I/O 多路复用系统调用签名对照、性能表格、strace 真实输出、书封图
3How containers work: overlayfs2019-11-18容器文件系统手绘漫画(overlay.jpeg)+ 完整 mount 实验终端记录

佐证来源:


1. 篇一:What happens when you start a process on Linux?(fork/exec 的"吃脑"模型)

链接:https://jvns.ca/blog/2016/10/04/exec-will-eat-your-brain/

核心问题

"启动一个进程"到底是几个系统调用?直觉答案是 start_process(["ls", ...]) 一个调用搞定——Julia 指出这在 Linux 上是错的:实际是 fork + exec 两个系统调用的组合(并坦白 posix_spawn 的存在)。

主要主张

Linux 进程的诞生不是"从无到有生一个孩子",而是"先克隆自己,然后让克隆体被吃掉大脑变成另一个程序"。这个怪异但准确的心智模型,能解释进程树、僵尸进程、环境继承等一系列现象。

方法

  1. 拟人叙事 + ASCII 进程树逐阶段演示:
my parent          my parent          my parent            my parent
 |- me    fork→     |- me    exec→     |- me      wait→     |- me
                    |-- clone         |-- ls               (一切归位)
                                      |-- ls (zombie)
  1. 给出可运行的伪代码(C 风格),把三个分支(子/失败/父)标注清楚;
  2. 列出进程属性清单(打开文件、环境变量、信号处理器、地址空间、寄存器、cgroups/namespace、cwd、用户……)来定义"大脑"到底是什么;
  3. 用一个真实 bug(Python 忽略 SIGPIPE)证明这套细节有实际后果。

数据与证据

结论

关键原文引述

"Instead of having children, what I do is you have a child that is a clone of myself, and then that child gets its brain eaten and turns into ls. Really."
"At this point ls is actually a zombie process! That means it's dead, but it's waiting around for me in case I want to check on its return value (using the wait system call.)"
"It turns out that Linux implements 'copy on write' for fork() calls, so that for all the 2GB of memory in the new process it's just like 'look at the old process! it's the same!'"
(fork 伪代码注释)"int pid = fork(); // now i am split in two! augh! // who am I? I could be either the child or the parent"
"So, your program's environment (environment, signal handlers, etc.) can matter! It inherits its environment from its parent process, whatever that was! This can sometimes be a useful thing to know when debugging."

局限

对我的启发


2. 篇二:Async IO on Linux: select, poll, and epoll(事件循环的内核地基)

链接:https://jvns.ca/blog/2017/06/03/async-io-on-linux--select--poll--and-epoll/

核心问题

Web 服务器同时有几千个连接,怎么知道哪个连接有新数据而不浪费 CPU 轮询?Julia 从 Michael Kerrisk 的《The Linux Programming Interface》(TLPI) 第 63 章 "Alternative I/O models" 出发,回答三个问题:select/poll/epoll 有什么区别?为什么 Linux 服务器最终都用 epoll?你的程序什么时候在用 epoll?

主要主张

select/poll 的本质缺陷是内核不记得监视列表——每次调用都要从头检查所有 fd;epoll 用"注册一次、内核记住、返回时只报就绪者"解决了这个问题,万级 fd 下快三个数量级。这是 node.js / Go / gevent / nginx 事件循环的共同地基。

方法

  1. 从书到内核源码:引用 TLPI 第 63 章后,去 Linux 4.10 源码验证"poll 和 select 用同一套代码"的惊人结论(给出 fs/select.c 行号级链接);
  2. 从签名反推性能:对比两个函数原型,指出 select 的 nfds 参数使它必须循环 0..nfds,而 poll 只遍历稀疏给出的 pollfd 数组——"poll can perform better than select if you have a sparse set of file descriptors";
  3. 用 strace 实证:贴出真实 strace 输出,展示 Go 程序做一次 DNS 查询时 runtime 的 epoll 调用序列(epoll_create1 → epoll_ctl ADD fd 3/4 → write 两个 DNS 查询 → read 得到 EAGAIN → epoll_wait);
  4. 引用性能表格(TLPI,100,000 次监视操作)并自己下结论。

数据与证据

TLPI 性能表(100,000 次监视操作的耗时,原文照录):

# operationspollselectepoll
100.610.730.41
1002.93.00.42
100035350.53
100009909300.66

结论

关键原文引述

"Instead of spending all CPU time to ask 'are there updates now? how about now? how about now? how about now?', instead we'd rather just ask the Linux kernel 'hey, here are 100 file descriptors. Tell me when one of them is updated!'."
"The first surprising thing I learned from this chapter are that poll and select fundamentally use the same code."(随后用内核源码证实)
"The kernel doesn't remember the list of file descriptors it's supposed to be monitoring!"
(strace 注释)"# then it uses epoll to wait for responses" / "16016 epoll_wait(5, <unfinished ...>"
"Probably there are some wrong things in this post, let me know what they are!"

局限

对我的启发


3. 篇三:How containers work: overlayfs(容器分层的 mount 实验)

链接:https://jvns.ca/blog/2019/11/18/how-containers-work--overlayfs/

核心问题

Docker 镜像动辄几百 MB(Anaconda 800MB–1.5GB),每个容器都以镜像为"白纸"启动——如果每个容器都拷贝一份镜像,磁盘和速度都不可接受。Docker 是怎么共享镜像内容的?答案是 overlay filesystem。

主要主张

overlayfs(union filesystem)用两个目录就能讲清楚:lower 只读、upper 可写;读文件先查 upper 再查 lower,写文件只写 upper。Docker 的"镜像分层"就是多个 lowerdir + 一个空 upperdir 的 mount 组合。

方法

先画漫画、再做实验。开头就是一篇手绘漫画(overlay.jpeg,后来发展成 zine《How Containers Work!》),然后全文用可复现的终端实验推进:

  1. mkdir upper lower merged work,在 lower/upper 各放文件(含同名 in_both.txt);
  2. sudo mount -t overlay overlay -o lowerdir=...,upperdir=...,workdir=... merged;
  3. 依次做三个实验:读同名文件(读到 upper 版本)、新建文件(出现在 upper)、删除文件(意外发现!)。

数据与证据

结论

关键原文引述

"I wrote a comic about overlay filesystems for a potential future container zine this morning, and then I got excited about the topic and wanted to write a blog post with more details."
"When a process reads a file, the overlayfs filesystem driver looks in the upper directory and reads the file from there if it's present. Otherwise, it looks in the lower directory."
"But what happened in upper is a little strange: there's a file called upper/in_both.txt, but it's a… character device? I guess this is how the overlayfs driver represents a file being deleted."
"I think containers often seem like they're doing 'complicated' things and I think it's fun to break them down like this – you can just run one mount incantation without actually doing anything else related to containers at all and see how overlays work!"

局限

对我的启发


4. 方法论佐证:她自己的"图解词汇表"

来源:How to teach technical concepts with cartoons(2017-09-28,本次精读)

这篇解释了上面三篇的"图解"从何而来。要点:

  1. 图解 ≠ 绘画:"There actually is a skill to explaining technical concepts to people with drawings... It is just a different skill than like 'drawing an elephant that looks like an elephant'"。她的全部视觉词汇就是几个基础形状(文中 artistic-range.jpg)。
  2. 六种图解模式(每种配例图):
    • Personify:把系统变成会说话的角色(kubelet ↔ API server;laptop/router/OS/program)——"Taking a complicated computer system and breaking down 'ok, these are 3 main important characters in this system' is incredibly useful."
    • Show a scene:让角色互动(两个程序抢同一个 mutex,program 2 心想 "not my turn yet");
    • Make a list:列表(网络工具清单、Unix 进程属性清单)——"none of them claim to be exhaustive";
    • Make a diagram:手绘图(内存碎片、UDP 包结构,用灰色弱化次要字段);
    • Make a joke + Paste some computer output:直接把手绘的 strace 输出放进漫画;
    • Structure/panels:用分格控制阅读顺序。
  3. 信息量标准(最重要的判断):图解有两种用途——"draw diagrams / cartoons that make what you're teaching more clear" vs "use drawings to make what you're teaching more fun"。她点名批评《The Children's Illustrated Guide to Kubernetes》:把长颈鹿画全删掉,信息量几乎不变,因为"almost all the actual content is in the text",而且全文没提 etcd(她认为不理解 etcd 就不理解 Kubernetes)。→ 我的图必须删不掉。
  4. 门槛破除:"drawings don't have to be beautiful to be awesome"——她早期用 Sharpie 画的草图同样获得大量好评;工具只是平板 + Squid 应用。
  5. 选题方法:"I organize a lot of my comics about some key insight / fact / surprising thing that it took me a long time to learn and was really useful to me."(例:浮点数漫画源于 Stefan Karpinski 给她的三个顿悟)
交叉验证:三篇精读文章恰好是这套方法论的落地——proc 篇 = personify + list(进程属性清单);epoll 篇 = paste computer output + 从签名推理;overlayfs 篇 = 手绘漫画 + 实验。

5. 七维总提取(跨三篇综合)

5.1 核心问题

系统级概念(进程创建、I/O 多路复用、容器文件系统)在 man page 和教科书里抽象、令人生畏,开发者"不知道自己不知道什么"。这三个概念分别决定了:shell 怎么运行命令、node/Go/nginx 为什么能扛万级连接、Docker 镜像为什么能分层复用。如何让普通开发者快速建立正确且可迁移的心智模型?

5.2 主要主张

复杂系统行为可以压缩为极简心智模型 + 亲手实验:

配套主张(方法论篇):图解必须承载信息量(clear > fun),画图不需要绘画技能。

5.3 方法

三段式:(a) 从一个惊讶事实或个人困惑切入("poll 和 select 用同一套内核代码""删除文件会留下字符设备");(b) 最小可复现实证——kernel 源码行号链接、strace 真实输出、mount 实验、TLPI 性能表;(c) 视觉化收尾——ASCII 图/手绘漫画/表格;全程引用一手资料(man pages、内核源码、Docker 文档)并公开标注不确定处。

5.4 数据与证据

5.5 结论

三篇合起来给出一条主线:Linux 系统是可观测、可实验的对象,不是黑盒。进程环境继承解释"同一程序不同父进程行为不同"类 bug;epoll 的"内核记住名单"是事件循环高性能的原因;overlayfs 的 lower/upper 规则解释容器镜像的共享与隔离。三者都可用一条命令(pstree / strace / mount)直接观察。

5.6 局限

5.7 对我的启发

  1. 写作结构可复制:惊讶事实 → 最小实验 → 图,是技术博客的高转化结构;把"意外发现"(白洞文件)当文章高潮而非删掉它。
  2. 学习顺序可复制:新系统先跑最小实证(strace/mount/dig)再读文档;从函数签名读性能差异。
  3. 图解标准:画图前自问"删掉这张图,信息少了吗?"——少则留,不少则删(clear > fun 标准)。
  4. 心智模型库:克隆-换脑(进程)、内核记住名单(epoll)、上下层目录(overlayfs)——这种"一句话 + 一张图"的压缩是可迁移的笔记格式。
  5. 诚实即可信:公开自我更正(posix_spawn)和标注推测("I assume")不损害反而增强信任,值得在我的笔记/博客里效仿。
  6. 直接工程收益:Python subprocess 继承 SIGPIPE=ignore 的坑;strace 见 epoll_wait = 事件循环;容器文件异常先查 lower/upper 层。

6. 附:本次未能完成/未验证的事项

引用来源汇总

  1. https://jvns.ca/blog/2016/10/04/exec-will-eat-your-brain/ (精读,2016-10-04)
  2. https://jvns.ca/blog/2017/06/03/async-io-on-linux--select--poll--and-epoll/ (精读,2017-06-03)
  3. https://jvns.ca/blog/2019/11/18/how-containers-work--overlayfs/ (精读,2019-11-18)
  4. https://jvns.ca/teach-tech-with-cartoons/ (方法论精读,2017-09-28)
  5. https://jvns.ca/ (全站归档,选文依据)
  6. https://wizardzines.com/ (zine 目录,背景来源)

文内二手引用:TLPI (Michael Kerrisk)、man7.org man pages、torvalds/linux fs/select.c、libuv、golang netpoll_epoll.go、nginx ngx_epoll_module.c、idea.popcount.org(Marek 三篇)、kamalmarhubi shell-workshop 及 bug 复盘、rachelbythebay fork 文、docs.docker.com storage drivers、moby#27653。

← 资料库今日播报 →