ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

kexec与kdump实战:不重启完成内核热切换与崩溃转储

kexec与kdump实战:不重启完成内核热切换与崩溃转储

机器又要重启了。内核想打个补丁,硬件要换个驱动,哪怕只是改一个启动参数,也逃不掉那套熟悉的流程:停机、断电自检、固件枚举设备、bootloader刷屏、内核重新初始化所有硬件……少说一两分钟,运气不好碰上硬盘自检或者网络环境复杂,十分钟都打不住。干运维和内核开发的人,应该都琢磨过同一个问题:只想换个内核,为什么要把整台机器的"重新开张"流程完整走一遍?机器上跑着的那个内核明明已经把内存、CPU、外设都初始化好了,让它直接把新内核接过来、把控制权交出去,不行吗?

Linux内核还真提供了这么个机制,就是kexec。全称是kernel execution,官方描述很直白:从当前正在运行的内核引导到一个新内核。说白了,在一台活着的机器上,不经过固件和bootloader,直接装载另一个内核镜像并跳转过去。这套机制不只是图快,它还是kdump(内核崩溃转储)的底座,是整个Linux系统可靠性体系里非常关键的一环。这篇文章会把kexec的来龙去脉、命令实操、kdump配置和排障经验一次讲透,适合Linux运维、内核开发者,以及对系统启动过程好奇的读者。看完你至少能自己动手完成一次"不重启却换内核"的实验,并且理解为什么生产环境里kdump如此重要。

1. kexec到底解决什么问题:重新审视"重启"这件事

1.1 传统重启流程里,时间都花在哪了

要理解kexec的价值,先得搞清楚一次普通重启到底在做什么。敲下reboot命令之后,机器要依次走完这些环节:当前内核执行关机流程、固件重新初始化(自检、内存训练、设备枚举)、bootloader加载(读配置、显示菜单、装载内核镜像)、新内核解压并初始化(解析设备树或ACPI、驱动探测、挂载根文件系统)、用户态服务一个个拉起来。

每一步都有真实的时间成本。固件自检尤其不可控,某些服务器内存条插得多、PCIe设备复杂时,光自检环节就能吃掉几十秒甚至几分钟。bootloader菜单如果配置了等待时间,哪怕选默认项也得倒计时走完。内核启动阶段要重新做一遍所有设备的枚举和驱动加载——这些工作在旧内核里明明已经全部做完了。

站在资源利用的角度看,这是一种实打实的浪费。一台机器上明明有一个活着的内核,它手里有写好的内存管理结构、已经完成初始化的驱动、已经建好的页表,结果全都要拆掉重来。kexec的思路非常直接:与其让固件和bootloader把"初始化"这套活再干一遍,不如让当前内核直接把新内核的镜像加载进内存,安排好必要的信息,然后一个跳转,把CPU控制权交给新内核。

这个设计取向跟内核里很多"尽量复用已有状态"的思路是一脉相承的,比如设备热插拔、CPU热迁移、容器原地升级,都是在减少状态重建带来的成本。kexec做的事情,就是在系统启动这个层面做了同样的优化,而且是在最底层、最核心的那个位置。

1.2 两个核心应用场景:快速重启与崩溃兜底

kexec的用途可以分成两条线来理解。

第一条线是快速重启。内核开发者在调试新特性、测试补丁时,经常要反复换内核启动。如果每次都走完整重启流程,一次实验周期可能拉长到十几分钟,而整个调试过程中真正必要的其实只有"启动新内核"这一步。用kexec压缩到几秒钟——机器跳过固件和bootloader,直接进入新内核的启动阶段。生产环境里做内核热升级时,kexec也能明显缩短业务中断窗口,虽然做不到业务完全不中断,但相比常规重启,停机时间能小一个量级。

第二条线是崩溃兜底,这是kexec更核心的价值。内核如果panic了,系统处于不可用状态,常规手段救不回来,只能靠硬件复位。但如果提前用kexec加载了一个备用内核(通常叫crash kernel),panic发生时就能从当前内核快速切换到备用内核,把那一刻内存里的转储信息(vmcore)抓下来再落盘。这就是kdump的基本原理。换句话说,kexec为"内核崩溃后的现场勘察"提供了机制基础——没有它,崩溃现场只能靠日志碎片去推测,有了它,能拿到完整的内存转储。

这两条线对应的用法细节不同:快速重启走的是普通加载路径(kexec -l加kexec -e),崩溃兜底走的是panic内核加载路径(kexec -p)。后面章节会分开细讲。

1.3 kexec与常规重启的对比:一张表看懂差异

对比维度常规重启kexec快速重启
参与组件固件、bootloader、新旧内核新旧内核(固件和bootloader不参与)
硬件重新初始化固件全量重新检测不复检,依赖内核驱动自行重置设备
典型耗时数分钟到十几分钟几秒到几十秒
适用场景首次装机、硬件变更、固件升级内核迭代调试、快速切换、崩溃转储
主要风险流程长但兼容性较稳部分硬件状态未完全重置,可能出现驱动异常

这张表里最需要注意的就是"硬件重新初始化"这一行。kexec跳过了固件,意味着新内核接手的是一个"已经被旧内核初始化过、但状态未必干净"的硬件世界。大多数情况下内核驱动能自己把设备重新初始化好,但总有意外——某些网卡、部分GPU、特殊外设,新内核可能认不到或驱动加载异常。这是kexec路线里绕不开的问题,后面排障章节会专门展开。

2. 核心机制拆解:kexec到底怎么"交接权力"

2.1 系统调用与用户态工具:装载和执行两段式设计

kexec最早是2004年前后进入内核的一个系统调用,正式名字是kexec_load,后来主线内核又引入了更新版的kexec_file_load。配套的用户态工具是kexec-tools,系统里你实际敲的kexec命令就来自这个包。两边分工清楚:系统调用负责在内核态完成镜像装载的关键动作,用户态命令负责读文件、解析镜像格式、组织参数、发起调用。

整个过程分两个阶段。第一阶段是装载(load):用户态工具把新内核镜像、initrd、启动参数组织成若干内存段(segment),然后调用kexec_load系统调用,让当前内核把这些段拷贝到预先选定的内存位置,并记录好入口地址。这时候新内核的"身体"已经躺在内存里了,但机器还在跑旧内核,业务不受任何影响。第二阶段是执行(exec):用户发出kexec -e命令,当前内核做最后的收尾——关闭中断、停止其他CPU、把控制权交给之前记录的入口,新内核接管整个系统。

两段分开有什么好处?最大的好处是安全可控。装载只是把数据放进内存,随时可以卸载(kexec -u);执行才是真正的"跳崖"动作,一旦跳出去,当前内核就回不来了。所以实际运维里有标准的"先装载、再三确认、最后执行"打法——先把新内核装载好,确认装载成功,再执行切换。这种准备与执行分离的设计,避免了"命令敲下去才发现镜像路径写错、却已经回不去"的尴尬。

2.2 内存布局、purgatory与入口点:新内核是怎么"上车"的

kexec对内存的编排相当讲究。新内核不能占用当前内核正在使用的内存区域,否则一装载就把自己家拆了。所以用户态工具在装载时会参与内存段规划,挑出安全的内存区域来安置各段。

这里有一个容易被忽略但又很关键的组件叫purgatory。它是一小段独立的机器码,位于装载的新内核之前,跳转时最先被执行。purgatory的职责有两块:一是验证各内存段的校验和,确认新内核镜像没被损坏;二是如果当前是带着crash kernel启动的崩溃场景,它还会把各CPU的寄存器状态保存下来,供kdump后续分析使用。你可以把它理解成两个内核之间的搬运中转站——旧内核把权力交给它,它确认一切妥当后再转给新内核。

入口点(entry point)则是新内核镜像中开始执行的地址,通常对应内核解压启动代码。整个交接过程看起来就是:旧内核暂停一切工作,CPU跳转到purgatory,purgatory做安全检查,然后跳转到新内核入口。新内核启动后会初始化自己的内存管理、重建页表、加载驱动——但它不会重新跑固件,也不会重新枚举所有硬件,这一点与冷启动有本质区别。

2.3 kexec与kdump的深度绑定:预留内存与crash内核

讲kexec就必须把kdump讲清楚,因为kdump是kexec机制在生产环境中最广泛的应用。kdump的基本玩法是:系统正常运行时就通过kexec -p加载一个专门的crash内核(通常是同一个内核但带特定配置,或者一个精简的小内核),这个crash内核放在一块预留的内存区域里,平时不用,就静静地等着。

当主内核panic时,它并不直接死透,而是检查自己有没有可切换的crash内核。有的话,保存好崩溃时刻的CPU寄存器、内存状态等现场信息,然后借道kexec跳转到crash内核。crash内核启动后不挂业务,而是抓取崩溃内核的内存镜像,写入磁盘上的vmcore文件。分析人员之后用crash工具打开这个vmcore,就能看到崩溃那一刻的内核栈、内存内容、寄存器值,定位问题根因。

这中间最需要精心规划的就是预留内存(reserved memory)。启动参数里通过crashkernel=来指定预留大小,太小了crash内核起不来,太大了又白白占用业务内存。经验上,x86_64平台常见配置是crashkernel=auto自动估算,或者手动给512M到1G,具体取决于机器内存总量和业务负载。这个参数的调优是个经验活,配置章节会细讲。

3. 实操手册:从安装到完成一次内核热切换

3.1 环境准备:安装kexec-tools并确认内核支持

先确认内核是否支持kexec。绝大多数主流发行版的内核在编译时已经打开了相关选项,x86_64和ARM64基本默认开启。可以这样确认:

# 查看内核配置中的kexec相关项 grep -E 'CONFIG_KEXEC' /boot/config-$(uname -r)

如果输出里有CONFIG_KEXEC=y,说明内核支持传统kexec_load路径;如果还有CONFIG_KEXEC_FILE=y,说明支持更新的kexec_file_load接口,后者会自动处理更多校验逻辑,用起来更省心。

安装工具链,不同发行版有差别:

# Debian/Ubuntu apt install kexec-tools # RHEL/CentOS/Rocky yum install kexec-tools # openSUSE zypper install kexec-tools

装完用kexec --version确认版本,一般2.0.20以上都比较成熟。这里有一个经验:如果系统装了systemd,部分发行版会带一个kexec.service或相关脚本,能让reboot命令自动走kexec路径,但这套自动机制各发行版实现差异较大,自己手动操作时别被干扰,下面全部以手动命令为准。

注意:后面涉及系统底层跳转的操作,建议先在虚拟机或测试机上完整跑一遍,确认无误后再考虑生产环境。

3.2 完整的一次热切换:镜像、initrd、参数三件套

手动做一次kexec快速重启,核心就是三个动作:装载、检查、执行。看一个最典型的例子,假设你编译好了新内核,当前运行内核是5.15,要切到6.1。

第一步,定位新版内核及initrd:

# 列出系统中可用内核 ls /boot/vmlinuz-* ls /boot/initrd.img-* # 假设目标镜像和initrd如下 KERNEL=/boot/vmlinuz-6.1.0-2-amd64 INITRD=/boot/initrd.img-6.1.0-2-amd64

第二步,装载新内核:

kexec -l "$KERNEL" --initrd="$INITRD" --reuse-cmdline

这里--reuse-cmdline表示原封不动复用当前内核的启动参数,对绝大多数场景是安全且省事的选择。如果只想在当前参数基础上追加或调整某些项目,用--append;想完全自定一套参数,用--command-line。三个参数的取舍逻辑下一小节专门讲。

第三步,确认装载状态:

kexec -s

这个命令在不同版本里可能输出有差异,但正常情况下会显示已加载镜像的相关状态。如果拿不准,可以加-d参数跑调试模式,看装载过程中的详细日志。

第四步,发起切换:

kexec -e

执行后,当前内核会关闭中断、停止其他CPU,然后跳转到purgatory,再进入新内核。整个过程视机器规模通常在几秒到几十秒。你会看到控制台输出变成新内核的启动日志,根文件系统重新挂载,服务重新拉起——熟悉但快得多。

3.3 参数选择逻辑:--command-line、--append与--reuse-cmdline

这三个参数是kexec命令里最容易出问题的位置,值得单独拆开讲。

--reuse-cmdline:复用当前内核的参数。最省心,因为当前内核能跑起来,说明这套参数是经过验证的。缺点是你没法临时调整参数,如果新内核恰好需要关闭某个特性或者增加内存设置,就满足不了。

--append:在当前参数后面追加内容。适合小改动,比如在现有参数基础上加一个panic=60或者quiet。注意append的语义是追加而不是覆盖,如果当前参数里已经有同名参数,追加的不生效还可能引发冲突,需要先看/proc/cmdline确认。

--command-line:完全自定义,相当于你手动写一份完整的启动参数。这对高级用户很友好,但也是翻车高发区——写错一个root=或者漏掉关键的initrd路径,新内核大概率起不来。我有一次实验就是从--reuse-cmdline切到--command-line时漏了crashkernel参数,结果新内核起来后kdump彻底失效,排查半天才意识到参数丢了。

实际操作经验是:默认优先--reuse-cmdline;确实要改参数时,先敲cat /proc/cmdline把当前参数打出来看一遍,逐项核对再决定用--append还是--command-line。看一眼的成本极低,省掉的风险却很大。

3.4 实操心得:装载成功不等于切换成功

第一次做kexec实验的时候,最容易犯的毛病是"装了就跑",结果新内核起不来,机器卡在半路,只能硬重启。后来我总结了一套稳妥的三步验证流程。

第一步,装载后先查状态。kexec -s有报错就停下来排查,别带着问题往下走。

第二步,有条件先检查initrd是否完整。kexec只负责把内核和initrd放进内存,不负责验证initrd内部是否完整。如果initrd制作有问题(比如新内核的模块路径不对),跳转后很可能卡在切换根文件系统的阶段。可以在装载前用lsinitramfs或者unmkinitramfs看一眼initrd内容。

第三步,保证有一个可靠的"逃生通道"。虚拟机用快照,物理机要确保带外管理或IPMI可用。因为kexec -e一旦执行,旧内核就彻底没了,如果新内核起不来,你只剩物理手段能救。把这条和"先装载、再确认、后执行"的原则配合起来,kexec的风险能压到很低。

4. 生产级实战:kdump崩溃转储的完整配置

4.1 kdump工作流:从panic到vmcore的全程路径

kdump整套机制跑起来后,一次内核崩溃的处理路径是这样:业务内核运行中触发panic,panic处理器检查是否有crash内核,有则保存现场并触发kexec跳转到crash内核,crash内核启动(极简启动,不挂业务),抓取原内核内存镜像,生成vmcore文件写入指定目录,之后系统等待人工接管或自动重启。

这条链路上每一步都有对应的配置项。启动参数层需要crashkernel预留内存;内核配置层需要CONFIG_CRASH_DUMP;用户态层面需要kdump服务负责加载crash内核和管理vmcore落盘。

有一个常见误解值得澄清:crash内核和业务内核不一定是两个不同的内核镜像。实际使用中,多数发行版直接复用同一个内核镜像作为crash内核,只是它以特殊参数和精简方式启动。这样既省空间又保证版本一致性,分析vmcore时内核符号和源码对得上,定位问题方便很多。

4.2 手把手配置kdump:五个关键步骤

以RHEL系为例,整体配置可以分成五步。

第一步,确认内核支持。检查/boot/config里的CONFIG_CRASH_DUMP和CONFIG_KEXEC。x86_64上额外确认CONFIG_RELOCATABLE,ARM64现在也支持得不错,可以一并查。

第二步,设置启动参数。编辑/etc/default/grub,在GRUB_CMDLINE_LINUX里加上crashkernel=512M,然后重新生成grub配置并重启让参数生效。

有的系统也支持crashkernel=auto自动估算,但自动值在不同内核版本里波动较大,我个人倾向于手动指定。给多少合理?看内存总量和业务特性。2G内存的虚拟机给512M够用,64G内存的物理机可以给1G甚至更多。预留内存是实打实从业务内存里切走的,越小越要抠着用。

第三步,安装并启动kdump服务。RHEL系装kexec-tools后服务名叫kdump,直接systemctl enable --now kdump。服务启动时会读取crashkernel参数并加载crash内核。

第四步,配置vmcore保存位置。核心配置文件在/etc/kdump.conf,关键两行如下:

path /var/crash core_collector makedumpfile -c -d 31

path指定落盘目录,core_collector指定压缩工具。makedumpfile的-d 31表示丢弃所有可丢弃页(包括缓存页、未使用页等),能把vmcore体积压到原来的几十分之一,强烈推荐。不压缩的话,一台64G内存的机器崩溃产出的vmcore可能也有几十G,磁盘压力很大。

第五步,验证配置。确认服务active、crash内核已加载:

cat /sys/kernel/kexec_crash_loaded # 输出1表示crash内核已装载 cat /sys/kernel/kexec_crash_size # 查看预留内存实际大小

4.3 让内核主动"躺平":安全触发一次真实panic验证

kdump配好之后不能停留在"看起来加载了"这个层面,必须实际触发一次panic验证整条链路能跑通。最直接的方法是用sysrq-trigger:

# 注意:这行命令执行后系统会立即崩溃,务必在维护窗口或测试机上做 echo c > /proc/sysrq-trigger

执行后系统立即panic,几秒后crash内核接管并生成vmcore。一切正常的话,重启后在配置的path目录下能看到vmcore文件。然后可以用crash工具对vmcore做初步分析:

crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/vmcore

进入crash交互环境后,bt命令查看崩溃时的栈回溯,log命令查看内核日志,ps命令还原当时的进程列表。第一次在真实vmcore上跑出完整调用栈时,那种直观感是看报错日志完全体会不到的——每一行栈帧都对应着崩溃前的实际执行路径,配合源码能一路追到根因。

5. 常见问题与排查技巧实录

5.1 高频问题速查表

症状可能原因排查方向
kexec -l报Operation not permitted内核未开启KEXEC;系统安全模块限制kexec系统调用检查内核config;查看系统锁策略和SELinux日志
跳转后新内核起不来,卡在早期启动initrd缺失或损坏;启动参数错误核对--reuse-cmdline与initrd路径;用-d模式看装载日志
跳转后部分设备不可用设备状态未干净重置;驱动与新内核不兼容查dmesg中对应设备错误;必要时候退回到冷启动
kdump服务启动失败crashkernel参数未生效;预留内存不足查dmesg中预留内存相关日志;调整crashkernel数值
vmcore文件异常庞大未正确使用makedumpfile压缩配置core_collector;检查-d参数级别
kexec -e执行后直接黑屏断电严重启动故障或硬件不兼容用带外管理看串口日志;换常规重启验证硬件

这张表只是起点,真正能拍板的依据永远在dmesg和串口日志里。kexec是一个"快进快出"的机制,如果跳过日志直接看结果,往往很难判断问题到底出在装载阶段还是跳转阶段。

5.2 两个让我印象深刻的排障案例

第一个案例发生在某台老机器上。做kexec快速切换,新内核装载一切正常,一执行就黑屏,半天没反应,最后通过带外管理看串口,输出停在固件校验失败附近。排查发现是老平台的固件对快速跳转支持不佳,purgatory执行时某些硬件状态异常导致校验没过。这个案例给我的教训很朴素:kexec对硬件"状态干净度"是有要求的,老平台或特殊外设环境下,不能默认它一次就能成功,务必先在同样的硬件组合上验证。

第二个案例和生产环境kdump失效有关。配置全对、crashkernel也预留了,但panic之后就是不跳转。查dmesg发现关键线索——内存预留区域被一个驱动占用了,导致crash内核装载失败。解决办法是放弃自动预留方案,改成手动指定大小,同时给内核参数增加了大页内存的相关约束,避免预留区域和业务内存互相挤压。这类问题在内存拓扑复杂或者启用了大页的机器上特别常见,配置时优先手动指定预留区域,宁可多留一点也别让它在关键时刻掉链子。

5.3 生产环境的安全红线与操作习惯

用kexec做生产操作,我给自己定了几条硬规矩。

第一条,永远在测试机上把整条流程完整跑通再上生产。这句话听起来像废话,但kexec恰恰是最容易被跳过验证的工具——因为装载阶段太"无害"了,命令敲完就成功,让人不自觉放松了对执行阶段的警惕。

第二条,装载成功之后、执行之前,至少核对三样东西:当前内核参数是否满足新内核需求、initrd是否与目标内核对应、磁盘空间是否足够(kdump场景下要确认vmcore落盘空间)。任何一项不确定,宁可多花两分钟验证。

第三条,把kexec操作纳入变更管理流程。生产上执行kexec -e前,通知相关方、记录时间点、确认回退方案可用。回退方案一般是新内核起不来时通过带外管理或物理手段回到原内核,这个方案必须在执行前就验证可用,不能等出了问题再找。

第四条,保持kexec-tools和内核版本的兼容性。内核接口偶有变动,老版本工具配合新内核容易出些莫名其妙的装载报错。保持工具链和内核版本相对同步,能省掉大量无谓的排障时间。

最后说点实实在在的体会。我最早接触kexec只是为了省重启时间,以为它就是个"快速重启工具",后来在kdump排障里真正意识到,kexec的价值远不止快——它是系统在极端时刻保住的最后一条退路。内核开发者、运维、SRE,很多人可能很久都用不上一次kdump,但真到内核崩溃的那一刻,有没有提前配置好kexec和kdump,直接决定你能拿到一份完整的事故现场记录,还是只能对着碎片日志猜。平时花半小时把kexec和kdump验证好,比出事之后翻两天日志有价值得多。如果还没在自己的环境里试过kexec,建议从虚拟机上的--reuse-cmdline热切换开始,几分钟就能建立起对这套机制的直观感受;kdump则挑一个维护窗口做一次真实的panic演练,体验过一次"崩溃之后还能抓现场"的从容,你就知道这套组合拳有多值得投入了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进