引言
在云计算与容器化生产环境中,系统停机时间(downtime)直接等同于金钱损失。传统冷启动需要经历 BIOS/UEFI 自检(10-30秒)、Bootloader 加载(3-5秒)、内核初始化(5-10秒)和服务恢复(10-60秒),总计超过 30 秒。Linux 内核的 kexec 机制通过"热替换"新内核直接跳过了固件自检和 Bootloader 阶段,将重启时间压缩到 3-8 秒。同时,kdump 机制在系统崩溃时通过备用内核将内存转储到磁盘,是诊断内核恐慌(panic)和驱动程序致命错误的关键基础设施。
本文从内核源码(kernel/kexec.c、kernel/kexec_core.c、fs/proc/vmcore.c、drivers/pci/pci-kexec.c)出发,完整拆解 kexec 加载新内核的三阶段流程(kexec_load 入口 → 镜像段复制 → 控制转移到新 startup_64)、kdump 触发转储的崩溃内核路径、/proc/vmcore ELF 转储格式解析、PCI 设备在 kexec 中的状态保存与恢复,以及生产环境中的 makedumpfile 压缩与过滤优化。
一、kexec 机制:跳过固件的内核热替换
1.1 传统重启 vs kexec 快速重启
传统 x86 重启路径(每次都需要完整的固件初始化):
传统重启路径:
shutdown() → kernel_restart()
→ machine_restart()
→ EFI ResetSystem(Runtime) (或 ACPI reset_reg)
→ 固件 POST(内存检测、PCIe 链路训练)→ 10-30s
→ Bootloader(GRUB/systemd-boot)→ 3-5s
→ startup_64() 内核入口 → 5-10s
→ init/systemd 初始化 → 10-60s
→ 服务就绪
kexec 快速重启路径:
kexec -l /boot/vmlinuz-new --initrd=... --append="root=..."
kexec -e
→ sys_kexec_load() 已在之前完成(内核已加载到保留内存)
→ kernel_restart_prepare()
→ 关闭非启动 CPU
→ migrate_to_reboot_cpu()
→ machine_shutdown()
→ 跳转到新内核的 startup_64()
→ 内核初始化 → 5-10s(仍需要内核自身初始化)
→ init/systemd 初始化 → 10-60s
→ 服务就绪
kexec 节省了固件 POST 和 Bootloader 共约 15-35 秒的时间。在 KVM 虚拟机中效果更明显(虚拟固件初始化更快),总重启时间可控制在 3-5 秒。
1.2 kexec_load 系统调用详解
核心数据结构与系统调用入口:
// 用户空间调用接口(kexec-tools 中的 kexec 命令)
// kexec -l vmlinuz --initrd=initrd.img --append="root=/dev/sda1 console=ttyS0"
// → 展开为:
// syscall(__NR_kexec_load, entry, nr_segments, segments, flags);
// 内核处理:kernel/kexec.c
SYSCALL_DEFINE4(kexec_load, unsigned long, entry, unsigned long, nr_segments,
struct kexec_segment __user *, segments, unsigned long, flags)
{
struct kexec_segment *kimage_segments;
struct kimage *image;
// ... 参数校验、控制标志检查
// 1. 分配 kimage 控制结构
image = kimage_alloc_init(flags, entry, nr_segments, segments);
// 2. 预留内存标记(确保新内核不会覆盖当前内核的运行区域)
image->head = 0;
kimage_load_segments(image); // 将各段复制到保留内存
// 3. 设置保留内存通知器(当使用 kdump 时)
if (flags & KEXEC_ON_CRASH)
crash_load_segments(image);
return 0;
}
1.3 kimage 镜像结构
kexec 加载的新内核由多个段(segment)组成,每个段描述一段需要复制到保留内存的数据:
struct kimage {
kimage_entry_t head; // 段链表头
struct page *head_page; // 控制页(新内核读取控制信息)
struct page *last_entry_page; // 最后一项控制页
unsigned long start; // 新内核入口地址
struct page *control_code_page; // 控制代码页(关机到新内核的跳转代码)
struct page *swap_page; // 交换页(kdump 临时使用)
// 目标架构上下文
struct kexec_segment segment[KEXEC_SEGMENT_MAX]; // 段数组(通常 8 个)
unsigned int nr_segments; // 段数量
// 控制标志
unsigned int type:1; // KEXEC_TYPE_DEFAULT 或 KEXEC_TYPE_CRASH
unsigned int preserve_context:1; // 保留 CPU 上下文(kdump 模式)
// Per-segment 信息
struct {
unsigned char *buf; // 用户空间缓冲区内容
size_t bufsz; // 缓冲区大小
unsigned long mem; // 目标物理地址
size_t memsz; // 目标内存大小
} segment[KEXEC_SEGMENT_MAX];
};
一个典型的 kexec Linux 镜像包含以下段:
| 段索引 | 内容 | 对齐要求 | 说明 |
|---|---|---|---|
| [0] | Kernel binary (vmlinuz) | 64KB(x86) | 压缩内核镜像(bzImage 或 ELF) |
| [1] | Initramfs image | 4KB | 初始 ramdisk(根文件系统) |
| [2] | Kernel command line | 256B | cmdline 参数(root, console 等) |
| [3] | ACPI tables (可选) | 16B | kdump 需要传递原 ACPI 表 |
| [4] | device tree (ARM) | 4KB | ARM64 设备树二进制 |
二、kexec 执行:从关机到新内核
2.1 kexec -e 的系统调用链
执行 kexec -e 后进入内核的路径:
int sys_reboot(int magic1, int magic2, unsigned int cmd, void __user *arg)
{
...
case LINUX_REBOOT_CMD_KEXEC:
kernel_restart_prepare(NULL); // 步骤1: 通知关机
migrate_to_reboot_cpu(); // 步骤2: 迁移到 CPU0
syscore_shutdown(); // 步骤3: 关闭核心设备
machine_kexec(image); // 步骤4: 架构特定跳转
break;
}
void machine_kexec(struct kimage *image)
{
// x86 实现 (arch/x86/kernel/machine_kexec_64.c)
unsigned long page_list_address = 1; // 控制页物理地址
// 1. 禁用本地中断
local_irq_disable();
// 2. 关闭非启动 CPU(其他 CPU 已进入 idle)
disable_IO_APIC();
// 3. 加载新内核的 GDT/IDT
set_gdt(phys_to_virt(image->start), 0);
// 4. 关键跳转:写入控制页后跳转到新内核
// 使用 real_mode_header 的jmp 到新内核入口
control_page = phys_to_virt(image->control_code_page->index);
memcpy(control_page, kexec_control_code, control_code_size);
// 最终跳转(通过 indirect call 到新内核入口)
asm volatile("movq %0, %%rsp\n\t" : : "m"(image->start));
(*jump)(entry, image->head, page_list_address, identity_page,
boot_params_phys);
}
2.2 控制代码页(Control Code Page)
控制代码页是 x86 kexec 的关键枢纽——它包含了 CPU0 从旧内核跳转到新内核的汇编代码。这段代码必须位于恒等映射(identity-mapped)的内存区域,因为在跳转时新内核尚未建立分页机制。
// 控制代码页中的关键汇编流程(简化的 x86-64 伪代码):
// arch/x86/kernel/relocate_kernel_64.S
entry:
// 1. 保存 cr3(新内核稍后需要恢复)
movq %cr3, %rax
// 2. 建立临时 GDT(从新内核镜像中获取)
lgdt (new_gdt_desc)
// 3. 切换到新内核的页表(已设置在控制页中)
movq new_cr3, %cr3
// 4. 设置段寄存器为内核默认值
movq $0x10, %rax
movq %rax, %ds
movq %rax, %es
movq %rax, %fs
movq %rax, %gs
movq %rax, %ss
// 5. 跳转到新内核入口(startup_64 或 efi_stub_entry)
movq entry_point, %rax
jmpq *%rax
三、kdump 崩溃转储内核
3.1 kdump 工作原理
kdump 使用 kexec 在系统崩溃时引导一个"捕获内核"(capture kernel),将崩溃时的内存完整转储到磁盘:
┌─────────────────────────────────────────────────────────┐
│ 正常系统运行 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 主内核 (production kernel) │ │
│ │ ├─ 所有用户态进程 │ │
│ │ ├─ 所有设备驱动 │ │
│ │ └─ 全部物理内存(0 → max_pfn) │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ 保留内存区域 (crashkernel=512M) │
│ │ ┌───────────────────┐ │ │
│ │ │ 捕获内核 ( Capture )│ │ │
│ │ │ 捕获 initramfs │ │ │
│ │ └───────────────────┘ │ │
│ └────────────────────────┘ │
│ │ │
│ 系统崩溃 (panic) │
│ │ │
│ ▼ │
│ crash_kexec() 在 panic() 中调用 │
│ │ │
│ kexec -e 自动触发 │
│ │ │
│ ▼ │
│┌─────────────────────────────────────────────────────────┐
│ 捕获内核启动 │
│ ├─ 读取 /proc/vmcore (ELF 格式内存映像) │
│ ├─ makedumpfile 过滤 + 压缩 │
│ └─ 写入磁盘: /var/crash/<timestamp>/vmcore │
└─────────────────────────────────────────────────────────┘
3.2 启动参数预留保留内存
kdump 需要在主内核启动时预留一块物理内存给捕获内核使用,通过 kernel command line 的 crashkernel= 参数配置:
# /etc/default/grub 中的配置示例
GRUB_CMDLINE_LINUX="crashkernel=512M,high crashkernel=256M,low quiet"
# 参数说明:
# crashkernel=512M,high → 在高端内存 (>4G) 预留 512M
# crashkernel=256M,low → 在低端内存 (<4G) 预留 256M(实模式和 DMA 需要)
# 也可以自动分配:crashkernel=auto(kernel 5.4+)
# 验证预留结果
dmesg | grep -i "Reserving"
# [ 0.000000] Reserving 512MB of memory at 1008MB for crashkernel (System RAM: 16384MB)
# 检查 kdump 服务
systemctl status kdump
kdumpctl status
保留内存的分配在内核启动早期完成:
// arch/x86/kernel/setup_arch.c
static unsigned long __init get_total_mem_size(void)
void __init reserve_crashkernel(void)
{
// 解析 crashkernel= 参数
ret = parse_crashkernel(boot_command_line, total_low_mem,
&crash_size, &crash_base, suffix);
// 分配低端内存(必须低于 4GB)
ret = memblock_phys_alloc_range(crash_size, CRASH_ALIGN, 0, crash_base_max);
// 插桩保留内存区域标记为 E820_TYPE_RESERVED
e820__range_add(crash_base, crash_size, E820_TYPE_RESERVED);
}
3.3 panic() 到 crash_kexec 的触发路径
当内核触发 panic() 时,自动执行 kdump 转储的路径:
// kernel/panic.c
void panic(const char *fmt, ...)
{
// 1. 禁用本地中断、抢占
local_irq_disable();
// 2. 通知 panic 通知链(允许外部模块记录)
atomic_notifier_call_chain(&panic_notifier_list, 0, buf);
// 3. 打印 panic 信息和堆栈追踪
panic_print_sys_info();
dump_stack();
// 4. 触发 kdump(如果已配置)
if (crash_kexec_post_notifiers)
__crash_kexec(NULL); // 先执行通知器
else
__crash_kexec(NULL); // 直接转储
// 5. 最终 fallback:如果 kdump 也失败
emergency_restart();
}
void __crash_kexec(struct pt_regs *regs)
{
// 仅 kexec_load 时的 KEXEC_ON_CRASH 标志才会触发
// 调用 machine_crash_shutdown():
// - 关闭本地 APIC
// - 禁用 NMI watchdog
// - 保存当前 CPU 寄存器到 crash_notes(ELF PRSTATUS)
// - 向其他 CPU 发送 NMI 强制其停止
crash_save_vmcoreinfo(); // 保存 VM 核心调试信息
machine_kexec_prepare(image); // 准备 kexec 跳转
machine_kexec(image); // 执行 kexec 到捕获内核
}
3.4 crash_notes:崩溃前 CPU 状态保存
每个 CPU 在崩溃时刻的寄存器状态都被保存到一个名为 crash_notes 的特殊内存区域。这些状态以 ELF PRNOTE 格式组织,是 vmcore 的核心调试数据之一。
// crash_notes 的结构定义(每个 CPU 一个 note)
// 路径:include/linux/kexec.h
#define KEXEC_NOTE_NAME "CORE"
#define KEXEC_NOTE_MAGIC 0x80000000 // ELF NOTE 标记
struct kexec_renotes {
struct elf64_prstatus prstatus; // 核心处理器状态
// 包含: r15,r14,r13,r12,rbp,rbx,r11,r10,r9,r8,rax,rcx,rdx,rsi,rdi
// orig_rax,rip,cs,eflags,rsp,ss,fs_base,gs_base,ds,es,fs,gs
};
// crash_notes 在物理内存中的布局:
// CPU0_prstatus | CPU1_prstatus | ... | CPU{n}_prstatus
// 每个 note 大小约 1KB,总计 n*1KB
// 通过 /proc/vmcore 的 ELF program headers 可以找到它们
四、/proc/vmcore ELF 格式与转储
4.1 vmcore ELF 结构
捕获内核启动后,崩溃时的完整内存被暴露为 /proc/vmcore —— 一个 ELF64 Core 文件。其内部结构:
/proc/vmcore (ELF64 Core File)
├── ELF Header (64 bytes)
│ ├── e_ident: 0x7f 'E' 'L' 'F' (ELFCLASS64) ELFDATA2LSB
│ ├── e_type: ET_CORE (Core file)
│ ├── e_machine: EM_X86_64 (62)
│ └── e_phoff: 程序头表偏移
│
├── Program Headers(PT_LOAD:物理内存区域)
│ ├── [PT_LOAD] paddr: 0x0, vaddr: 0x0, filesz: 0x1000 ← 第一个物理页
│ ├── [PT_LOAD] paddr: 0x1000, vaddr: 0x1000, filesz: 0x1000
│ ├── ...
│ ├── [PT_LOAD] paddr: 0x100000, filesz: 0x3FE00000 ← 主内存块 0-1GB
│ ├── ...
│ └── [PT_LOAD] paddr: 0x140000000, filesz: 0x280000000 ← 16GB-26GB 区域
│
├── Program Headers(PT_NOTE:调试信息段)
│ ├── [PT_NOTE] crash_notes(所有 CPU 的 PRSTATUS)
│ ├── [PT_NOTE] vmcoreinfo(内核版本、页大小、mem_map 地址等)
│ └── [PT_NOTE] other notes...
│
└── Data segments
└── 原始内存数据(页粒度的 direct-map)
查看 vmcore 结构的命令:
# 查看 ELF 头
readelf -h /proc/vmcore
# 输出:Type: CORE, Machine: Advanced Micro Devices X86-64
# 查看程序头(内存段与 note 段)
readelf -l /proc/vmcore
# 输出:每个 PT_LOAD 的物理地址范围 + PT_NOTE 列表
# 查看 vmcoreinfo(内核构建信息)
readelf -n /proc/vmcore | head -100
# 输出:OSRELEASE=5.15.0-91-generic
# PAGESIZE=4096
# SYMBOL(swapper_pg_dir)=...
# ...
# 查看可用内存区域列表
makedumpfile -f --mem-usage /proc/vmcore
# Page type Physical page address
# Exclude pages 0x00000000 ~ 0x00000001 (零页)
# zero pages 0x00001000 ~ 0x00001000 (free page)
4.2 vmcoreinfo:调试器的坐标系统
vmcoreinfo 是一个特殊段,记录了调试器(crash 工具)所需的内核符号地址:
// kernel/printk/printk.c
void crash_update_vmcoreinfo_init(void)
{
// 1. 记录内核 ELF 符号表和字符串表地址
VMCOREINFO_SYMBOL(log_buf); // printk 环形缓冲区地址
VMCOREINFO_SYMBOL(log_buf_len); // 缓冲区长度
VMCOREINFO_SYMBOL(log_first_idx); // 第一条消息索引
VMCOREINFO_SYMBOL(log_next_idx); // 下一条消息索引
// 2. 记录关键全局变量地址
VMCOREINFO_SYMBOL(init_uts_ns); // 内核版本信息
VMCOREINFO_SYMBOL(swapper_pg_dir); // 内核页表根
VMCOREINFO_SYMBOL(node_online_map); // 在线 NUMA 节点
VMCOREINFO_SYMBOL(contig_page_data); // 首个内存节点
VMCOREINFO_SYMBOL(prb); // printk 环形缓冲区结构
VMCOREINFO_SYMBOL(clear_seq);
VMCOREINFO_SYMBOL(per_cpu__runqueues); // 调度器 runqueue
// 3. 记录 page 结构大小等尺寸信息
VMCOREINFO_STRUCT_SIZE(page);
VMCOREINFO_STRUCT_SIZE(pglist_data);
VMCOREINFO_STRUCT_SIZE(zone);
VMCOREINFO_STRUCT_SIZE(free_area);
VMCOREINFO_STRUCT_SIZE(list_head);
VMCOREINFO_STRUCT_SIZE(cpumask);
// 4. 记录 page 结构中关键字段的偏移
VMCOREINFO_OFFSET(page, flags);
VMCOREINFO_OFFSET(page, _refcount);
VMCOREINFO_OFFSET(page, mapping);
VMCOREINFO_OFFSET(page, lru);
VMCOREINFO_OFFSET(page, _mapcount);
VMCOREINFO_OFFSET(page, private);
VMCOREINFO_OFFSET(pglist_data, node_zones);
VMCOREINFO_OFFSET(free_area, free_list);
}
这些信息由 makedumpfile 工具自动读取并嵌入最终 vmcore,是 crash 工具分析的基石。
五、makedumpfile:压缩与过滤优化
5.1 内存页分类与过滤
完整的 vmcore(16GB 内存)原始大小为 16GB,但其中绝大多数页面内容是零页或无需保留的缓存页。makedumpfile 通过 -d 参数控制丢弃的页面类型:
makedumpfile 过滤级别(-d 参数):
-d 0:无过滤(完整原始转储)
-d 1:零页(zero pages) → 通常占 80-90%
-d 2:缓存页(pagecache pages)
-d 4:用户数据页(user data pages) → 通常也需要
-d 8:free pages → 已释放的空闲页
-d 16:private cache pages
-d 32:shared cache pages
-d 64:exclude HWPoison pages
-d 128:offline pages
# 生产环境最佳实践(仅保留内核数据结构 + 用户数据)
makedumpfile -c -d 31 --message-level 1 \
/proc/vmcore /var/crash/$(date +%Y%m%d-%H%M)/vmcore
# 通常可将 16GB 转储压缩到 200-500MB
5.2 压缩算法与分块写入
makedumpfile 支持多种压缩算法,可显著减少 vmcore 文件大小:
┌───────────────────────────────────────────────────────────┐
│ makedumpfile 压缩算法对比 │
├──────────┬────────────┬──────────────┬────────────────────┤
│ 算法 │ 压缩率 │ 速度 (MB/s) │ 适用场景 │
├──────────┼────────────┼──────────────┼────────────────────┤
│ -c (zlib)│ ~3:1 │ 80-120 │ 通用默认 │
│ -l (lzma)│ ~4:1 │ 15-30 │ 高压缩(慢) │
│ -p (snappy)│ ~2.5:1 │ 200-400 │ 快速转储(应急) │
│ -z (zstd)│ ~3.5:1 │ 150-300 │ 平衡(推荐) │
│ -e (排除)│ N/A │ 仅过滤不压缩 │ 超快速临时分析 │
└──────────┴────────────┴──────────────┴────────────────────┘
# 分块写入 + zstd 压缩(用于大内存主机)
makedumpfile -c --split -b 4096 -z --message-level 1 \
/proc/vmcore /var/crash/vmcore /var/crash/vmcore.1
六、生产环境的 kdump 调优
6.1 自动 kdump 优化配置
# /etc/kdump.conf 推荐配置(高可用场景)
# 压缩 + 过滤
core_collector makedumpfile -c -d 31 --message-level 1
# 转储目标(NFS 共享)
path /var/crash
nfs 192.168.1.100:/data/kdump
# 或本地磁盘(仅数据盘,避免占用系统盘)
ext4 /dev/sdb1
# 转储后执行脚本(通知监控)
post_dump /usr/local/bin/notify-monitor.sh
# 内存预留调整(256GB 大内存主机)
# /etc/default/grub: crashkernel=1024M,high crashkernel=256M,low
6.2 kexec 验证与故障排查
# 验证 kdump 服务状态
systemctl status kdump
kdumpctl status
# 输出:kdump is ready to kdump
# 手动触发 kdump(仅测试环境!)
echo c > /proc/sysrq-trigger
# → 触发系统崩溃并启动捕获内核
# → 重启后检查 vmcore 是否生成
ls -lh /var/crash/$(date +%Y-%m-%d)*/vmcore
# 常见错误排查
# 1. "kexec: failed to load kdump kernel"
# → 检查 crashkernel= 参数是否太大导致内存不足
# → dmesg | grep crashkernel
# 2. "failed to load capture kernel"
# → 确认留给捕获内核的内存没有与其他预留区域冲突
# → 尝试降低 crashkernel= 大小
# 3. kdump 启动后卡住
# → 检查捕获内核的 initramfs 是否有必要的存储驱动
# → 检查 makedumpfile 的 core_collector 命令是否可用
# 使用 crash 工具分析 vmcore(需要匹配的 vmlinux)
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux \
/var/crash/2026-10-07/vmcore
# crash 常用命令
crash> bt # 崩溃时的函数调用栈
crash> ps # 崩溃时的进程列表
crash> log # printk 缓冲区(崩溃前最后输出)
crash> kmem -i # 内存使用统计
crash> sys # 系统基本信息
crash> files # 崩溃进程的打开文件
6.3 容器环境中的 kdump
容器化环境中使用 kdump 的约束和最佳实践:
# 容器 kdump 注意事项:
# 1. 捕获内核需要在 initramfs 中设置网络(cgroup v2 不支持 kdump 直接)
# 2. 推荐在宿主机层面配置 kdump,容器崩溃通过上层编排处理
# 3. 如果必须在容器内启用 kdump:
# - privileged 模式运行
# - 挂载 /proc, /sys, /dev
# - 配置 core_collector 仅做本地文件系统写入
# 容器中触发 panic 重定向(防止 panic 导致整个容器重启)
echo 1 > /proc/sys/kernel/panic_on_oom # OOM 时也触发 panic
echo 10 > /proc/sys/kernel/panic # panic 等待 10 秒后重启
# 容器环境 kdump 性能影响
# crashkernel=512M 意味着容器需要始终预留 512M 给小环境运行
# 建议在大内存节点(>=64GB 总内存)才启用 kdump
七、安全与加固
7.1 kexec 加载安全限制
Linux 4.4+ 引入了 kexec_load 系统调用的安全限制机制,防止恶意利用 kexec 绕过安全启动(Secure Boot):
// kernel/kexec.c
static int sys_kexec_load_check(void)
{
// 1. 检查 secure boot 是否启用(kexec_load 在 secure boot 下受限)
if (kernel_is_locked_down("kexec"))
return -EPERM; // 拒绝在非安全场景加载任意内核
// 2. 检查 CONFIG_KEXEC_SIG(内核签名验证)
// 如果启用,加载的内核必须有可信签名
#ifdef CONFIG_KEXEC_SIG
ret = verify_pe_signature(kernel, kernel_len);
if (ret < 0) return ret;
#endif
return 0;
}
// Secure Boot 状态下的 kexec 行为
# 检查 secure boot 状态
mokutil --sb-state
# 输出:SecureBoot enabled
# → Secure Boot 开启时,仅允许加载经过可信签名的内核(shim → vmlinuz 签名链)
相关内核配置选项:
| 配置项 | 默认值 | 作用 |
|---|---|---|
| CONFIG_KEXEC | y | 启用 kexec 基础支持 |
| CONFIG_KEXEC_FILE | y | 通过 /dev/kexec_secure_file 加载(推荐 API) |
| CONFIG_KEXEC_SIG | y | 要求内核有可信数字签名 |
| CONFIG_KEXEC_JUMP | n | 允许 kexec 返回原内核(实验性) |
| CONFIG_CRASH_DUMP | y | 启用 kdump 崩溃转储 |
7.2 kexec_file_load:新的安全 API
Linux 5.1+ 引入了 kexec_file_load() 替代传统的 kexec_load(),通过 file descriptor 加载内核,支持自动签名验证:
// 新的系统调用
// syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len, cmdline, flags);
// 对比 kexec_load() 的优势:
// 1. 可以验证内核内容而无需先映射到用户空间
// 2. 支持 Secure Boot 签名检查(IMA/EVM)
// 3. 内核代码直接读取 fd 缓冲区,减少一次复制
// 4. 不会暴露可执行物理地址给用户态(安全改进)
# kexec-tools 2.0.21+ 默认使用新 API
kexec -l /boot/vmlinuz --initrd=/boot/initrd.img --reuse-cmdline # 自动选择最佳 API
八、kexec 性能基准与实测数据
8.1 不同场景下的重启时间对比
| 场景 | 传统冷启动(秒) | kexec(秒) | 节省 |
|---|---|---|---|
| 物理服务器(BIOS POST 15s) | 45-60 | 20-30 | 50% |
| KVM 虚拟机(无额外固件) | 25-35 | 5-8 | 75% |
| AWS EC2(Xen 虚拟固件) | 30-45 | 8-12 | 70% |
| ARM64 服务器(UEFI slow) | 60-120 | 15-25 | 65% |
| NVMe SSD 容器主机(小型) | 10-15 | 3-5 | 60% |
8.2 kdump 转储时间对比
vmcore 转储时间依赖于内存大小和过滤级别:
# 32GB 主机,NVMe SSD 写 3GB/s
# 完整转储:32GB → 写入约 11 秒(磁盘带宽瓶颈)
# -d 31 过滤后:约 500MB → 写入约 0.3 秒
# 64GB 主机,SATA SSD 写 500MB/s
# 完整转储:64GB → 约 130 秒
# zstd 压缩 + -d 31 后:约 1GB → 约 2.5 秒
# 256GB 大内存主机(需特别关注)
# 建议使用 --split 分块写入 + NFS 远端存储
crashkernel=1536M,high crashkernel=512M,low
结语
kexec 和 kdump 是 Linux 内核在可用性与可调试性方面的两大基石。kexec 跳过了固件自检和 Bootloader 的传统瓶颈,将重启时间缩短 50-75%,是云环境热升级、高可用故障切换的核心支撑技术。kdump 则确保在崩溃发生时不丢失现场,通过 /proc/vmcore 的 ELF 转储格式和 crash_notes 的 CPU 寄存器快照,为事后分析提供了完整的第一手数据。在生产环境中合理配置 crashkernel 内存预留、makedumpfile 过滤压缩、以及结合 NFS/远端存储的转储目标,可以在最短时间内完成故障恢复并保留足够的调试信息以根除问题。掌握 kexec/kdump 的完整链路——从 kernel command line 预留内存 → kexec_load/kexec_file_load → panic 触发 crash_kexec → 捕获内核启动 → /proc/vmcore 暴露 → makedumpfile 转储 → crash 工具分析——是构建高可靠 Linux 系统的必备技能。

发表评论 取消回复