第三十五章:万物归一
筑基圆满涉及内核源码:
一
林小源站在内核的数据结构之海中,回望来时的路。
从 到 ,从 到 ,从信号到线程,从命名空间到 cgroup,从管道到共享内存——他走过了进程管理的完整路径。
他闭上眼睛,在脑海中画出了一幅完整的图景。
这幅图景不再只是系统调用的先后顺序。它还包括那些看不见的边界:nsproxy 决定进程看到哪个世界,cgroup v2 的统一层级决定它属于哪条资源因果链,PSI 和 delay accounting 则记录它在资源不足时究竟停顿了多久。进程的一生,不只是出生、执行、死亡,也包括它在哪个世界出生、被哪棵树约束、在哪些等待里损失了时间。
/*
* 进程管理的完整生命周期:
*
* 创建 → 蜕变 → 执行 → 通信 → 退出 → 回收
*
* 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");#include <stdio.h>
/*
* 进程管理的完整生命周期:
*
* 创建 → 蜕变 → 执行 → 通信 → 退出 → 回收
*
* fork() — 创建进程(复制 task_struct)
* execve() — 蜕变(替换地址空间、加载新程序)
* 系统调用 — 执行(与内核交互)
* IPC — 通信(管道、共享内存、信号量、信号)
* exit() — 退出(释放资源,变成僵尸)
* wait() — 回收(释放 task_struct,获取退出状态)
*/
struct phase {
const char *name;
const char *syscall;
const char *kernel_func;
const char *description;
};
int main() {
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");
return 0;
}万物生于一,一又生万物。
全貌归一之试
本卷总结里,命名空间负责隔离世界,cgroup v2 负责什么?
二
林小源在回顾的过程中,想起了那些他遇到过的存在。
init 童子——那个曾经嘲笑他的 PID 1。现在林小源理解了:init 童子的傲慢源于孤独。PID 1 不能被杀死,不能退出,不能犯错。他的每一个决定都影响整个用户态世界。这种压力,不是常人能承受的。
kthreadd 婶婶——那个沉默的 PID 2。她知道 #ifdef 0 的存在,知道林小源的起源,但她选择沉默。也许她有她的理由——有些真相,知道了反而是一种负担。
shell 小妹——那个活泼的 bash 进程。她不懂内核的深处,但她的每一次命令执行都是一次完整的进程生命周期。她是用户态世界中最常见的存在——平凡,但不可或缺。
日志仙翁——那个神秘的 dmesg 游荡者。他在关键时刻出现,在 dmesg 中留下一条看似无关的消息,引导林小源走向关键的源码。他是林小源的"暗中导师"。
信号量——那个在共享内存路口充当路标的计数器。它不传数据,只控制节奏,是并发世界的守护者。
管道——那条环形的水流通道。它不落盘,不回头,用完即弃。它是 Unix 哲学的化身:简单、优雅、组合性强。
cgroup 那棵资源之树——它不判断公平,只执行规则。管理员定的规则,它一丝不苟地执行。
PSI 三泉——cpu.pressure、memory.pressure、io.pressure。它们不争辩谁更重要,只在 some 与 full 中记下停顿:有些任务被堵住,或者所有非 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 追踪任务级等待时间。这些机制让"容器很慢"不再只是抱怨,而能被拆成可观测的资源因果。
进程间通信有管道(单向字节流)、共享内存(零复制最快)、信号量(纯同步)这几种主要手段。
筑基圆满,方可窥见更深的内核之道。