kexec
kexec (“kernel execute”) lets a running Linux kernel boot a new kernel directly, skipping the platform firmware, the Power-On Self-Test (POST), and the bootloader entirely. The mechanism splits into two halves: a load step — the
kexec_load(2)orkexec_file_load(2)system call copies a new kernel image, an optional initramfs, and a command line into memory of the still-running kernel and stages it — and an execute step —reboot(LINUX_REBOOT_CMD_KEXEC)quiesces devices and jumps straight into the staged kernel instead of resetting the machine. Because it bypasses firmware re-initialization (which on big servers can take minutes), kexec gives fast reboots, atomic kernel upgrades, and — its other major use — a pre-armed crash kernel that boots out of the wreckage of a dead kernel to capture a memory dump (see Kernel Crash Dumps and kdump). The whole feature lives inkernel/kexec*.c; this note is pinned to Linux 6.12 LTS (kernel/kexec.c at v6.12).
Mental Model
Think of kexec as a bootloader that runs inside the kernel it is replacing. A normal reboot throws away everything — the CPU resets, firmware re-runs POST, the bootloader re-reads disk, the new kernel decompresses from scratch. kexec short-circuits all of that: the currently running kernel acts as the loader. It allocates physical pages, copies the new kernel’s segments into them, and at the moment of “reboot” it does not signal the hardware to reset — it shuts down devices in software, disables interrupts, and branches the CPU directly to the new kernel’s entry point with the boot parameters already laid out in memory.
The conceptual difficulty is that the old kernel’s pages are scattered across physical RAM exactly where the new kernel needs to live. kexec solves this with a tiny relocation trampoline: the new kernel’s final segments are staged in arbitrary “source” pages during load, and a small assembly stub plus the purgatory copy each segment to its real “destination” address at the last instant, then jump. The purgatory is the only code running in the gap between the two kernels — a no-man’s-land where neither kernel’s data structures are trustworthy.
flowchart TB subgraph RUN["Running kernel (kernel A)"] SC["kexec_load(2) /<br/>kexec_file_load(2)"] --> STAGE["Stage segments:<br/>new kernel + initramfs<br/>+ cmdline + purgatory<br/>into kexec_image"] REB["reboot(LINUX_REBOOT_CMD_KEXEC)"] --> KK["kernel_kexec():<br/>shut down devices,<br/>disable IRQs"] end KK --> MK["machine_kexec():<br/>relocate segments,<br/>jump to purgatory"] MK --> PG["purgatory:<br/>verify SHA-256 of<br/>all segments"] PG -->|hashes OK| KB["kernel B entry point<br/>(decompress + start_kernel)"] PG -->|hash mismatch| HALT["spin forever<br/>(corrupted image)"] STAGE -.staged, idle.-> REB
The two phases of kexec. What it shows: the load syscall stages everything into kexec_image while kernel A keeps running normally; only when reboot(...KEXEC) fires does kernel_kexec() tear down devices and machine_kexec() hand off through the purgatory into kernel B. The insight: load and execute are decoupled — you can stage an image now and fire it minutes later (this is exactly how kdump pre-arms a crash kernel), and the purgatory’s SHA-256 check is the integrity gate guarding the handoff.
Mechanical Walk-through
The load step: staging an image
Userspace (almost always the kexec tool from kexec-tools) builds the description of the new kernel and calls one of two syscalls. The classic SYSCALL_DEFINE4(kexec_load, ...) (kernel/kexec.c) takes an entry address, a count nr_segments, an array of struct kexec_segment, and flags. Each kexec_segment (include/uapi/linux/kexec.h) is four fields — const void *buf; __kernel_size_t bufsz; (the source buffer in userspace) and const void *mem; __kernel_size_t memsz; (the physical destination and its length). With the classic syscall, userspace does all the parsing: it reads vmlinuz, lays out the segments, builds the boot parameters, and even supplies the purgatory blob. The kernel just trusts and copies. The hard cap is KEXEC_SEGMENT_MAX = 16 segments.
The flags low bits select architecture (KEXEC_ARCH_MASK is 0xffff0000; e.g. KEXEC_ARCH_X86_64 is (62 << 16), KEXEC_ARCH_AARCH64 is (183 << 16)) and behaviour: KEXEC_ON_CRASH (0x1) stages the image into the crash-reserved memory region (see kdump below) and arms it to fire automatically on panic rather than on demand; KEXEC_PRESERVE_CONTEXT (0x2) requests the suspend/resume “jump” path under CONFIG_KEXEC_JUMP.
Inside the kernel, kexec_load_check() enforces the gate. The comment is blunt: “We only trust the superuser with rebooting the system.” The check is kexec_load_permitted() (kernel/kexec_core.c):
bool kexec_load_permitted(int kexec_image_type)
{
struct kexec_load_limit *limit;
/*
* Only the superuser can use the kexec syscall and if it has not
* been disabled.
*/
if (!capable(CAP_SYS_BOOT) || kexec_load_disabled)
return false;
...
}So a caller needs the CAP_SYS_BOOT capability, and the administrator can permanently disable loading by writing 1 to the kexec_load_disabled sysctl (a one-way switch — the sysctl table only allows the 0 → 1 transition). Beyond the capability check, kexec_load_check() also calls security_kernel_load_data(LOADING_KEXEC_IMAGE, false) (so Integrity Measurement Architecture / Linux Security Modules can weigh in) and security_locked_down(LOCKDOWN_KEXEC) — under lockdown the classic kexec_load is refused outright, because it would let root boot an arbitrary unsigned kernel and so subvert Secure Boot.
The actual work happens in do_kexec_load(), serialized with kexec_trylock(). The comment explains the lock: “we need a serialization here to prevent multiple crash kernels from attempting to load simultaneously.” It allocates a struct kimage, calls the arch hook machine_kexec_prepare(), copies each segment’s bytes from userspace into freshly allocated kernel pages, copies the kernel’s vmcoreinfo note (so a crash kernel can find symbols), and finally installs the new image atomically with xchg(dest_image, image) — swapping the global kexec_image (or kexec_crash_image) pointer.
The execute step: jumping to the new kernel
Loading only stages the image; nothing runs yet. The new kernel is launched by the reboot(2) syscall with the LINUX_REBOOT_CMD_KEXEC command, which in kernel/reboot.c simply dispatches to kernel_kexec(). That function (kernel/kexec_core.c) is the heart of the handoff. Its header comment: “Move into place and start executing a preloaded standalone executable. If nothing was preloaded return an error.” The non-jump (normal) path is:
{
kexec_in_progress = true;
kernel_restart_prepare("kexec reboot");
migrate_to_reboot_cpu();
syscore_shutdown();
...
cpu_hotplug_enable();
pr_notice("Starting new kernel\n");
machine_shutdown();
}
kmsg_dump(KMSG_DUMP_SHUTDOWN);
machine_kexec(kexec_image);Reading that top to bottom: kernel_restart_prepare("kexec reboot") runs the reboot notifier chain and tells device drivers the system is going down (calling their .shutdown methods so disks flush, NICs quiesce, etc.); migrate_to_reboot_cpu() pins execution onto a single CPU; syscore_shutdown() stops core subsystems; machine_shutdown() is the arch hook that disables the other CPUs and the interrupt controllers. Then kmsg_dump(KMSG_DUMP_SHUTDOWN) flushes the kernel log, and machine_kexec(kexec_image) is the point of no return — the arch-specific assembly that relocates the segments to their final addresses and branches into the purgatory. From here the old kernel ceases to exist.
There is a subtle bug-fix encoded in the comment: migrate_to_reboot_cpu() disables CPU hotplug on the assumption that an ordinary reboot follows, but “the kexec path depends on using CPU hotplug again; so re-enable it here” via cpu_hotplug_enable().
Purgatory: the code between two kernels
machine_kexec does not jump to the new kernel directly — it jumps to the purgatory, a tiny self-contained blob staged as one of the segments. On x86 the entry is dead simple (arch/x86/purgatory/purgatory.c):
void purgatory(void)
{
int ret;
ret = verify_sha256_digest();
if (ret) {
/* loop forever */
for (;;)
;
}
}verify_sha256_digest() walks an array purgatory_sha_regions[] (one entry per loaded segment, each a {start, len} pair filled in by the kernel at load time), hashes every region with SHA-256, and compares against purgatory_sha256_digest — a digest also baked in at load time. If any segment was corrupted (a bit-flip in RAM, a stray DMA write from a dying device), the hashes mismatch and purgatory spins forever rather than jump into garbage. Both globals sit in a dedicated .kexec-purgatory ELF section. After the check passes, purgatory does any arch-specific boot-param fixup (on x86 it sets up the real-mode/boot_params handoff and, for a crash kernel, restores the “backup region” of low memory) and jumps to the new kernel’s real entry point — where ordinary boot resumes with self-decompression and start_kernel(). The purgatory is therefore the integrity firewall of kexec: it is the only thing that runs in the gap, and its single job is “don’t boot a corrupted image.”
The two load variants — and why kexec_file_load exists
The classic kexec_load has a fatal security property: it lets userspace hand the kernel arbitrary pre-parsed segments. The kernel never sees the original vmlinuz file, so it cannot check a signature on it. As Matthew Garrett pointed out, that makes kexec a trivial way to “circumvent UEFI secure boot restrictions” — boot a signed kernel, then kexec_load an unsigned one and you have laundered an untrusted kernel through a trusted boot (LWN: Reworking kexec for signatures).
The fix, introduced in Linux 3.17, is SYSCALL_DEFINE5(kexec_file_load, int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char __user *cmdline_ptr, unsigned long flags) (kernel/kexec_file.c). Instead of segments, userspace passes file descriptors for the kernel and initramfs. The kernel reads them itself with kernel_read_file_from_fd() (capped at KEXEC_FILE_SIZE_MAX), parses the kernel format with an in-kernel loader (arch_kexec_kernel_image_probe() → kexec_image_load_default()), and builds the purgatory and boot params in the kernel. As the LWN write-up puts it, this “puts the kernel in the loop so that it can … verify what is being loaded and executed.” Its flags are distinct: KEXEC_FILE_UNLOAD (0x1, drop the staged image), KEXEC_FILE_ON_CRASH (0x2), KEXEC_FILE_NO_INITRAMFS (0x4, reuse the running initramfs), KEXEC_FILE_DEBUG (0x8).
Because the kernel now holds the original file, it can verify a cryptographic signature on it before staging. Under CONFIG_KEXEC_SIG, kimage_validate_signature() runs:
ret = kexec_image_verify_sig(image, image->kernel_buf, image->kernel_buf_len);
if (ret) {
if (sig_enforce) {
pr_notice("Enforced kernel signature verification failed (%d).\n", ret);
return ret;
}
/*
* If IMA is guaranteed to appraise a signature on the kexec
* image, permit it even if the kernel is otherwise locked
* down.
*/
if (!ima_appraise_signature(READING_KEXEC_IMAGE) &&
security_locked_down(LOCKDOWN_KEXEC))
return -EPERM;
pr_debug("kernel signature verification failed (%d).\n", ret);
}
return 0;The verification uses the same machinery as module signing — the PE/COFF signature embedded in the kernel binary, checked against the kernel’s keyring. sig_enforce is static bool sig_enforce = IS_ENABLED(CONFIG_KEXEC_SIG_FORCE); — so with CONFIG_KEXEC_SIG_FORCE (or after set_kexec_sig_enforced(), which lockdown calls) a failed signature is fatal, full stop. Without forcing, a bad signature still passes unless the system is locked down and IMA will not separately appraise the image — in which case -EPERM. The net effect: on a Secure-Boot/lockdown system, the classic kexec_load is blocked and only the signature-checked kexec_file_load survives, so the trust chain holds.
Uncertain
Verify: that on s390
kexec_file_loadperforms in-kernel signature verification even withoutCONFIG_KEXEC_SIG(s390 historically wired verification into its own image loader). Reason: not directly addressed by the v6.12kexec_file.cexcerpt consulted; arch loaders differ. To resolve: readarch/s390/kernel/machine_kexec_file.cat v6.12 and thearch_kexec_kernel_verify_sighook. uncertain
The kexec-tools userspace
In practice nobody calls the syscalls directly; the kexec command from kexec-tools does it (kexec(8)). The canonical two-step:
# 1. Load (stage) a new kernel + initramfs + cmdline into the running kernel
kexec -l /boot/vmlinuz-6.12.0 \
--initrd=/boot/initramfs-6.12.0.img \
--reuse-cmdline
# 2. Execute it — jump straight in, skipping firmware
kexec -e-l/--load stages the image (this is the syscall); -e/--exec fires reboot(LINUX_REBOOT_CMD_KEXEC) without running the normal shutdown scripts — so on a real system you usually let systemctl kexec (or reboot integrated with kexec) handle the clean userspace teardown first. -u/--unload drops a staged image. --reuse-cmdline copies the running kernel’s command line (stripping crashkernel= and BOOT_IMAGE=); --append= adds parameters; --command-line= replaces it wholesale. -p/--load-panic stages a crash kernel (the KEXEC_ON_CRASH/KEXEC_FILE_ON_CRASH path — see kdump). -t/--type= names the image format (e.g. bzImage-x86).
Crucially for Secure Boot, the tool chooses the syscall: -c/--kexec-syscall forces the classic kexec_load, -s/--kexec-file-syscall forces kexec_file_load, and the default -a/--kexec-syscall-auto tries kexec_file_load first and falls back. The man page is explicit: “KEXEC_FILE_LOAD is required on systems that use locked-down secure boot to verify the kernel signature.”
Relationship to kdump (crash capture)
The most widespread production use of kexec is kdump — and the mechanics are entirely kexec, just pre-armed. The full treatment lives in Kernel Crash Dumps and kdump; the kexec-side summary: the production kernel boots with crashkernel= on its command line (e.g. crashkernel=256M), which reserves a chunk of physical memory the running kernel will never use. A capture kernel is then staged into that reserved region with kexec -p (the KEXEC_ON_CRASH flag), and kexec_image for crashes is held in the separate kexec_crash_image pointer. Nothing runs — it sits idle. When the production kernel panics, panic() calls crash_kexec(), which (skipping the orderly kernel_kexec() device-shutdown dance, since the kernel is already dead) jumps via machine_kexec() straight into the reserved capture kernel. That capture kernel boots in its tiny reserved region, exposes the dead kernel’s RAM as the ELF file /proc/vmcore, and a tool like makedumpfile writes the dump for later analysis with crash or gdb (kdump documentation). The reason kexec is the right primitive here: the crash kernel must boot without trusting any of the dead kernel’s data or re-initializing hardware that holds the forensic state — exactly what kexec’s firmware-skipping, pre-staged jump provides.
Failure Modes
kexec_loadreturns-EPERMunder Secure Boot. On a locked-down kernel the classic syscall is refused (security_locked_down(LOCKDOWN_KEXEC)). The fix is to usekexec_file_load(kexec -s) with a properly signed kernel — and withCONFIG_KEXEC_SIG_FORCE, an unsigned or wrongly-signed kernel yields “Enforced kernel signature verification failed” indmesg.- The new kernel hangs or the machine resets instead of kexec-ing. kexec depends on every driver’s
.shutdownmethod correctly quiescing its device; a device left mid-DMA can corrupt the staged image (caught by purgatory’s SHA-256 — symptom: a hang in purgatory) or scribble on the new kernel after handoff. Devices that need firmware re-init that only POST provides (some GPUs, exotic NICs) may not come back. This is the classic kexec fragility: skipping firmware means skipping firmware’s device re-initialization. kexec_load_disabledalready set. If a hardening profile wrote1to the sysctl, every load fails with-EPERMand cannot be re-enabled without a real reboot.-EADDRNOTAVAIL/ segment overlap. Destinationmemaddresses must be page-aligned and must not collide with the running kernel’s reserved regions; a hand-rolledkexec_loadcaller that miscomputes the layout gets rejected.- No
crashkernel=reservation.kexec -pfails if the production kernel never reserved memory; the capture kernel has nowhere to live. (kdump-specific; see Kernel Crash Dumps and kdump.)
Alternatives and When to Choose Them
A firmware reboot (reboot(2) with LINUX_REBOOT_CMD_RESTART) is the safe default: it re-runs POST, re-initializes every device cleanly, and works regardless of driver .shutdown quality — at the cost of minutes of POST on enterprise hardware and a full cold boot. Choose kexec when that downtime is the bottleneck (kernel security updates across a fleet) and your hardware/drivers tolerate the firmware skip.
Live patching (kpatch/livepatch) replaces individual kernel functions in a running kernel with no reboot at all — far less disruptive than kexec, but limited to small fixes (you cannot change data structures or swap the whole kernel). kexec is the tool when you need a genuinely different kernel.
The emerging frontier is Kexec HandOver (KHO), merged for Linux 6.16 (LWN, 2025; kernel KHO docs), and the Live Update Orchestrator (LUO) layered on top of it (LWN, 2025). Plain kexec discards all of the old kernel’s RAM contents; KHO lets a subsystem preserve specific memory across the jump — e.g. kho_preserve_folio() saves a folio that kho_restore_folio() recovers in the new kernel. The motivating use case is updating a virtualization host’s kernel while running guests’ memory, IOMMU state, and VFIO assignments survive untouched — turning kexec from “fast cold reboot” into “live kernel swap.” These are 6.16-era and beyond, not in the 6.12 LTS this note pins.
Uncertain
Verify: that KHO landed in mainline 6.16 (versus an earlier/later release) and LUO’s merge status as of mid-2026. Reason: dated to LWN coverage from 2025 and a Phoronix “looks like it might be ready for 6.16” headline — not confirmed against a tagged 6.16 tree. To resolve: check the 6.16 changelog /
MAINTAINERSfor KHO and the LUO series status on lore.kernel.org. uncertain
Production Notes
Oracle documents kexec as a routine fast-reboot tool for kernel updates, where skipping firmware “can save several minutes” on large servers (Oracle Linux blog). On modern distributions the integration is via systemd: systemctl kexec performs an orderly userspace shutdown and then the kexec jump, so it is a drop-in faster reboot rather than a raw kexec -e. The dominant deployment is still kdump: virtually every production server pre-arms a crash kernel so that a panic produces a forensic /proc/vmcore instead of a silent reboot (Opensource.com: kdump usage and internals). The newest direction — KHO/LUO — is being driven by hyperscale virtualization operators who want to patch the host kernel without migrating or pausing guest VMs, which makes “reboot the kernel under a live workload” a first-class operation rather than a curiosity.
See Also
- Kernel Crash Dumps and kdump — the dominant use of kexec: a pre-armed crash kernel boots from a dead kernel to capture
/proc/vmcore - Kernel Lockdown Mode — why classic
kexec_loadis blocked under lockdown (LOCKDOWN_KEXEC) - Secure Boot and the Kernel Trust Chain — the chain
kexec_file_load’s signature check exists to protect - Kernel Decompression — what the new kernel does after purgatory hands off (self-extraction, then
start_kernel()) - The initramfs and initrd — the second thing kexec stages alongside the kernel
- Linux Boot and Init MOC — parent map (§8 Trust, Recovery, and Alternate Boot Paths)