Skip to content

第十一章:劫后余生

炼气初期

涉及内核源码:

林小源以为一切都结束了。

但世界又亮了起来。

重启。电源重新接通,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 实现崩溃转储。它的工作流程是:

  1. 正常内核启动时,预留一块内存(通常 128MB-256MB)给 crash kernel
  2. 用 kexec 把 crash kernel 加载到预留内存
  3. 当 panic 发生时,跳转到 crash kernel
  4. crash kernel 启动后,通过 /proc/vmcore 访问前一个内核的内存转储
  5. 转储被保存到磁盘
  6. 重启后,用 工具分析转储

工具是内核调试的瑞士军刀。它可以:

  • 查看内核数据结构(task_struct、页表、slab 缓存等)
  • 分析调用栈
  • 检查内存内容
  • 诊断死锁、内存泄漏等问题

kpanic 是内核的墓碑,kdump 是遗书,crash 是验尸官。


c
/*
 * 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");

破关试炼

劫后之试

系统走到崩溃边缘时,本章提到哪种机制可以不经完整硬件重启而跳入新内核?

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

以修仙之名,悟内核之道