引言

在云计算与容器化生产环境中,系统停机时间(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 image4KB初始 ramdisk(根文件系统)
[2]Kernel command line256Bcmdline 参数(root, console 等)
[3]ACPI tables (可选)16Bkdump 需要传递原 ACPI 表
[4]device tree (ARM)4KBARM64 设备树二进制

二、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_KEXECy启用 kexec 基础支持
CONFIG_KEXEC_FILEy通过 /dev/kexec_secure_file 加载(推荐 API)
CONFIG_KEXEC_SIGy要求内核有可信数字签名
CONFIG_KEXEC_JUMPn允许 kexec 返回原内核(实验性)
CONFIG_CRASH_DUMPy启用 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-6020-3050%
KVM 虚拟机(无额外固件)25-355-875%
AWS EC2(Xen 虚拟固件)30-458-1270%
ARM64 服务器(UEFI slow)60-12015-2565%
NVMe SSD 容器主机(小型)10-153-560%

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 系统的必备技能。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部