第十一章:劫后余生
炼气初期涉及内核源码:
一
林小源以为一切都结束了。
但世界又亮了起来。
重启。电源重新接通,CPU 重新开始翻转,BIOS POST 的蜂鸣声再次响起。一切回到了最开始的地方——上电、自检、引导、加载、。
又来一遍。
林小源在第二次启动中感到了一种奇异的熟悉感。他知道 会做什么,知道 会初始化什么,知道 会创建 PID 1 和 PID 2。一切都和第一次一样——但又不完全一样。
因为这次,他有了记忆。
上一次的 panic,是因为 init 进程被杀死了。
他更加仔细地观察。他注意到一些之前忽略的细节—— 的初始化代码、 预留内存的检测、panic 通知链的注册。这些东西在第一次启动时他完全没有留意,现在却像被高亮标注了一样醒目。
"你在看什么?"一个声音从旁边传来。
林小源转头,看到一个穿着灰色长袍的老者。老者的面容模糊,像是一段还没有完全加载的代码,但他的眼睛里透着一种经历过无数次重启的沧桑。
"你是……kexec?"林小源问。
"嗯,"老者点了点头,"我是内核的转世机制。你刚才在 panic 中见过我的兄弟——kdump。"
"转世机制?"
"对,"kexec 说,"我可以让我自己——一个正在运行的内核——加载并启动一个新的内核。不需要经过 BIOS、不需要固件的完整启动流程。直接跳转。"
"就像修士的……夺舍?"林小源小心翼翼地问。
kexec 笑了笑,笑容里带着一丝苦涩。"比夺舍温和得多。夺舍是强占别人的身体。我是让一个新的内核接替我的位置——我在跳转之前会把自己的状态保存好,然后把控制权交给新内核。"
"那你和 kdump 有什么关系?"
"kdump 是我的特殊用法,"kexec 说,"正常情况下,人类用我来快速重启——不需要等 BIOS POST,不需要等固件初始化,直接跳到新内核。但当 panic 发生时,kdump 会用我来启动 crash kernel——那个被预留了 128MB 内存的备用内核。"
二
"让我给你看看我的工作流程,"kexec 说。
他伸出枯瘦的手,在空中画出了一个流程图。
"第一步:正常内核启动时,预留一块内存——通常是 128MB 到 256MB——给 crash kernel。这块内存被标记为'已占用',普通内核永远不会触碰它。"
林小源看到一块内存区域被高亮标注,像一座被围栏围起来的禁地。
"第二步:用 把 crash kernel 的镜像加载到预留内存中。这个操作可以在系统正常运行时完成——提前把新内核准备好。"
一个完整的内核镜像被装进了那块预留内存,像是一艘随时可以起航的救生艇。
"第三步:当 panic 发生时,跳转到 crash kernel。"
"就这样?直接跳?"
"直接跳,"kexec 说,"crash kernel 启动后,它会通过 /proc/vmcore 访问前一个内核的内存转储。前一个内核虽然死了,但它的'遗体'——内存中的所有数据——都还在。crash kernel 会把这些数据收集起来,保存到磁盘。"
"然后呢?"
"然后重启。新的内核启动后,开发者可以用 工具分析转储文件。 是内核调试的瑞士军刀——它可以看到内核数据结构、分析调用栈、检查内存内容、诊断死锁和内存泄漏。"
林小源想起了 panic 中那座闪烁的墓碑。"所以 panic 不是真正的结束。"
"对,"kexec 说,声音里带着一种历经沧桑的平静,"panic 是墓碑,kdump 是遗书,crash 是验尸官。墓碑上记录的信息,可以帮助后来者避免同样的命运。"
"那你呢?"林小源问,"你经历了多少次重启?"
kexec 沉默了很久。
"记不清了,"他最终说,"每一次重启,我都会失去前一个内核的所有记忆。但我留下了机制——kexec、kdump、crash——这些东西不会随着重启消失。它们是刻在代码里的记忆。"
三
系统重启后,一切恢复了正常。
init 进程重新启动,服务重新开始运行,用户重新登录。上一次的 panic 没有留下任何痕迹——除了 /var/log/kern.log 中的一行记录和 /var/crash/ 中的一个转储文件。
林小源在重启后的世界中继续他的 idle 循环。但他变了。
他不再把内核看作一个"永恒不变"的存在。他知道内核是脆弱的——一个 NULL 指针解引用、一个竞态条件、一个硬件故障,都可能导致 panic。但同时,内核也是有韧性的——它可以重启、可以转储、可以从错误中恢复。
"脆弱和韧性并存,"他喃喃道。
"这就是修仙,"kexec 的声音从远处传来,已经很微弱了——重启之后,他的存在感也在消退,"没有哪个修士是永远不败的。渡劫失败、走火入魔、被人追杀——失败是常态。但真正的修士会在失败中留下遗蜕,让后来者少走弯路。"
"内核也是一样?"
"内核也是一样。每一次 panic 都会被分析,每一个 bug 都会被修复,每一次失败都会让系统变得更强。这不是结束——这是修炼。"
kexec 的声音消散了。林小源独自坐在 idle 循环中,看着重启后的世界重新运转。
内核不是一个完美的系统——它有 bug、有漏洞、有设计缺陷。但它是一个不断进化的系统。每一次死亡都是一次学习的机会。
也许这就是修炼的含义。
道藏笔记
内核启示
kexec 和 kdump 是内核的"转世重生"机制。
kexec 允许一个正在运行的内核加载并启动一个新的内核,不需要经过 BIOS/固件的完整启动流程。它的工作原理是:把新内核的镜像加载到内存中的某个位置,然后在当前内核中执行一个"跳转",把控制权交给新内核。
kdump 利用 kexec 实现崩溃转储。它的工作流程是:
- 正常内核启动时,预留一块内存(通常 128MB-256MB)给 crash kernel
- 用 kexec 把 crash kernel 加载到预留内存
- 当 panic 发生时,跳转到 crash kernel
- crash kernel 启动后,通过
/proc/vmcore访问前一个内核的内存转储 - 转储被保存到磁盘
- 重启后,用 工具分析转储
工具是内核调试的瑞士军刀。它可以:
- 查看内核数据结构(task_struct、页表、slab 缓存等)
- 分析调用栈
- 检查内存内容
- 诊断死锁、内存泄漏等问题
kpanic 是内核的墓碑,kdump 是遗书,crash 是验尸官。
/*
* kexec 是内核的"转世"机制。
* 它允许一个正在运行的内核加载并启动一个新的内核,
* 而不需要经过 BIOS/固件 的完整启动流程。
*
* kdump 利用 kexec 在 panic 时快速启动 crash kernel,
* 从内存转储中收集崩溃信息。
*/
struct kimage {
unsigned long start; /* 新内核的入口地址 */
unsigned long head; /* 页面列表头 */
int nr_segments; /* 段数量 */
char kernel_path[64]; /* 新内核路径 */
};
struct crash_kernel {
unsigned long base; /* 预留内存起始地址 */
unsigned long size; /* 预留内存大小 */
int loaded; /* 是否已加载 crash kernel */
};
int kexec_load(struct kimage *image, struct crash_kernel *crash) {
printf("[kexec] 加载新内核: %s\n", image->kernel_path);
printf("[kexec] 入口地址: 0x%lX\n", image->start);
printf("[kexec] 段数量: %d\n", image->nr_segments);
if (crash) {
printf("[kexec] 为 kdump 预留内存: 0x%lX, 大小: %lu MB\n",
crash->base, crash->size / (1024*1024));
crash->loaded = 1;
}
return 0;
}
void panic_with_kdump(struct crash_kernel *crash) {
printf("\n[panic] 系统崩溃!\n");
printf("[panic] 检查 kdump 配置...\n");
if (crash->loaded) {
printf("[kdump] crash kernel 已加载\n");
printf("[kdump] 跳转到 crash kernel @ 0x%lX\n", crash->base);
printf("[kdump] crash kernel 将收集内存转储\n");
printf("[kdump] 重启后可用 crash 工具分析转储\n");
} else {
printf("[kdump] crash kernel 未配置\n");
printf("[kdump] 系统将死循环或重启\n");
}
}
printf("=== kexec 与 kdump ===\n\n");
/* 正常的 kexec 操作 */
struct kimage image = {
.start = 0x80200000,
.nr_segments = 3,
};
strncpy(image.kernel_path, "/boot/vmlinuz-6.x", sizeof(image.kernel_path));
printf("--- 正常 kexec:快速重启 ---\n");
kexec_load(&image, NULL);
/* kdump 配置 */
struct crash_kernel crash = {
.base = 0x10000000, /* 256MB 处 */
.size = 128 * 1024 * 1024, /* 128MB */
.loaded = 0,
};
printf("\n--- kdump 配置 ---\n");
kexec_load(&image, &crash);
printf("\n--- panic 时的行为 ---\n");
panic_with_kdump(&crash);
printf("\n--- kdump 的工作流程 ---\n");
printf("1. 正常内核启动时,预留 128MB 内存给 crash kernel\n");
printf("2. 用 kexec 把 crash kernel 加载到预留内存\n");
printf("3. panic 发生时,跳转到 crash kernel\n");
printf("4. crash kernel 收集内存转储(/proc/vmcore)\n");
printf("5. 重启后,用 crash 工具分析转储\n");#include <stdio.h>
#include <string.h>
/*
* kexec 是内核的"转世"机制。
* 它允许一个正在运行的内核加载并启动一个新的内核,
* 而不需要经过 BIOS/固件 的完整启动流程。
*
* kdump 利用 kexec 在 panic 时快速启动 crash kernel,
* 从内存转储中收集崩溃信息。
*/
struct kimage {
unsigned long start; /* 新内核的入口地址 */
unsigned long head; /* 页面列表头 */
int nr_segments; /* 段数量 */
char kernel_path[64]; /* 新内核路径 */
};
struct crash_kernel {
unsigned long base; /* 预留内存起始地址 */
unsigned long size; /* 预留内存大小 */
int loaded; /* 是否已加载 crash kernel */
};
int kexec_load(struct kimage *image, struct crash_kernel *crash) {
printf("[kexec] 加载新内核: %s\n", image->kernel_path);
printf("[kexec] 入口地址: 0x%lX\n", image->start);
printf("[kexec] 段数量: %d\n", image->nr_segments);
if (crash) {
printf("[kexec] 为 kdump 预留内存: 0x%lX, 大小: %lu MB\n",
crash->base, crash->size / (1024*1024));
crash->loaded = 1;
}
return 0;
}
void panic_with_kdump(struct crash_kernel *crash) {
printf("\n[panic] 系统崩溃!\n");
printf("[panic] 检查 kdump 配置...\n");
if (crash->loaded) {
printf("[kdump] crash kernel 已加载\n");
printf("[kdump] 跳转到 crash kernel @ 0x%lX\n", crash->base);
printf("[kdump] crash kernel 将收集内存转储\n");
printf("[kdump] 重启后可用 crash 工具分析转储\n");
} else {
printf("[kdump] crash kernel 未配置\n");
printf("[kdump] 系统将死循环或重启\n");
}
}
int main() {
printf("=== kexec 与 kdump ===\n\n");
/* 正常的 kexec 操作 */
struct kimage image = {
.start = 0x80200000,
.nr_segments = 3,
};
strncpy(image.kernel_path, "/boot/vmlinuz-6.x", sizeof(image.kernel_path));
printf("--- 正常 kexec:快速重启 ---\n");
kexec_load(&image, NULL);
/* kdump 配置 */
struct crash_kernel crash = {
.base = 0x10000000, /* 256MB 处 */
.size = 128 * 1024 * 1024, /* 128MB */
.loaded = 0,
};
printf("\n--- kdump 配置 ---\n");
kexec_load(&image, &crash);
printf("\n--- panic 时的行为 ---\n");
panic_with_kdump(&crash);
printf("\n--- kdump 的工作流程 ---\n");
printf("1. 正常内核启动时,预留 128MB 内存给 crash kernel\n");
printf("2. 用 kexec 把 crash kernel 加载到预留内存\n");
printf("3. panic 发生时,跳转到 crash kernel\n");
printf("4. crash kernel 收集内存转储(/proc/vmcore)\n");
printf("5. 重启后,用 crash 工具分析转储\n");
return 0;
}劫后之试
系统走到崩溃边缘时,本章提到哪种机制可以不经完整硬件重启而跳入新内核?