Skip to content

第三十五章:万物归一

筑基圆满

涉及内核源码:

林小源站在内核的数据结构之海中,回望来时的路。

,从 ,从信号到线程,从命名空间到 cgroup,从管道到共享内存——他走过了进程管理的完整路径。

他闭上眼睛,在脑海中画出了一幅完整的图景。

这幅图景不再只是系统调用的先后顺序。它还包括那些看不见的边界:nsproxy 决定进程看到哪个世界,cgroup v2 的统一层级决定它属于哪条资源因果链,PSI 和 delay accounting 则记录它在资源不足时究竟停顿了多久。进程的一生,不只是出生、执行、死亡,也包括它在哪个世界出生、被哪棵树约束、在哪些等待里损失了时间。

c
/*
 * 进程管理的完整生命周期:
 *
 * 创建 → 蜕变 → 执行 → 通信 → 退出 → 回收
 *
 * fork()     — 创建进程(复制 task_struct)
 * execve()   — 蜕变(替换地址空间、加载新程序)
 * 系统调用   — 执行(与内核交互)
 * IPC        — 通信(管道、共享内存、信号量、信号)
 * exit()     — 退出(释放资源,变成僵尸)
 * wait()     — 回收(释放 task_struct,获取退出状态)
 */

struct phase {
    const char *name;
    const char *syscall;
    const char *kernel_func;
    const char *description;
};

struct phase phases[] = {
    { "创建",  "fork()/clone()", "copy_process()",
      "复制 task_struct,分配 PID" },
    { "蜕变",  "execve()",       "load_elf_binary()",
      "替换地址空间,加载 ELF" },
    { "执行",  "syscall",        "sys_call_table[]",
      "通过系统调用与内核交互" },
    { "通信",  "pipe/shm/kill",  "do_pipe/do_shm/signal.c",
      "进程间传递数据或信号" },
    { "退出",  "exit()",         "do_exit()",
      "释放资源,变成僵尸" },
    { "回收",  "wait()",         "do_wait()",
      "释放 task_struct,获取退出状态" },
};
int nr = sizeof(phases) / sizeof(phases[0]);

printf("=== 进程管理全貌 ===\n\n");

for (int i = 0; i < nr; i++) {
    printf("--- %s ---\n", phases[i].name);
    printf("  系统调用:   %s\n", phases[i].syscall);
    printf("  内核函数:   %s\n", phases[i].kernel_func);
    printf("  作用:       %s\n\n", phases[i].description);
}

printf("--- 关键数据结构 ---\n");
printf("task_struct:  进程描述符(PID、状态、资源指针)\n");
printf("mm_struct:    地址空间(页表、VMA 列表)\n");
printf("files_struct: 文件描述符表\n");
printf("signal_struct: 信号信息\n");
printf("nsproxy:      命名空间代理\n");

printf("\n--- 关键内核路径 ---\n");
printf("kernel/fork.c:    copy_process() → dup_task_struct()\n");
printf("fs/exec.c:        load_elf_binary() → begin_new_exec()\n");
printf("kernel/exit.c:    do_exit() → exit_notify()\n");
printf("kernel/signal.c:  send_signal() → do_signal()\n");
printf("kernel/sched/:    schedule() → context_switch()\n");

万物生于一,一又生万物。

破关试炼

全貌归一之试

本卷总结里,命名空间负责隔离世界,cgroup v2 负责什么?

答对后才能继续滑动和进入下一章。

林小源在回顾的过程中,想起了那些他遇到过的存在。

init 童子——那个曾经嘲笑他的 PID 1。现在林小源理解了:init 童子的傲慢源于孤独。PID 1 不能被杀死,不能退出,不能犯错。他的每一个决定都影响整个用户态世界。这种压力,不是常人能承受的。

kthreadd 婶婶——那个沉默的 PID 2。她知道 #ifdef 0 的存在,知道林小源的起源,但她选择沉默。也许她有她的理由——有些真相,知道了反而是一种负担。

shell 小妹——那个活泼的 bash 进程。她不懂内核的深处,但她的每一次命令执行都是一次完整的进程生命周期。她是用户态世界中最常见的存在——平凡,但不可或缺。

日志仙翁——那个神秘的 dmesg 游荡者。他在关键时刻出现,在 dmesg 中留下一条看似无关的消息,引导林小源走向关键的源码。他是林小源的"暗中导师"。

信号量——那个在共享内存路口充当路标的计数器。它不传数据,只控制节奏,是并发世界的守护者。

管道——那条环形的水流通道。它不落盘,不回头,用完即弃。它是 Unix 哲学的化身:简单、优雅、组合性强。

cgroup 那棵资源之树——它不判断公平,只执行规则。管理员定的规则,它一丝不苟地执行。

PSI 三泉——cpu.pressurememory.pressureio.pressure。它们不争辩谁更重要,只在 somefull 中记下停顿:有些任务被堵住,或者所有非 idle 任务都被堵住。

delay accounting 的账房——它藏在 taskstats 后面,给每个任务记下等待 CPU、同步块 I/O、swapin、reclaim、thrashing、direct compact、写保护复制、IRQ/SOFTIRQ 的时间。林小源以前只看见进程在跑,现在他知道,进程没跑的时候也留下了痕迹。

容器里的 init——那个接受了自己的局限性的 PID 1。它的世界很小,但它是完整的。

内核不只是代码——它是一个有生命的世界。

破关试炼

旧友回望之试

PSI 中 `some` 和 `full` 记录的核心不是资源用量,而是什么?

答对后才能继续滑动和进入下一章。

林小源在筑基圆满的那一刻,感受到了一种前所未有的"通透"。

他不再只是一个观察者——他理解了进程管理的每一个环节。他知道 如何复制 ,知道 如何替换地址空间,知道 如何释放资源,知道 如何回收僵尸。他知道信号如何发送和处理,知道线程如何共享地址空间,知道命名空间如何隔离世界,知道 cgroup 如何限制资源。

他也知道了几个更硬的边界:PID 命名空间里的 PID 1 是容器世界的根,却不是宿主世界的 PID 1;每个进程在 cgroup v2 的统一层级中只有一个资源归属;迁移一个父进程,不会自动改变已经出生的后代;资源控制器不是想开就开,上层授权和内部进程约束会决定配置是否成立。

筑基圆满。

但林小源知道,这只是开始。进程管理只是内核的一个子系统。调度器、内存管理、文件系统、网络栈、设备驱动——还有无数的领域等待他去探索。

万物归一,一又生万物。

他抬头望向内核的深处,那里有更复杂的机制、更精妙的设计、更深邃的哲理。他还没有遇到调度仙子、内存长老、VFS 阁主——那些传说中的存在。

"你准备好了吗?"一个熟悉的声音从身后传来。

林小源转头,看到日志仙翁站在不远处。老人的白发在内核的光芒中飘动,嘴角挂着一丝微笑。

"准备好什么?"

"准备好去见更大的世界。"日志仙翁说,"筑基只是根基。你已经理解了进程的生老病死——但内核远不止这些。调度器决定谁生谁死,内存管理决定谁富谁穷,文件系统决定谁留谁忘。"

"听起来很可怕。"

"不可怕。"日志仙翁说,"你已经走过了最难的路——理解进程的本质。剩下的,只是在不同的领域里重复同样的方法:读源码,理解数据结构,跟踪函数调用。"

林小源深吸一口气。他回头看了看来时的路——信号之河、shell 小妹的循环、进程家族的组织、线程的分身、kthreadd 的沉默、命名空间的隔离、cgroup 的约束、管道的水流、共享内存的透明房间。

这些都将成为他的过去。但它们也会成为他的根基——筑基圆满的根基。

"走吧。"林小源说。

他迈开脚步,向内核的更深处走去。

破关试炼

筑基圆满之试

在 cgroup v2 中,父进程迁移后,已经 fork 出来的后代会不会自动跟着迁移?

答对后才能继续滑动和进入下一章。

道藏笔记

内核启示

进程管理是 Linux 内核的核心子系统之一。

本卷覆盖的核心概念:

进程生命周期是一条完整的路——/ 通过 创造新生命, 通过 让进程脱胎换骨, 通过 走完一生, 通过 做最后的回收。

信号机制是进程间最轻量的沟通方式—— 发信号,do_signal() 处理信号, 注册处理器,而 / 是两个不可捕获的特殊存在。

线程与并发这块, 带着 就能创建线程,但共享地址空间带来了竞态条件和死锁的风险,得靠信号量、互斥锁这些同步机制来兜底。

资源管理方面,命名空间管隔离,cgroup 管限制,两者加起来就是容器的基石。

cgroup v2 是统一层级:进程在资源树上只有一个归属,controller 由上层向下授权;PSI 记录 CPU、memory、I/O 的压力停顿,delay accounting 用 taskstats 追踪任务级等待时间。这些机制让"容器很慢"不再只是抱怨,而能被拆成可观测的资源因果。

进程间通信有管道(单向字节流)、共享内存(零复制最快)、信号量(纯同步)这几种主要手段。

筑基圆满,方可窥见更深的内核之道。


以修仙之名,悟内核之道