4. kdump原理与架构

kdump 是 Linux 内核最核心的崩溃转储机制,其设计哲学是 “用另一个内核来抢救当前内核的尸体”。当生产内核(Production Kernel)崩溃时,kdump 通过 kexec 快速启动一个独立的捕获内核(Capture Kernel),在捕获内核中安全地导出崩溃现场的内存快照(vmcore),供后续离线分析。本章深入剖析 kdump 的三层核心:kexec 跳板机制、捕获内核的加载与运行、vmcore 文件的完整生成链路。

核心观点: kdump 不是“调试器”,而是一个基于内核切换的故障隔离容器。生产内核崩溃后,系统不依赖 BIOS/引导加载器,而是由 kexec 直接覆盖内存中的内核映像,在保留崩溃内存的前提下启动一个最小化的捕获内核。

4.1 kexec 机制:从崩溃到新内核的“热跳转”

kexec 是 kdump 的底层引擎,它允许一个内核在运行时直接加载并跳转到另一个内核,跳过固件和引导加载器阶段。在 kdump 场景中,kexec 承担两个关键角色:预加载捕获内核 和 崩溃时触发跳转。

4.1.1 kexec 的两阶段工作流

阶段 触发时机 执行动作 关键系统调用
加载阶段 系统正常运行时(开机后) 将捕获内核 vmlinux、initrd、dtb 等加载到预留内存区域,并注册崩溃通知链 kexec_load() / kexec_file_load()
跳转阶段 生产内核 panic / hard lockup 时 保存当前 CPU 寄存器、关闭中断、重置设备,然后跳转到捕获内核入口点 machine_kexec() → relocate_kernel()
性能提示: 加载阶段仅在系统启动时执行一次,对运行时性能无影响。跳转阶段耗时通常在 50ms~200ms 之间,远快于完整重启(秒级)。

4.1.2 kexec 跳转的底层路径

当生产内核触发 panic() 后,kdump 注册的 crash_kexec() 回调被调用,执行以下关键步骤:

  1. 冻结其他 CPU:通过 smp_send_stop() 或 NMI IPI 停止所有非引导 CPU。
  2. 保存崩溃 CPU 上下文:将当前 CPU 的通用寄存器、堆栈指针、程序计数器保存到 elfcorehdr 区域。
  3. 设备静默:调用 crash_shutdown_handlers 通知各驱动停止 DMA、屏蔽中断。
  4. 执行机器级跳转:machine_kexec() 将控制权交给 relocate_kernel 汇编例程,该例程负责页表切换、缓存刷新,最终跳转到捕获内核的入口。
kexec 跳转流程:从生产内核到捕获内核 kexec 跳转流程:生产内核 → 捕获内核 生产内核运行 panic() / crash_kexec() 冻结其他CPU 保存寄存器/堆栈 设备静默 停止DMA/中断 machine_kexec() relocate_kernel 汇编 捕获内核启动 保留内存中运行 关键特性 跳过BIOS/引导 保留崩溃内存 50~200ms完成跳转

4.2 捕获内核(Capture Kernel)的加载与运行环境

捕获内核是一个经过精简的 Linux 内核,它运行在系统启动时预留的一块物理内存区域中,与生产内核完全隔离。捕获内核的唯一使命:将崩溃内存导出为 vmcore 文件。

4.2.1 预留内存与捕获内核的加载

在系统启动时,通过内核参数 crashkernel= 预留一段物理内存。这段内存对生产内核不可见(被 memblock 保留),但 kexec 加载时会将捕获内核的 vmlinux 和 initrd 放入其中。

参数示例 预留大小 适用场景
crashkernel=256M 固定 256MB 小内存系统(<4GB)
crashkernel=512M,high 512MB,从 4GB 以上地址分配 大内存系统(>=4GB)
crashkernel=1G,low 1GB,从低端内存分配 需要大量捕获内核内存的场景
常见陷阱: 预留内存过小会导致捕获内核 OOM,无法完成 vmcore 压缩或写入;预留内存过大则浪费生产内核可用内存。建议按 每 1GB 生产内存预留 64MB~128MB 估算,最小不低于 256MB。

4.2.2 捕获内核的启动与初始化

捕获内核启动时,会执行以下特殊初始化流程:

  1. 识别崩溃内存区域:通过 elfcorehdr 内核参数或设备树节点,获取生产内核的内存布局和崩溃上下文。
  2. 挂载最小根文件系统:initrd 中包含 makedumpfile、cp、ssh 等工具,以及网络/存储驱动。
  3. 执行捕获脚本:initrd 中的 /init 或 systemd 单元调用 /etc/kdump.conf 中配置的转储命令。
  4. 导出 vmcore:将生产内核的内存从 /proc/vmcore 读取并写入目标路径(本地磁盘、NFS、SSH 等)。
  5. 重启或关机:转储完成后,捕获内核执行 reboot 或 poweroff。
捕获内核内存布局:预留区域与崩溃内存 捕获内核内存布局:预留区域与崩溃内存 系统物理内存(4GB 示例) 生产内核内存(3.5GB) 预留内存 crashkernel=512M 捕获内核 initrd elf 生产内核无法访问预留内存;捕获内核只能访问预留内存 + 通过 /proc/vmcore 只读访问崩溃内存

4.3 vmcore 文件生成流程:从 /proc/vmcore 到持久化存储

vmcore 的生成是 kdump 的核心产出环节。捕获内核启动后,通过 /proc/vmcore 虚拟文件以 ELF 格式暴露崩溃内存,然后由用户态工具完成过滤、压缩和写入。

4.3.1 /proc/vmcore 的 ELF 格式

/proc/vmcore 是一个 ELF 格式的虚拟文件,其结构如下:

  • ELF 头部:标识为 ET_CORE 类型,包含程序头表(Program Header Table)偏移。
  • 程序头表:每个 PT_LOAD 段描述一段连续物理内存的起始地址、大小和文件偏移。
  • 内存数据:按程序头表描述的顺序,依次存放各段内存的原始内容。
  • NT_PRSTATUS 备注段:包含每个 CPU 的寄存器状态(struct elf_prstatus)。
关键数据结构: 内核在 fs/proc/vmcore.c 中通过 read_from_oldmem() 函数从崩溃内存中读取数据,并映射为 ELF 格式。用户态读取 /proc/vmcore 时,内核按需从预留的 elfcorehdr 区域获取程序头信息,然后从崩溃内存中拷贝数据。

4.3.2 makedumpfile 过滤与压缩

直接导出完整的 /proc/vmcore 会包含大量无用页(如全零页、用户态缓存页),导致文件过大。makedumpfile 工具提供以下过滤能力:

过滤级别 过滤内容 典型压缩比
-d 0 不过滤,导出全部内存 1:1(原始大小)
-d 1 过滤零页(全零页面) 约 1:0.7
-d 31 过滤零页、缓存页、用户态页、未使用页等 约 1:0.1~1:0.3

makedumpfile 还支持 压缩算法(zlib/lzo/snappy)和 分片输出(--split),进一步减小存储压力。

4.3.3 完整 vmcore 生成流程

  1. 捕获内核启动:initrd 中的 kdump-init 脚本挂载目标文件系统(本地磁盘、NFS 或 SSH 目标)。
  2. 调用 makedumpfile:执行类似 makedumpfile -d 31 -c -l /proc/vmcore /mnt/vmcore.$(date +%s).dump 的命令。
  3. 读取 /proc/vmcore:makedumpfile 通过 pread() 系统调用读取 ELF 头部和程序头表,然后按段读取内存数据。
  4. 过滤与压缩:对每个页面,检查是否满足过滤条件;若保留,则使用指定压缩算法压缩后写入输出文件。
  5. 写入存储:输出文件写入本地磁盘、NFS 或通过 SSH 发送到远程服务器。
  6. 清理与重启:写入完成后,卸载文件系统,执行 reboot -f 重启系统。
vmcore 生成完整流程 vmcore 生成完整流程 崩溃内存 物理内存 0x0~0xFFFF /proc/vmcore ELF 格式虚拟文件 makedumpfile 过滤 + 压缩 vmcore.dump 本地 / NFS / SSH 持久化存储 磁盘 / NAS / 远程 过滤级别示例 -d 0 : 不过滤 -d 1 : 过滤零页 -d 31: 激进过滤

4.4 关键代码路径与数据结构

以下代码展示了 kdump 核心路径中的关键函数调用关系:

// 生产内核 panic 路径
panic()
  → __crash_kexec()
    → machine_crash_shutdown()    // 冻结其他CPU,设备静默
    → crash_save_cpu()            // 保存当前CPU寄存器到elfcorehdr
    → machine_kexec()             // 架构相关跳转
      → relocate_kernel()         // 汇编例程,切换页表,跳转到捕获内核

// 捕获内核 /proc/vmcore 读取路径
read_from_oldmem()                // 从崩溃内存读取数据
  → ioremap()                     // 映射物理内存到虚拟地址
  → memcpy()                      // 拷贝数据到用户缓冲区
  → elfcorehdr_read()             // 读取程序头表信息

// 用户态 makedumpfile 核心循环
for each page in /proc/vmcore:
    if is_zero_page(page):
        skip
    if is_user_page(page) and dump_level & DUMP_EXCLUDE_USER:
        skip
    compress(page, output_stream)
    write(output_stream, compressed_data)
调试技巧: 如果 vmcore 生成失败,首先检查 /proc/vmcore 是否存在且可读(ls -l /proc/vmcore),然后查看捕获内核的 dmesg 输出,重点关注 makedumpfile 的退出码和错误信息。

4.5 本章小结

kdump 的核心原理可以概括为三个层次:

  • kexec 机制:提供从崩溃内核到捕获内核的快速跳转,跳过固件和引导加载器,保留崩溃现场。
  • 捕获内核:运行在预留内存中的最小化内核,通过 /proc/vmcore 以 ELF 格式暴露崩溃内存。
  • vmcore 生成:makedumpfile 工具读取 /proc/vmproc,执行过滤、压缩后写入持久化存储。