首页 / 资讯中心 / 文章详情

kexec原理与实战:跳过重启实现秒切Linux内核的完整指南

kexec原理与实战:跳过重启实现秒切Linux内核的完整指南 ★ FEATURED ARTICLE
1. 从“重启”到“秒切内核”kexec到底解决了什么痛点我在生产环境折腾内核不是一天两天了。早些年每次升级内核或者调试一个新的内核特性最让人头疼的就是“重启”这两个字。一台承载着业务的服务机动不动就要等上几分钟甚至十几分钟的BIOS自检、硬件初始化、引导加载程序扫描然后才是内核解压、驱动加载、服务拉起。如果是一台需要快速切换内核的测试机倒还好但如果是在线上环境每多等一秒都意味着业务中断时间被拉长。有没有一种办法能跳过那堆冗长的硬件自检和引导流程直接从当前正在运行的Linux内核“跳”到一个新内核答案就是题目里这个命令kexec。kexec是Linux内核提供的一个机制它的全称是“Kernel EXecution”直白地说就是让当前正在运行的内核直接加载并切换到一个新内核不需要经过传统的重启流程。底层对应一个系统调用用户态配套的工具也叫kexec。它的工作方式相当于你在办公室里正开着会突然要换一间会议室但你不是走出大楼再从大门进来而是直接推开隔壁会议室的门坐下——省掉了所有重复的入场流程。这个能力在不少场景下都非常值钱。比如线上内核升级后需要验证新内核是否稳定你不想经历漫长的停机窗口再比如你在调试一个会导致系统崩溃的内核模块希望崩溃时能自动跳到一个保留的“急救内核”去抓取现场信息这就是后面要聊的kdump机制的基础。作为Linux运维、内核开发者或者底层系统爱好者掌握kexec几乎是必修课。这篇内容我尽量用实际操作的角度把kexec的原理、用法、坑点和配套场景都讲透确保你不仅能看懂还能直接拿去用。2. kexec的两种核心工作模式与系统级架构2.1 加载式的两阶段操作kexec -l 与 kexec -ekexec这个工具的使用逻辑非常清晰一共就两大步先加载再执行。第一步用kexec -l把新内核和它的initrd初始根文件系统加载到当前内核已经预留好的内存区域中同时把启动参数一并准备好。执行完这一步新内核其实是“待命”状态当前系统一切照常运行没有任何切换发生。第二步在你准备好的时机执行kexec -e当前内核会立即停止一切正常活动直接跳转到之前加载好的新内核入口地址。整个切换过程非常快通常就是一两秒的事根本没有传统重启那样的硬件重置和引导流程。这两个步骤分开设计是刻意为之。因为加载内核是一个相对耗时的过程需要读取内核文件、解压缩、把各段放入内存、组装启动参数而执行切换是瞬间动作。把加载和执行解耦意味着你可以提前很多时间把新内核准备好然后在业务低峰期一键切换。我在实际使用中经常提前几分钟把内核加载好反复检查启动参数无误最后执行真正的切换动作。需要注意kexec -l参数后面可以直接指定内核镜像文件路径比如/boot/vmlinuz-xxx。同时几乎都必须配合--initrd指定initrd文件否则新内核可能因为缺少必要的驱动模块而无法挂载根文件系统。至于启动参数可以用--command-line手动指定但如果懒得一个个敲直接用--reuse-cmdline复用当前内核的启动参数也是一种省事且稳妥的做法。2.2 立即执行的kexec系统调用与一次性的顶层封装上面说的两阶段是针对kexec工具的使用流程。在系统调用层面其实还有一个更加猛烈的封装kexec_load系统调用可以直接完成从加载到执行的整个过程因为加载和执行本质上是一个连续的动作。只是内核出于安全考虑通常会要求调用者具备CAP_SYS_BOOT权限而且大多数发行版默认不会把这种“自杀式”调用暴露给普通用户。实际日常操作中我们使用kexec工具就足够了。kexec工具在加载阶段会把内核镜像、initrd等数据通过kexec_load系统调用交给内核但并不会触发立即切换。而kexec -e执行的其实是另一个系统调用reboot(LINUX_REBOOT_CMD_KEXEC)相当于告诉内核“我已经准备好了你现在可以跳转过去了”。此外如果你的系统使用systemd作为init系统还有一个更省事的入口systemctl kexec。这个命令会读取systemd预设的kexec加载配置通常是/run/systemd/system/kexec.conf或者由systemd-kexec服务在启动时配置然后自动完成加载并执行切换。对很多场景来说直接用systemctl kexec比手工敲kexec -l加kexec -e要省心很多但前提是你得先把加载信息提前配置好。3. 为什么kexec能跳过重启深入内核的切换机制3.1 跳过固件与引导加载程序的关键设计传统启动的路径是电源上电 - BIOS/UEFI自检 - 引导加载程序如GRUB加载内核到内存 - 内核初始化硬件 - 挂载根文件系统 - 启动init进程。这个过程中BIOS/UEFI自检和引导加载程序的扫描往往是耗时的大头很多时候不需要每次都完整执行。kexec的核心思想是当前内核已经完成过硬件初始化这些硬件状态其实都还活着不需要重新初始化一遍。新内核只需要接管这些已经被初始化好的硬件即可。所以kexec在加载新内核的时候会把新内核镜像放入一段特定的内存区域然后在新内核入口点执行时它并不会像传统引导那样重新扫描总线、重新枚举设备而是尽可能继承当前内核已经建立的硬件上下文。这就像你换了个新的大脑来接管一个已经正常运行的身体而不是让整个身体推到手术室重新组装一遍。当然实际操作中新内核还是会做大量的软初始化工作比如重新初始化内存管理、进程调度、中断等核心子系统但硬件层面的重复枚举确实被大幅简化了。3.2 内存布局与加载地址的指定逻辑kexec加载内核时有一个很重要的概念是加载地址。使用kexec -l加载时系统默认会选择一段“安全”的内存区域来放新内核。这个安全区域通常是当前内核未使用的物理内存段由内核在启动时预留或者动态分配。在某些特殊场景下你可能需要手动指定加载地址比如当默认加载区域被某些固件或驱动占用时会报出kexec: Cannot allocate memory或者类似的内存冲突错误。这时可以通过--load参数配合地址选项手动指定。但绝大多数情况下内核自带的这个内存分配逻辑已经足够智能会自动避开正在使用的内存区域。这里要特别提醒一个点kexec加载新内核时必须保证新内核镜像的完整性并且新内核运行时需要的物理内存区域覆盖范围不能和当前内核正在使用的关键数据结构冲突。否则切换后可能出现无法解释的随机崩溃。我在调试时遇到过好几次类似问题最后都发现是加载地址选择不当造成的。3.3 内核配置选项与kexec的依赖关系要在你的Linux系统上使用kexec首先内核必须开启相应的配置项最常见的有三个CONFIG_KEXEC、CONFIG_KEXEC_FILE和CONFIG_CRASH_DUMP。CONFIG_KEXEC是支持传统kexec加载机制的开关CONFIG_KEXEC_FILE是指基于文件描述符的加载方式也就是用kexec -s参数时走的那条路径。基于文件的加载方式在安全性上要更好一些因为内核会对镜像签名做校验尤其在启用了Secure Boot的机器上这种方式几乎成了唯一选择。如果你的内核编译时没开这两个选项即使你装了kexec工具执行时也会提示不支持。所以在折腾kexec之前先确认一下你的内核配置zgrep KEXEC /proc/config.gz或者如果你的发行版没有把config导出到/proc/config.gz那就检查一下/boot/config-$(uname -r)文件。确认有CONFIG_KEXECy或CONFIG_KEXEC_FILEy之后再进行下一步。4. 实操篇kexec的安装、加载与切换全流程4.1 安装kexec-tools不同发行版的差异kexec的用户态工具一般叫kexec-tools几乎所有主流Linux发行版都提供了对应的软件包。在Debian/Ubuntu系列上安装命令是sudo apt install kexec-tools安装过程中会询问你是否允许系统在重启时自动使用kexec快速启动如果没有特殊需求可以先选择否手动控制更稳。在RHEL/CentOS/Fedora系列上安装命令是sudo yum install kexec-tools或者在新版本上用sudo dnf install kexec-tools在openSUSE上则是sudo zypper install kexec-tools安装完成后先用kexec --version验证一下工具是否正常可用。版本号不同参数解析上会有细微差异但核心操作逻辑基本一致。4.2 加载新内核到内存一个完整的kexec -l示例下面我以一个实际的升级场景来演示。假设当前系统跑的是5.15内核你要切换到刚装好的6.1内核但暂时不想执行真正的重启动作。首先确认新内核和initrd文件已经放在了/boot目录下ls /boot/vmlinuz-6.1* /boot/initrd.img-6.1*确认无误之后执行加载命令sudo kexec -l /boot/vmlinuz-6.1.0-rc3-generic \ --initrd/boot/initrd.img-6.1.0-rc3-generic \ --reuse-cmdline这里--reuse-cmdline会自动复用当前内核的启动参数在很多场景下是最省事的选择。但如果你的新内核需要额外的启动参数比如要预留内存给kdump那就需要手动指定sudo kexec -l /boot/vmlinuz-6.1.0-rc3-generic \ --initrd/boot/initrd.img-6.1.0-rc3-generic \ --command-lineroot/dev/mapper/vg-root ro crashkernel256M quiet这里有一个小提醒手动指定启动参数时root一定要写对否则新内核起来后会因为找不到根文件系统而直接进入initramfs的紧急模式甚至直接panic。如果你不确定当前根文件系统的设备路径可以先执行findmnt /查看。4.3 执行切换kexec -e与systemctl kexec加载完成之后你可以通过kexec -e立即执行内核切换。但执行前务必确认几件事所有重要的数据已保存当前服务的状态可以接受中断。因为kexec -e执行后当前内核几乎不会有任何优雅的退出流程所有用户态进程会直接消失未落盘的数据可能会丢失。如果你用的是systemd更优雅的替代方案是sudo systemctl kexecsystemctl kexec会触发systemd先执行一些基本的服务停止操作然后再调用kexec切换。注意它并不是完整地执行关机流程而是会尽量在短时间内停掉部分服务、卸载部分文件系统然后快速切换。优点是比kexec -e稍微温和一些缺点是没有完整关机那么干净。从我个人的经验来看如果是测试机或者你能扛得住服务直接中断的场景直接kexec -e反而更简单直接因为加载阶段你已经准备好了一切。systemctl kexec多执行的那些步骤有时候反而会引入额外的变量。4.4 用脚本一键完成“加载-切换”的自动化实践如果你经常需要切换不同的内核写一个简单的脚本可以省去每次手动输入参数的麻烦。下面是一个我常用的shell脚本模板#!/bin/bash KERNEL$1 INITRD$2 EXTRA_CMDLINE${3:-} if [ -z $KERNEL ] || [ -z $INITRD ]; then echo Usage: $0 kernel-image initrd-image [extra-cmdline] exit 1 fi echo Loading kernel: $KERNEL echo Initrd: $INITRD CMD if [ -n $EXTRA_CMDLINE ]; then CMD--command-line$EXTRA_CMDLINE else CMD--reuse-cmdline fi sudo kexec -l $KERNEL --initrd$INITRD $CMD echo Kernel loaded. Run sudo kexec -e to switch.使用时就执行./kexec-switch.sh /boot/vmlinuz-6.1.0-rc3-generic /boot/initrd.img-6.1.0-rc3-generic这个脚本只负责加载切换动作留给使用者手动触发避免脚本误触发导致意外重启。5. kdump场景kexec的另一半江山5.1 crashkernel预留内存与kdump机制讲到kexec不可能不提kdump。kdump是Linux内核崩溃转储机制的核心它依赖的正是kexec技术。在系统启动时通过内核参数crashkernel256M预留一块物理内存这块内存专门用于在系统崩溃时加载和维护一个“捕获内核”。平时这个捕获内核并不会运行它只是被kexec预先加载到预留内存中处于待命状态。当主内核发生panic时它会直接跳转到这个捕获内核捕获内核启动后会尽可能保存崩溃现场的内存映像也就是生成一个vmcore文件供后续离线分析。这背后的流程是系统正常启动时用kexec -p加载捕获内核。这里-p参数的含义是panic内核加载目标是一段独立的预留内存。等到真正崩溃时主内核无法继续执行但它会跳转到这段预留内存中运行捕获内核。捕获内核不依赖被崩溃破坏的根文件系统而是从预先加载好的initrd启动然后把主内核的内存转储到磁盘上。5.2 配置kdump的完整步骤与参数说明配置一套可用的kdump环境其实很简单。首先在GRUB的内核启动参数中加入crashkernel256M这个大小取决于你的系统内存总量和需要转储的内存大小。一般建议至少预留系统内存的1/16比如系统有8G内存预留512M会比较稳。修改/etc/default/grub中的GRUB_CMDLINE_LINUX加上参数然后执行sudo update-grub重启后确认预留内存已生效cat /proc/iomem | grep Crash kernel这一步如果没有任何输出说明crashkernel参数没有生效需要检查是不是GRUB配置没写对。接着安装kdump工具sudo apt install kdump-tools安装完成后kdump-tools服务会自动用kexec -p把当前内核的捕获内核加载到预留内存中。验证是否加载成功可以看kdump-config show正常情况下会显示ready to kdump状态。最后测试一下崩溃转储是否生效sudo sysctl -w kernel.panic_on_oops1 sudo echo c /proc/sysrq-trigger这条命令会强制触发内核panic然后系统应该会快速跳转到捕获内核并生成vmcore。注意执行这条命令后系统会短暂不可用生产环境别乱试。测试成功后重启回到正常内核检查/var/crash目录下是否有vmcore文件生成。5.3 捕获内核与主内核的资源隔离问题kdump有一个容易被忽略的问题捕获内核和主内核共享物理硬件但预留内存是独立的。如果捕获内核加载了和主内核冲突的驱动或者在转储时访问了被主内核破坏的硬件寄存器可能导致转储失败。为了减少这种概率很多发行版会为捕获内核准备一个专门的initrd里面只包含最基础的驱动模块。实际操作中如果发现kdump经常转储失败大概率是initrd里包含了太多不必要或者有副作用的驱动模块。这时候可以考虑精简initrd只保留磁盘控制器、网卡等必要驱动。不过需要注意的是精简initrd是个技术活搞不好反而会让捕获内核根本起不来。我在这里的建议是先用发行版默认的initrd跑一遍测试确认基本可用后再考虑优化。6. 我踩过的坑kexec使用中的常见问题与排查技巧6.1 kexec -l 报错内核镜像格式或权限问题最常见的问题之一是在执行kexec -l时直接报kexec: Loading kernel failed或者Invalid file format。这类错误通常有两个原因。第一是权限不够。kexec加载内核需要root权限如果你用的是普通用户记得加上sudo。第二是内核镜像文件格式不对。部分发行版在/boot目录下的vmlinuz文件不是标准的Linux内核镜像而是带EFI stub的混合镜像某些旧版本的kexec-tools识别不了。解决办法是使用kexec -s走基于文件描述符的加载方式它会用内核内部的kexec_file_load接口来处理兼容性通常更好。另外还有一个小概率问题内核镜像文件损坏。检查一下文件的校验值是否和发行版的发布值一致。如果文件不完整加载时内核在解压阶段就会失败。6.2 切换后新内核起不来initrd问题与模块缺失kexec -e执行后如果新内核一直黑屏或者反复重启大概率是initrd有问题。initrd的作用是在真正的根文件系统挂载前提供必要的驱动和工具让内核能找到根设备。如果你没有指定initrd或者initrd和内核版本不匹配新内核会因为没有磁盘驱动而无法挂载根文件系统。遇到这种情况我的排查思路是这样在kexec -l之前先检查一下initrd里是否包含了对应的磁盘控制器驱动。可以用以下命令查看initrd内容lsinitramfs /boot/initrd.img-6.1.0-rc3-generic如果发现缺少必要模块可以用update-initramfs -c -k 6.1.0-rc3-generic重新生成initrd。另外一个容易被忽略的点是部分驱动模块依赖固件文件而固件文件在initrd中缺失导致驱动加载失败。这种问题通常会在系统日志中留下firmware: failed to load之类的记录。排查时如果黑屏看不到任何输出可以考虑在内核启动参数里加上noreplace-paravirt earlyprintk这类调试选项让启动早期信息输出到串口或VGA上。6.3 Secure Boot场景下的签名校验问题现在不少服务器默认开启Secure Boot这种情况下直接加载未签名的内核镜像会被固件拦截。使用传统kexec -l通常会直接失败或者虽然在加载阶段通过了但在执行切换时被固件中止。正确的做法是使用带签名的内核镜像并且使用kexec -s基于文件描述符的加载方式让内核自己去做签名校验。如果你需要签名的工具链可以在发行版仓库里找sbsigntool用自己的Machine Owner KeyMOK对内核镜像签名。签完名后加载时会比传统方式稍微复杂一点但整体动作还是那几步。如果你只是为了在本地测试不想折腾签名最简单的方案是在BIOS设置里临时关闭Secure Boot。但要注意生产环境不建议为了图省事关闭安全启动这会大大降低系统安全性。6.4 切换后网卡名变化导致网络失联的应对这是一个非常实际的问题。当你从一个内核切换到另一个内核时如果新内核使用的网络驱动命名规则不同比如从eth0变成了enp0s3你的网络配置可能全部失效远程连接的机器会直接断线。解决办法是在新内核的启动参数中固定网卡命名规则或者提前在initrd和系统配置中预置好新环境下的网络配置。最简单粗暴的办法是在切换前确保新内核的驱动模块和udev规则和旧内核一致。如果你用的是现代发行版通常系统的网络管理服务会自动处理网卡重命名但还是建议在切换前把网卡的配置文件检查一遍。我这里还有一个实用的小技巧如果你必须远程操作一台机器做kexec切换最好准备一个IPMI或者带外管理口。万一切换后网络起不来你还能通过带外方式恢复。这个教训我是付出过代价的在没有任何带外管理的情况下远程切换内核结果只能跑去机房物理操作。6.5 内存不足导致的加载失败当系统可用内存非常紧张时kexec -l可能会报错Cannot allocate memory。这是因为kexec需要一段连续的物理内存来存放新内核和initrd。系统内存碎片化严重时即使总可用内存还有不少也找不到足够大的连续区域。这时有两个常用手段。一是尝试用--load配合指定的加载地址尝试不同的内存段。这种情况通常需要根据/proc/iomem里的内存布局来选择合适的区域。二是先用echo 3 /proc/sys/vm/drop_caches释放页缓存减少内存碎片。如果这两个办法都不行那基本只能等一些内存占用大户退出后再尝试。6.6 常见问题速查表为方便快速定位问题我把上面提到的典型问题整理成一张速查表现象可能原因排查方向kexec -l 报 Invalid file format内核镜像格式不被识别尝试 kexec -s 走文件描述符方式加载时报 Cannot allocate memory内存碎片化或预留区域冲突释放缓存或手动指定加载地址kexec -e 后黑屏initrd缺失或驱动模块不完整检查initrd内容并重新生成Secure Boot拦截切换内核镜像未签名使用签名内核或临时关闭Secure Boot切换后网络失联网卡命名变化或驱动缺失提前确认新内核网络配置切换后系统反复重启根文件系统参数不对检查 command-line 里的 root 配置kdump转储失败预留内存不够或模块冲突调大 crashkernel 或精简initrd这张表不能覆盖所有场景但常见的坑基本都在这了。7. kexec的进一步扩展与GRUB、systemd和容器的协同使用7.1 和GRUB引导流程的对比何时用kexec何时用传统重启有人可能会问既然kexec这么方便为什么不把所有重启都改成kexec答案在于kexec的适用范围是有限的。传统重启的好处是彻底。硬件被完全重置、固件重新初始化能够清掉很多潜在的不稳定状态。如果系统里某个硬件设备状态异常传统重启很容易解决但kexec切换时硬件状态是继承的如果某个设备已经处于不稳定状态新内核也会跟着遭殃。所以我的经验法则是日常内核升级和版本切换用kexec能显著缩短停机时间但如果系统出现莫名其妙的硬件异常、驱动僵死、或者你要清理固件级别的异常状态那就老老实实走完整重启。不要迷信kexec它只是工具不是银弹。7.2 systemd-kexec服务与内核升级自动化systemd提供了一套和kexec协同的机制你可以通过配置/etc/systemd/kexec.conf来预设kexec加载信息。每当系统执行systemctl kexec时systemd会根据配置完成加载并切换。同时在内核升级自动化方面有些发行版会在安装新内核包后自动执行kexec -l预加载新内核。这样用户之后只需要执行一条命令就能完成切换。Debian系的部分环境在安装kexec-tools时会提示你配置这个行为。实际操作时我不太建议完全自动化毕竟内核切换是一件影响面很大的动作提前预加载可以但真正的切换动作还是应该由人来控制至少得留一个确认环节。7.3 kexec在容器和虚拟机快速迭代中的妙用在容器场景下kexec本身并不直接作用于容器但如果你是搞内核开发的经常需要测试新内核在容器运行时环境下的表现kexec就能帮上大忙。比如某公司在物理机上跑着一堆容器需要快速验证新内核与容器运行时是否兼容。如果走传统重启可能要等很多容器重新调度。用kexec提前加载好新内核在维护窗口一键切换整体服务中断时间能压缩到一分钟以内。虚拟化场景下的思路也类似。某些虚拟化平台支持guest内部用kexec快速切换内核宿主机层面无感知对业务影响极小。但需要注意虚拟化平台的某些特性比如动态内存气球、设备透传在kexec切换后可能无法保持原有状态测试时一定要验证这些高级特性是否仍然正常工作。8. 实操记录一次线上内核升级的完整复盘最后分享一次我实际操作的案例。环境是一台某公司的测试服务器跑着一个对网络性能要求很高的应用内核版本从5.10升到5.15但那次升级不能走正规的重启流程因为业务要求尽量减少中断时间而完整的硬件自检要花掉将近十分钟。我的操作流程是这样先把新内核和initrd准备好检查/boot目录下的文件无误然后执行kexec -l预加载加载时手动指定了root设备和几个必要的启动参数没有用--reuse-cmdline因为旧内核的某些参数比如旧驱动专用的配置在新内核上已经不适用。加载完成后我用kexec -e执行切换整个切换过程只花了大概两秒钟系统直接进入新内核。新内核启动后我第一时间检查了/var/log/kern.log确认没有明显的驱动报错然后又检查了网络状态和业务进程的运行情况一切正常。整个过程从发出切换指令到业务完全恢复用了不到三十秒。如果走传统重启这个数字至少要膨胀到十分钟。再分享一个反面案例。另一次我图省事没有手动指定启动参数直接用了--reuse-cmdline结果新内核继承了旧内核里一个已经废弃的驱动参数导致驱动加载失败网络接口起不来最后只能跑机房处理。所以现在我在kexec -l这一步从来不敢偷懒启动参数宁可花两分钟仔细核对也不要切换后花半小时收拾烂摊子。这里给你一个操作清单可以当作checklist用确认新内核和initrd文件存在且完整检查当前内核启动参数决定复用还是手动指定手动指定参数时确认root设备路径正确确认预留内存区域kdump场景没有被新参数覆盖切换前保存所有重要数据通知业务方执行kexec -l后用kexec --status检查加载状态一切就绪后执行kexec -e或systemctl kexec切换完成后立刻检查内核日志和关键服务状态9. 个人经验与建议kexec虽好用但别忽视细节我用kexec已经有比较长的时间了从最开始单纯为了加快重启速度到后来结合kdump做崩溃分析再到线上内核版本的低中断升级这个工具确实帮了大忙。但我也想说kexec不是一个可以完全无脑使用的黑魔法它要求使用者对内核启动机制、内存布局和硬件状态都有足够的理解。给刚接触kexec的朋友几个建议。第一先在虚拟机或者测试机上反复练习整个加载、切换、回退流程不要一上来就在生产环境上操作。第二切换前务必备份当前内核的启动参数和根文件系统配置一旦新内核出了问题至少还能用传统方式引导回旧内核。第三如果条件允许配合串口控制台使用。串口能让你在屏幕黑掉的情况下也能看到内核启动日志这对排错来说简直是无价之宝。最后再分享一个小技巧如果你需要频繁在不同的内核版本之间切换测试建议在磁盘上保留至少两个可引导的内核版本并分别用GRUB菜单项固定好。这样一来即使kexec切换失败你重启后还能通过GRUB选择器回到原来的内核。生产环境里的安全网越多越好这个习惯能帮你避免很多低级事故。
阅读完成 · 觉得有帮助?
咨询建站