4. kdump原理与架构:kexec机制、捕获内核与vmcore生成流程
kdump 是 Linux 内核最核心的崩溃转储机制,其设计哲学是 “用另一个内核来抢救当前内核的尸体”。当生产内核(Production Kernel)崩溃时,kdump 通过 kexec 快速启动一个独立的捕获内核(Capture Kernel),在捕获内核中安全地导出崩溃现场的内存快照(vmcore),供后续离线分析。本章深入剖析 kdump 的三层核心:kexec 跳板机制、捕获内核的加载与运行、vmcore 文件的完整生成链路。
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() |
4.1.2 kexec 跳转的底层路径
当生产内核触发 panic() 后,kdump 注册的 crash_kexec() 回调被调用,执行以下关键步骤:
- 冻结其他 CPU:通过
smp_send_stop()或 NMI IPI 停止所有非引导 CPU。 - 保存崩溃 CPU 上下文:将当前 CPU 的通用寄存器、堆栈指针、程序计数器保存到
elfcorehdr区域。 - 设备静默:调用
crash_shutdown_handlers通知各驱动停止 DMA、屏蔽中断。 - 执行机器级跳转:
machine_kexec()将控制权交给relocate_kernel汇编例程,该例程负责页表切换、缓存刷新,最终跳转到捕获内核的入口。
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,从低端内存分配 | 需要大量捕获内核内存的场景 |
4.2.2 捕获内核的启动与初始化
捕获内核启动时,会执行以下特殊初始化流程:
- 识别崩溃内存区域:通过
elfcorehdr内核参数或设备树节点,获取生产内核的内存布局和崩溃上下文。 - 挂载最小根文件系统:initrd 中包含
makedumpfile、cp、ssh等工具,以及网络/存储驱动。 - 执行捕获脚本:initrd 中的
/init或 systemd 单元调用/etc/kdump.conf中配置的转储命令。 - 导出 vmcore:将生产内核的内存从
/proc/vmcore读取并写入目标路径(本地磁盘、NFS、SSH 等)。 - 重启或关机:转储完成后,捕获内核执行
reboot或poweroff。
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 生成流程
- 捕获内核启动:initrd 中的
kdump-init脚本挂载目标文件系统(本地磁盘、NFS 或 SSH 目标)。 - 调用 makedumpfile:执行类似
makedumpfile -d 31 -c -l /proc/vmcore /mnt/vmcore.$(date +%s).dump的命令。 - 读取 /proc/vmcore:makedumpfile 通过
pread()系统调用读取 ELF 头部和程序头表,然后按段读取内存数据。 - 过滤与压缩:对每个页面,检查是否满足过滤条件;若保留,则使用指定压缩算法压缩后写入输出文件。
- 写入存储:输出文件写入本地磁盘、NFS 或通过 SSH 发送到远程服务器。
- 清理与重启:写入完成后,卸载文件系统,执行
reboot -f重启系统。
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)
/proc/vmcore 是否存在且可读(ls -l /proc/vmcore),然后查看捕获内核的 dmesg 输出,重点关注 makedumpfile 的退出码和错误信息。
4.5 本章小结
kdump 的核心原理可以概括为三个层次:
- kexec 机制:提供从崩溃内核到捕获内核的快速跳转,跳过固件和引导加载器,保留崩溃现场。
- 捕获内核:运行在预留内存中的最小化内核,通过
/proc/vmcore以 ELF 格式暴露崩溃内存。 - vmcore 生成:makedumpfile 工具读取
/proc/vmproc,执行过滤、压缩后写入持久化存储。