如何应对Linux 内核崩溃
kdump 是一种用于获取 Linux 内核崩溃转储的方法,而要找到关于其使用和内部结构的解释性文档可能有一些挑战。在这篇文章中,我将深入探讨 kdump 的基本用法以及 kdump/kexec 在内核中的实现。
首先,让我们了解 kexec。kexec 是一个 Linux 内核到内核的引导加载程序,它可以帮助从第一个内核的上下文快速引导到第二个内核。使用 kexec,可以关闭第一个内核,绕过 BIOS 或固件阶段,并直接跳转到第二个内核,从而实现快速重启,无需经历传统的 BIOS 阶段。
kdump 和 kexec 可以一起使用。当第一个内核崩溃时,kexec 可以引导第二个内核,而第二个内核则用于复制第一个内核的内存转储。之后,可以使用调试工具如 gdb 和 crash 来分析这个崩溃的原因。在这里,我将使用术语“第一内核”表示当前运行的内核,“第二内核”表示通过 kexec 运行的内核,“捕获内核”表示在当前内核崩溃时运行的内核。
kexec 机制涉及到内核和用户空间中的多个组件。内核提供了几个用于 kexec 重启功能的系统调用。用户空间的 kexec 工具(通常是 kexec-tools)利用这些调用,并提供可执行文件,用于加载和引导“第二内核”。一些发行版可能还会添加封装器,以便捕获和保存各种配置转储目标的转储。在这里,我将使用 Fedora Linux 发行版的 kexec-tools。
通过使用 kdump 和 kexec,你可以更有效地处理内核崩溃情况,加速系统的重启过程,并方便地分析和调试内核问题。
Fedora kexec-tools 工具
在 Fedora 操作系统上,你可以通过运行以下命令安装 fedora-kexec-tools:
bashCopy code
sudo dnf install kexec-tools
安装完成后,你可以使用以下命令启动 kdump 服务:
bashCopy code
sudo systemctl start kdump
启动 kdump 服务时,它会创建一个包含保存 vmcore 所需资源的根文件系统(initramfs),以及执行将 vmcore 复制和转储到目标位置的命令。此服务还会加载内核和 initramfs 到内核崩溃区域的适当位置,以便在发生内核崩溃时执行它们。
在 Fedora 中,有两个配置文件可供修改:
/etc/kdump.conf
:指定那些在修改后需要重新构建 initramfs 的配置参数。例如,如果将转储目标从本地磁盘更改为 NFS 挂载的磁盘,则需要重新加载与 NFS 相关的内核模块。/etc/sysconfig/kdump
:指定那些在修改后不需要重新构建 initramfs 的配置参数。例如,如果只需修改传递给“捕获内核”的命令行参数,则不需要重新构建 initramfs。
如果内核在 kdump 服务启动后出现故障,那么“捕获内核”将执行,并进一步执行 initramfs 中的 vmcore 保存过程。然后,系统将重新启动到稳定的内核。这种设置使得在系统遇到内核崩溃时能够更有效地保存转储信息和进行故障排除。
kexec-tools 工具
通过编译 kexec-tools 源代码,你将获得一个名为 kexec
的可执行文件。这个同名的可执行文件可以用于两个主要操作:加载和执行“第二内核”或加载“捕获内核”以在内核崩溃时执行。
对于加载“第二内核”,你可以使用以下命令:
bashCopy code
# kexec -l kernel.img --initrd=initramfs-image.img --reuse-cmdline
在这里,--reuse-cmdline
参数表示使用与“第一内核”相同的命令行。通过使用 --initrd
选项传递 initramfs。-l
参数表明你正在加载“第二内核”,这个内核不能在内核崩溃时执行。如果你想要加载并在内核崩溃时执行“捕获内核”,则必须使用 -p
参数,而不是 -l
。
以下是加载“捕获内核”的示例命令:
bashCopy code
# kexec -p kernel.img --initrd=initramfs-image.img --reuse-cmdline
为了测试内核崩溃,你可以使用以下命令:
bashCopy code
echo c > /proc/sysrq-trigger
这将触发内核崩溃,以便进行测试。有关 kexec-tools 提供的其他选项的详细信息,你可以查阅 man kexec
。在转到下一部分之前,建议观看一下 kexec_dump
的演示。
视频地址:
https://img.linux.net.cn//static/video/kexec_kdump_demo-iOq_rJhrKhA.mp4
kdump: 端到端流
在上述流程图中,必须在引导“第一内核”时为捕获内核保留一定量的内存,通过在内核命令行中传递 crashkernel=Y@X
来实现,其中 Y
是保留的内存大小,X
是可选的。通常,使用 crashkernel=256M
对于大多数 x86_64 系统是合适的,但选择适当的内存大小取决于多个因素,包括内核大小、initramfs 的大小以及运行时内存需求。
您可以使用 kexec
可执行文件传递内核和 initramfs 镜像,如上文“kexec-tools”部分所示的命令。值得注意的是,“捕获内核”可以与“第一内核”相同,也可以是不同的。通常,它们是相同的。Initramfs 是可选的,例如,当内核使用 CONFIG_INITRAMFS_SOURCE
编译时,您可能不需要它。通常,使用一个不同的捕获 initramfs 可以更好地执行 vmcore 的自动处理。
当“第一内核”崩溃时,它会执行必要的退出过程并切换到 purgatory(如果存在)。purgatory 的作用包括验证加载二进制文件的 SHA256,如果验证通过,则将控制权传递给“捕获内核”。一旦“捕获内核”接管,它将根据从 elfcorehdr
接收到的系统内存信息创建 vmcore。因此,在“捕获内核”启动后,您将在 /proc/vmcore
中看到来自“第一内核”的转储。根据使用的 initramfs,您可以进一步分析并将其复制到磁盘,也可以设置自动复制,然后重新启动到稳定的内核。
内核系统调用
内核提供了两个与 kexec 相关的系统调用:kexec_load()
和 kexec_file_load()
。这两个系统调用用于加载新的内核,以便通过 reboot()
系统调用启动或在内核崩溃时执行。
- kexec_load():
kexec_load()
系统调用用于加载一个可以稍后通过
reboot()
执行的新内核。其原型定义如下:
cCopy code
long kexec_load(unsigned long entry, unsigned long nr_segments, struct kexec_segment *segments, unsigned long flags);
- 用户空间需要传递给不同组件不同的段,如内核、initramfs 等。
kexec
可执行文件帮助准备这些段。
kexec_segment
结构如下:
cCopy codestruct kexec_segment {
void *buf; /* 用户空间缓冲区 */
size_t bufsz; /* 用户空间缓冲区长度 */
void *mem; /* 内核的物理地址 */
size_t memsz; /* 物理地址长度 */
};
- 如果传递
KEXEC_ON_CRASH
标志给kexec_load()
,加载的内核将不使用reboot(LINUX_REBOOT_CMD_KEXEC)
启动,而是在内核崩溃时执行。要使用kexec
,必须启用CONFIG_KEXEC
,并为kdump
启用CONFIG_CRASH_DUMP
。
- kexec_file_load():
是一个更高级别的系统调用,它接受内核和 initramfs 的文件描述符,然后由内核完成其余部分。其原型如下:
cCopy code
long kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char __user *cmdline_ptr, unsigned long flags);
- 与
kexec_load()
不同,kexec_file_load()
还支持传递命令行。在此情况下,内核根据系统体系结构接受和执行命令行。目前,kexec_file_load()
仅支持 x86 和 PowerPC。
当内核崩溃时会发生什么?
当内核崩溃时,以下操作将在将控制权传递给 purgatory 或“捕获内核”之前执行:
- 准备 CPU 寄存器。
- 更新 vmcoreinfo 备注。
- 关闭非崩溃的 CPU 并保存准备好的寄存器。
- 在此阶段可能需要禁用中断控制器。
- 执行 kexec 重新启动,加载或刷新 kexec 段到内存,并将控制权传递给执行文件。输入段可以是下一个内核的 purgatory 或起始地址。
ELF(Executable and Linkable Format)ELF 程序头和崩溃转储
ELF(Executable and Linkable Format)是一种常用于可执行文件和共享库的文件格式。在崩溃转储中,ELF 程序头对于描述如何将程序加载到内存中非常重要。在 vmcore 中,大多数转储核心都是 ELF 格式的,因此理解 ELF 程序头是很有帮助的。
每个 ELF 文件都有一个程序头,由系统加载器读取,描述了如何将程序加载到内存中。你可以使用 objdump -p elf_file
来查看程序头。
以下是 vmcore 的 ELF 程序头的示例:
# objdump -p vmcore
vmcore: file format elf64-littleaarch64
Program Header:
NOTE off 0x0000000000010000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**0
filesz 0x00000000000013e8 memsz 0x00000000000013e8 flags ---
LOAD off 0x0000000000020000 vaddr 0xffff000008080000 paddr 0x0000004000280000 align 2**0
filesz 0x0000000001460000 memsz 0x0000000001460000 flags rwx
LOAD off 0x0000000001480000 vaddr 0xffff800000200000 paddr 0x0000004000200000 align 2**0
filesz 0x000000007fc00000 memsz 0x000000007fc00000 flags rwx
LOAD off 0x0000000081080000 vaddr 0xffff8000ffe00000 paddr 0x00000040ffe00000 align 2**0
filesz 0x00000002fa7a0000 memsz 0x00000002fa7a0000 flags rwx
LOAD off 0x000000037b820000 vaddr 0xffff8003fa9e0000 paddr 0x00000043fa9e0000 align 2**0
filesz 0x0000000004fc0000 memsz 0x0000000004fc0000 flags rwx
LOAD off 0x00000003807e0000 vaddr 0xffff8003ff9b0000 paddr 0x00000043ff9b0000 align 2**0
filesz 0x0000000000010000 memsz 0x0000000000010000 flags rwx
LOAD off 0x00000003807f0000 vaddr 0xffff8003ff9f0000 paddr 0x00000043ff9f0000 align 2**0
filesz 0x0000000000610000 memsz 0x0000000000610000 flags rwx
在这个例子中,有一个 note 段,其余的是 load 段。note 段提供了有关 CPU 信息,load 段提供了关于复制的系统内存组件的信息。
vmcore 从 elfcorehdr
开始,它具有与 ELF 程序头相同的结构。
参见下图中 elfcorehdr
的表示:
kexec-tools
读取 /sys/devices/system/cpu/cpu%d/crash_notes
并准备 CPU PT_NOTE
的标头。同样,它读取 /sys/kernel/vmcoreinfo
并准备 vmcoreinfo PT_NOTE
的标头,从 /proc/iomem
读取系统内存并准备存储器 PT_LOAD
标头。当“捕获内核”接收到 elfcorehdr
时,它从标头中提到的地址中读取数据,并准备 vmcore。
- Crash Notes (
/sys/devices/system/cpu/cpu%d/crash_notes
):
- Crash notes 是用于在系统崩溃时存储有关 CPU 状态的区域。它包含有关当前 PID 和 CPU 寄存器的信息。
- VMcoreinfo (
/sys/kernel/vmcoreinfo
):
- VMcoreinfo 是一个包含内核调试信息的文件。
kexec-tools
读取此文件并准备vmcoreinfo PT_NOTE
的标头。其中包含一些关键的宏定义,如VMCOREINFO_PAGESIZE
、VMCOREINFO_SYMBOL
、VMCOREINFO_SIZE
、VMCOREINFO_STRUCT_SIZE
等。
- makedumpfile:
是一个应用程序,用于处理/proc/vmcore的数据,排除不必要的页面并在复制时进行压缩。它还可以从转储中删除敏感的符号信息。
通常在 kdump 环境中使用,可以使用以下示例命令:
bashCopy code
# makedumpfile -l --message-level 1 -d 31 /proc/vmcore makedumpfilecore
- 详细信息请参阅
man makedumpfile
。
kdump 调试
对于初学者使用 kdump 时可能遇到的问题:
问题:kexec -p kernel_image
执行失败
问题:在“第一内核”结束后,在控制台上没有看到任何输出(例如“bye”)
- 这可以帮助在早期阶段看到更多的调试输出。
- 确保第二内核的设置和参数正确。可能需要在命令行中传递额外的选项以启用调试信息。
- 如果体系结构不支持 purgatory 中的控制台,很难进行调试。确认 SHA 验证是否通过。
- 检查是否有适用于您的体系结构和机器的正确配置。有些平台可能需要特定的设置。
- 确保
kexec -e
命令成功启动了第二内核。 - 检查
kexec -e
之后的kexec -l kernel_image
命令是否正常工作。 - 确认是否缺少支持的体系结构或特定机器的选项。
- 验证 purgatory 的 SHA 验证是否失败。
- 检查是否第二内核早已崩溃。
- 在第二内核的命令行中传递
earlycon
或earlyprintk
选项。 - 如果问题仍然存在,使用 kexec-tools 邮件列表共享第一个内核和捕获内核的
dmesg
日志。 kexec -d -p kernel_image
- 在启动时确保使用正确的内存参数,例如
crashkernel=256M
。 - 运行
cat /proc/iomem | grep "Crash kernel"
,应该显示一个合适的分配范围。如果没有显示,可能是由于未正确传递crashkernel=
参数。 - 运行
cat /sys/kernel/kexec_crash_size
,它不应该返回零值。如果为零,表示崩溃内存没有正确分配。 - 检查是否分配了崩溃内存。
- 验证
/proc/iomem
中是否有 "Crash kernel" 的分配范围。 - 在命令行中确保传递正确的
crashkernel=
参数。 - 如果问题仍然存在,使用
-d
参数运行kexec
命令,将输出信息发送到 kexec-tools 邮件列表。