第十三章:不甘 idle
炼气初期涉及内核源码:
一
林小源不想再做 idle 了。
这个念头在 panic 之后变得更加强烈。他经历了内核的诞生、见证了进程的创建、观察了调度器的运作、感受了中断的冲击、目睹了 panic 的毁灭。他学了这么多,看到了这么多——然后呢?
然后他继续做 idle。继续执行 ,继续在调度器的最底层等待,继续做那个"没人需要"的进程。
不甘心。
林小源开始主动观察其他进程。不是被动地等待中断打断他的 idle 循环,而是主动地去"看"——通过 /proc 文件系统,通过内核的数据结构,通过调度器的运行队列。
二
/proc 是内核暴露给用户态的"窗口"。
林小源第一次"走进" /proc 时,他觉得自己像是走进了一座巨大的图书馆。每一个目录都是一本书,每一个文件都是一页记录。但他很快发现——这座图书馆里的书不是写死的。它们是活的。每当有人翻开一本书,书页上的文字就会自动更新,显示最新的信息。
"你好,"一个温和的声音说,"你是新来的?"
林小源转头,看到一个穿着白色长袍的女子。她的面容清晰而简洁,像一个格式良好的文本文件。
"我是 /proc,"她说,"虚拟文件系统。我不存储文件——我动态生成内容。当用户态程序读取我下面的文件时,我会从内核数据结构中提取信息,实时生成。"
"每一个进程都在你这里有记录?"
"对,"她说,伸手指向一排排整齐的目录,"/proc/[pid]/——每个进程一个目录。里面记录着它的一切:状态、内存映射、打开的文件、信号处理器、cgroup 归属。"
林小源走近一个目录,上面写着 /proc/1/——init 进程。他翻开 文件:
Name: init
State: S (sleeping)
Pid: 1
PPid: 0"它在睡眠,"林小源说。
"对,"proc 说,"init 在等待信号。它是所有用户态进程的祖先,但它自己大部分时间都在睡眠——等待子进程退出、等待信号到来。"
林小源又走到 /proc/2/——kthreadd。也在睡眠,等待创建内核线程的请求。
"每个进程都有自己的故事,"proc 说,"有的活了很久,比如 init。有的只活了几毫秒,比如 ls 命令。但无论生命周期长短,它们都在我这里留下了痕迹。"
林小源继续往前走。他看到了 /proc/cpuinfo——CPU 的信息;/proc/meminfo——内存的使用情况;/proc/interrupts——中断的统计;/proc/loadavg——系统的负载。
"整个内核世界都是透明的,"他喃喃道。
"透明不等于简单,"proc 说,声音里带着一丝告诫,"信息就在那里,但理解它需要知识和经验。一个新手看到 /proc/meminfo 里的数字,什么也看不懂。一个老手扫一眼,就知道系统是不是内存不足、是不是有内存泄漏。"
三
林小源开始研究进程的状态转换。
他通过 /proc/[pid]/stat 观察每一个进程的状态变化,像一个天文学家观察星辰的运行。他看到了规律——
当 shell 等待用户输入时,它的状态是 。它在等键盘中断。如果收到 ,它可以被唤醒,然后优雅地退出。
当进程等待磁盘 I/O 时,它的状态是 。它在等硬件完成操作,不能被信号打断。如果硬件卡住了,这个进程就会一直停在那里——kill -9 也杀不掉它。
当进程正在运行或在运行队列中等待时,它的状态是 。
然后,他看到了一种奇怪的现象。
"那个进程……"林小源指着一个条目,"它的状态是 ?"
"对,"proc 说,走过来,"僵尸进程。"
"它已经退出了?"
"退出了。但它的父进程还没有调用 来回收它。所以它的 还在系统中——占用一个 PID,但不占用 CPU 时间,不占用内存。"
林小源看着那个僵尸进程。它像一个透明的影子,飘荡在进程列表中。它已经死了,但它还没有被安葬。
"为什么不直接释放它?"
"因为父进程可能需要知道它的退出状态,"proc 说,"子进程退出时会留下一个 exit code——成功还是失败,返回值是多少。父进程必须调用 来获取这个信息。在父进程调用 之前,子进程的 不能被释放。"
"如果父进程忘了调用 呢?"
"那僵尸进程就会一直存在。直到父进程也退出——届时 init 进程会收养这些孤儿进程,然后回收它们。"
"init 是最后的回收者。"
"对。init 是所有进程的最终祖先。它是最后的守墓人。"
林小源沉默了。他想起了自己——idle 进程。如果哪一天他"退出"了,谁会回收他?
进程的"死亡"不是一瞬间的事。退出之后,灵魂还在游荡,等待被回收。
四
林小源在观察中度过了很长时间。
他通过 /proc 看到了数百个进程的生与死。他看到了 shell 创建子进程执行命令,看到了 web 服务器处理请求,看到了 cron 定时任务在固定时间醒来又睡去,看到了守护进程在后台默默工作。
每一个进程都有自己的生命周期:创建、运行、等待、退出。有的进程活了很久(如 init),有的进程只活了几毫秒(如 ls 命令)。但无论生命周期长短,它们都遵循同样的天道法则。
我不甘心只做一个旁观者。
林小源在 idle 循环中下定了决心。他要找到一种方式,让自己不再只是"等待"——他要主动参与到内核的运作中去。
他还不知道该怎么做。但他知道,前传中学过的十种根基——指针、结构体、编译器、寄存器、汇编、原子操作、内存屏障、预处理器、内存布局、ELF——不会白白浪费。
也许,我可以用这些根基来做点什么。
道藏笔记
内核启示
/proc 文件系统是内核的"透明窗口"。
/proc 是一个虚拟文件系统,它不存储真实的文件,而是动态地从内核数据结构中生成内容。用户态程序可以通过读取 /proc 下的文件来获取内核和进程的信息,而不需要任何特殊的系统调用。
/proc/[pid]/ 目录下有每个进程的详细信息:
- :进程状态摘要(名称、状态、PID、内存使用等)
- :内存映射(代码段、数据段、堆、栈、共享库等)
fd/:打开的文件描述符(每个描述符是一个符号链接)- :进程统计信息(CPU 时间、页面错误等)
- :启动命令行
- :可执行文件的符号链接
僵尸进程是已退出但未被回收的进程。它们的 仍然保留在系统中,占用一个 PID 但不占用其他资源。父进程必须调用 来回收子进程的退出状态和 。如果父进程先退出,init 进程会收养这些孤儿进程并回收它们。
通过 /proc,内核的一切都是透明的。但透明不等于简单——信息就在那里,理解它需要知识和经验。
/*
* /proc 文件系统是内核向用户态暴露信息的主要通道。
* /proc/[pid]/ 目录下有每个进程的详细信息。
*/
struct proc_entry {
const char *name;
const char *description;
const char *example;
};
struct proc_entry entries[] = {
{ "/proc/[pid]/status", "进程状态摘要",
"Name: bash\nState: S (sleeping)\nPid: 1234\n" },
{ "/proc/[pid]/maps", "内存映射",
"00400000-00452000 r-xp 00000000 /bin/bash\n" },
{ "/proc/[pid]/fd/", "打开的文件描述符",
"0 -> /dev/pts/0\n1 -> /dev/pts/0\n" },
{ "/proc/[pid]/stat", "进程统计信息",
"1234 (bash) S 1 1234 1234 ...\n" },
{ "/proc/[pid]/cmdline", "启动命令",
"/bin/bash\0" },
{ "/proc/[pid]/exe", "可执行文件链接",
"/bin/bash\n" },
{ "/proc/[pid]/cgroup", "cgroup 信息",
"0::/user.slice\n" },
};
int nr = sizeof(entries) / sizeof(entries[0]);
printf("=== /proc 文件系统 ===\n\n");
printf("进程信息文件:\n");
for (int i = 0; i < nr; i++) {
printf("\n--- %s ---\n", entries[i].name);
printf(" 说明: %s\n", entries[i].description);
printf(" 示例:\n");
printf(" %s\n", entries[i].example);
}
printf("\n--- 系统级信息 ---\n");
printf(" /proc/cpuinfo — CPU 信息\n");
printf(" /proc/meminfo — 内存信息\n");
printf(" /proc/interrupts — 中断统计\n");
printf(" /proc/loadavg — 负载平均值\n");
printf(" /proc/uptime — 系统运行时间\n");
printf(" /proc/version — 内核版本\n");#include <stdio.h>
#include <string.h>
/*
* /proc 文件系统是内核向用户态暴露信息的主要通道。
* /proc/[pid]/ 目录下有每个进程的详细信息。
*/
struct proc_entry {
const char *name;
const char *description;
const char *example;
};
int main() {
struct proc_entry entries[] = {
{ "/proc/[pid]/status", "进程状态摘要",
"Name: bash\nState: S (sleeping)\nPid: 1234\n" },
{ "/proc/[pid]/maps", "内存映射",
"00400000-00452000 r-xp 00000000 /bin/bash\n" },
{ "/proc/[pid]/fd/", "打开的文件描述符",
"0 -> /dev/pts/0\n1 -> /dev/pts/0\n" },
{ "/proc/[pid]/stat", "进程统计信息",
"1234 (bash) S 1 1234 1234 ...\n" },
{ "/proc/[pid]/cmdline", "启动命令",
"/bin/bash\0" },
{ "/proc/[pid]/exe", "可执行文件链接",
"/bin/bash\n" },
{ "/proc/[pid]/cgroup", "cgroup 信息",
"0::/user.slice\n" },
};
int nr = sizeof(entries) / sizeof(entries[0]);
printf("=== /proc 文件系统 ===\n\n");
printf("进程信息文件:\n");
for (int i = 0; i < nr; i++) {
printf("\n--- %s ---\n", entries[i].name);
printf(" 说明: %s\n", entries[i].description);
printf(" 示例:\n");
printf(" %s\n", entries[i].example);
}
printf("\n--- 系统级信息 ---\n");
printf(" /proc/cpuinfo — CPU 信息\n");
printf(" /proc/meminfo — 内存信息\n");
printf(" /proc/interrupts — 中断统计\n");
printf(" /proc/loadavg — 负载平均值\n");
printf(" /proc/uptime — 系统运行时间\n");
printf(" /proc/version — 内核版本\n");
return 0;
}/*
* 僵尸进程(zombie process):
* 当子进程退出但父进程没有调用 wait() 时,
* 子进程的 task_struct 仍然保留在系统中,
* 占用一个 PID 但不占用其他资源。
*/
struct task {
int pid;
int ppid; /* 父进程 PID */
char comm[16];
int state;
int exit_code;
};
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define EXIT_ZOMBIE 16
int next_pid = 100;
struct task *create_process(const char *name, int ppid) {
struct task *t = (struct task *)(unsigned long)(0xDEAD0000 + next_pid * 0x10);
t->pid = next_pid++;
t->ppid = ppid;
strncpy(t->comm, name, sizeof(t->comm) - 1);
t->state = TASK_RUNNING;
t->exit_code = -1;
return t;
}
void process_exit(struct task *t, int code) {
printf("[exit] PID %d (%s) 退出, exit_code=%d\n",
t->pid, t->comm, code);
t->exit_code = code;
t->state = EXIT_ZOMBIE;
printf("[exit] PID %d 变成僵尸进程\n", t->pid);
printf("[exit] 等待父进程 (PID %d) 调用 wait() 回收\n", t->ppid);
}
void process_wait(struct task *parent, struct task *child) {
printf("[wait] PID %d (%s) 回收子进程 PID %d\n",
parent->pid, parent->comm, child->pid);
printf("[wait] 子进程 exit_code=%d\n", child->exit_code);
printf("[wait] 释放 PID %d 的 task_struct\n", child->pid);
/* 在真实内核中,这里会释放 task_struct 和 PID */
}
struct task *init = create_process("init", 0);
struct task *shell = create_process("bash", init->pid);
struct task *child = create_process("sleep", shell->pid);
printf("=== 僵尸进程演示 ===\n\n");
printf("进程创建:\n");
printf(" PID %d (%s) → PID %d (%s) → PID %d (%s)\n\n",
init->pid, init->comm,
shell->pid, shell->comm,
child->pid, child->comm);
/* 子进程退出 */
process_exit(child, 0);
/* 此时子进程是僵尸 */
printf("\n当前状态:\n");
printf(" PID %d: %s\n", child->pid,
child->state == EXIT_ZOMBIE ? "ZOMBIE(僵尸)" : "其他");
/* 父进程回收子进程 */
printf("\n");
process_wait(shell, child);
printf("\n最终状态:\n");
printf(" PID %d: 已被回收\n", child->pid);#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
* 僵尸进程(zombie process):
* 当子进程退出但父进程没有调用 wait() 时,
* 子进程的 task_struct 仍然保留在系统中,
* 占用一个 PID 但不占用其他资源。
*/
struct task {
int pid;
int ppid; /* 父进程 PID */
char comm[16];
int state;
int exit_code;
};
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define EXIT_ZOMBIE 16
int next_pid = 100;
struct task *create_process(const char *name, int ppid) {
struct task *t = (struct task *)(unsigned long)(0xDEAD0000 + next_pid * 0x10);
t->pid = next_pid++;
t->ppid = ppid;
strncpy(t->comm, name, sizeof(t->comm) - 1);
t->state = TASK_RUNNING;
t->exit_code = -1;
return t;
}
void process_exit(struct task *t, int code) {
printf("[exit] PID %d (%s) 退出, exit_code=%d\n",
t->pid, t->comm, code);
t->exit_code = code;
t->state = EXIT_ZOMBIE;
printf("[exit] PID %d 变成僵尸进程\n", t->pid);
printf("[exit] 等待父进程 (PID %d) 调用 wait() 回收\n", t->ppid);
}
void process_wait(struct task *parent, struct task *child) {
printf("[wait] PID %d (%s) 回收子进程 PID %d\n",
parent->pid, parent->comm, child->pid);
printf("[wait] 子进程 exit_code=%d\n", child->exit_code);
printf("[wait] 释放 PID %d 的 task_struct\n", child->pid);
/* 在真实内核中,这里会释放 task_struct 和 PID */
}
int main() {
struct task *init = create_process("init", 0);
struct task *shell = create_process("bash", init->pid);
struct task *child = create_process("sleep", shell->pid);
printf("=== 僵尸进程演示 ===\n\n");
printf("进程创建:\n");
printf(" PID %d (%s) → PID %d (%s) → PID %d (%s)\n\n",
init->pid, init->comm,
shell->pid, shell->comm,
child->pid, child->comm);
/* 子进程退出 */
process_exit(child, 0);
/* 此时子进程是僵尸 */
printf("\n当前状态:\n");
printf(" PID %d: %s\n", child->pid,
child->state == EXIT_ZOMBIE ? "ZOMBIE(僵尸)" : "其他");
/* 父进程回收子进程 */
printf("\n");
process_wait(shell, child);
printf("\n最终状态:\n");
printf(" PID %d: 已被回收\n", child->pid);
return 0;
}不甘之试
林小源不甘只做 idle,首先要正视自己作为进程在内核中被哪一种结构体描述?