Hibernation Suspend-to-Disk

Hibernation (also called suspend-to-disk, swsusp for “software suspend”, or ACPI sleep state S4) is the deepest Linux sleep state: it takes a complete snapshot image of everything in RAM, writes that image to a swap device, and powers the machine all the way off. Because the image lives on persistent storage, hibernation survives total power loss — you can pull the battery — and resumes the exact prior session on the next boot, when the freshly-booted kernel detects the image, loads it back into memory, and jumps into the saved state. The price is latency: writing and reading gigabytes through swap is far slower than suspend-to-RAM or s2idle, which keep RAM powered. The mechanism is implemented across three files — kernel/power/hibernate.c (the orchestrator), kernel/power/snapshot.c (the in-memory image), and kernel/power/swap.c (the on-disk I/O) — and every claim here is pinned to Linux 6.12 LTS source.

The defining trick of hibernation is the atomic copy: you cannot reliably write RAM to disk while RAM is still changing, so the kernel first quiesces the system (freezes tasks, suspends devices, stops all but one CPU, disables interrupts), then makes an in-memory copy of every in-use page into spare RAM with nothing else running, then re-enables everything and writes that frozen copy out at leisure. The snapshot is taken in a single-CPU, interrupts-off critical section; the slow disk write happens afterward with the system briefly alive again. Understanding hibernation is mostly understanding why that ordering is necessary and how it is achieved.


Mental Model — Snapshot Now, Write Later, Restore on Boot

The cleanest way to think about hibernation is as three phases separated by two power events. Phase 1 (snapshot): while the machine is up, freeze everything and copy in-use RAM into a reserved region of RAM. Phase 2 (write + power off): wake the system just enough to stream that copy through the swap device, mark the swap header so a future boot can find it, and power off. Phase 3 (resume), on the next boot: a normal kernel boots, notices a hibernation image on the resume device, freezes its own (nearly empty) userspace, reads the image back over the booted kernel’s pages, and transfers control into the restored image — at which point the machine is the pre-hibernation session again.

flowchart TB
  subgraph P1["Phase 1 — Snapshot (system up)"]
    direction TB
    A["hibernate()"] --> B["pm_notifier(PM_HIBERNATION_PREPARE)"]
    B --> C["freeze_processes()<br/>freeze_kernel_threads()"]
    C --> D["hibernation_snapshot():<br/>preallocate image RAM<br/>dpm_suspend(devices)"]
    D --> E["create_image():<br/>1 CPU, IRQs off<br/>swsusp_arch_suspend()<br/>= atomic copy of in-use pages"]
  end
  subgraph P2["Phase 2 — Write & power off"]
    direction TB
    F["swsusp_write():<br/>stream copy -> swap<br/>(LZO/LZ4 compressed)"]
    G["mark_swapfiles():<br/>write S1SUSPEND signature<br/>+ image start sector"]
    H["power_down():<br/>kernel_power_off() / reboot / platform"]
    F --> G --> H
  end
  subgraph P3["Phase 3 — Resume (next boot)"]
    direction TB
    I["fresh kernel boots,<br/>resume= device"]
    J["software_resume():<br/>swsusp_check() finds S1SUSPEND"]
    K["freeze_processes()<br/>load_image_and_restore()"]
    L["swsusp_arch_resume():<br/>copy image back, jump in"]
    I --> J --> K --> L
  end
  E --> F
  H -.->|"power loss survivable"| I

The three phases of hibernation and the two power events between them. What it shows: the snapshot is built entirely in RAM under a single-CPU, interrupts-off critical section (Phase 1); only afterward is it streamed to swap and the machine powered off (Phase 2); a completely separate boot later detects and restores it (Phase 3). The insight to take: the expensive, fragile part — copying live memory — is done atomically in RAM with nothing else running, decoupling correctness (the atomic copy) from throughput (the slow disk write that follows). The dashed arrow is hibernation’s whole reason for existing over suspend-to-RAM: the gap between power-off and resume can include total power loss.


Mechanical Walk-through

1. hibernate() — the orchestrator

The userspace trigger (echo disk > /sys/power/state, or systemctl hibernate) lands in hibernate() in kernel/power/hibernate.c. The core sequence:

error = pm_notifier_call_chain_robust(PM_HIBERNATION_PREPARE,
                                      PM_POST_HIBERNATION);
if (error) goto Restore;
 
ksys_sync_helper();
error = freeze_processes();
 
error = create_basic_memory_bitmaps();
error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM);
 
if (in_suspend) {
        error = swsusp_write(flags);
        swsusp_free();
        if (!error) {
                if (hibernation_mode == HIBERNATION_TEST_RESUME)
                        snapshot_test = true;
                else
                        power_down();
        }
}

Step by step. First it fires the PM notifier chain with PM_HIBERNATION_PREPARE, giving every registered subsystem a chance to quiesce or veto (the _robust variant guarantees the matching PM_POST_HIBERNATION runs even on failure). Then ksys_sync_helper() flushes filesystem buffers to disk — essential, because the image-on-disk and filesystem-on-disk must be mutually consistent. Then freeze_processes() brings userspace to a halt (see The Freezer and Freezing Tasks). create_basic_memory_bitmaps() allocates the bitmaps that track which page frames are saveable. The heart is hibernation_snapshot(), which builds the in-RAM image. The flag in_suspend is the cleverest part of the whole design: it is 1 while the snapshot is being created and 0 after the image has been restored — the same code path (swsusp_arch_suspend) returns “twice”, once when the snapshot is taken and again when it is restored, distinguished only by this flag. So if (in_suspend) runs only on the snapshot side: write the image with swsusp_write(), free the snapshot’s working memory, and power_down().

2. hibernation_snapshot() — suspend devices, then snapshot

int hibernation_snapshot(int platform_mode)
{
	pm_message_t msg;
	int error;
 
	pm_suspend_clear_flags();
	error = platform_begin(platform_mode);
	if (error) goto Close;
 
	/* Preallocate image memory before shutting down devices. */
	error = hibernate_preallocate_memory();
	if (error) goto Close;
 
	error = freeze_kernel_threads();
	if (error) goto Cleanup;
	...
	error = dpm_prepare(PMSG_FREEZE);
	...
	suspend_console();
	pm_restrict_gfp_mask();
	error = dpm_suspend(PMSG_FREEZE);
 
	if (error || hibernation_test(TEST_DEVICES))
		platform_recover(platform_mode);
	else
		error = create_image(platform_mode);
	...
}

hibernate_preallocate_memory() reserves the spare RAM the atomic copy will write into — done before devices are suspended, while allocation still works normally. freeze_kernel_threads() then stops the freezable kernel threads too (hibernation freezes more than plain suspend, because the atomic copy runs with kernel threads stopped). dpm_prepare() and dpm_suspend() drive every device through its ->prepare and ->suspend PM callbacks in dependency order (see System Suspend and Resume Phases), and suspend_console() quiets the console so no log output mutates memory during the copy. With devices down, it calls create_image().

3. create_image() — the atomic copy

This is the single-CPU, interrupts-off critical section that defines hibernation:

static int create_image(int platform_mode)
{
	int error;
 
	error = dpm_suspend_end(PMSG_FREEZE);   /* devices: suspend_late + suspend_noirq */
	...
	error = pm_sleep_disable_secondary_cpus();   /* take all but one CPU offline */
	...
	local_irq_disable();
	system_state = SYSTEM_SUSPEND;
	error = syscore_suspend();                    /* core system devices down */
	...
	if (hibernation_test(TEST_CORE) || pm_wakeup_pending())
		goto Power_up;
 
	in_suspend = 1;
	save_processor_state();
	error = swsusp_arch_suspend();                /* <-- the atomic copy happens here */
	/* control "returns here" both after snapshot AND after restore */
	restore_processor_state();
	...
	if (!in_suspend) {                            /* only true on the RESTORE side */
		events_check_enabled = false;
		clear_or_poison_free_pages();
	}
	...
 Power_up:
	syscore_resume();
 Enable_irqs:
	system_state = SYSTEM_RUNNING;
	local_irq_enable();
 Enable_cpus:
	pm_sleep_enable_secondary_cpus();
	...
}

dpm_suspend_end() runs the late and no-IRQ device phases. pm_sleep_disable_secondary_cpus() hot-unplugs every CPU except the boot CPU, so the copy runs single-threaded — no other CPU can mutate memory. local_irq_disable() and syscore_suspend() stop interrupts and core devices. Now in_suspend = 1 is set, the CPU registers are saved with save_processor_state(), and swsusp_arch_suspend() performs the architecture-specific atomic copy: it invokes the generic swsusp_save() to copy every saveable page into the preallocated spare pages. When the snapshot is complete, control falls through, restore_processor_state() runs, and because in_suspend is still 1 the if (!in_suspend) block is skipped. The system then re-enables syscore, interrupts, and the secondary CPUs and unwinds back up through hibernation_snapshot(). The genius is that on a future resume, swsusp_arch_resume() will restore exactly this register/stack state, so execution appears to “return from swsusp_arch_suspend()” again — but this time with in_suspend == 0, which is how the restored kernel knows it is the restore side and runs clear_or_poison_free_pages().

4. swsusp_save() — counting and copying pages

The actual page copy lives in kernel/power/snapshot.c:

asmlinkage __visible int swsusp_save(void)
{
	unsigned int nr_pages, nr_highmem;
 
	pr_info("Creating image:\n");
	drain_local_pages(NULL);
	nr_pages = count_data_pages();
	nr_highmem = count_highmem_pages();
	pr_info("Need to copy %u pages\n", nr_pages + nr_highmem);
	...
	nr_copy_pages = copy_data_pages(&copy_bm, &orig_bm, &zero_bm);
	...
}

count_data_pages() and count_highmem_pages() tally the saveable pages — those not in the forbidden_pages_map (kernel-reserved, nosave regions, device memory) and not in the free_pages_map (free at hibernation time, no need to save). copy_data_pages() then copies each saveable page into a spare page tracked in copy_bm. An optimisation: pages that are entirely zero are recorded in a separate zero_bm bitmap and not copied at all, shrinking the image. The classification logic in saveable_page() is what keeps the image correct — copy a page you shouldn’t (e.g. a DMA buffer) and the restored system is corrupt; skip one you should copy and you lose state.

5. swsusp_write() — streaming to swap

Back on the snapshot side, swsusp_write() in kernel/power/swap.c streams the in-RAM copy to the swap device. It first writes a header page (struct swsusp_info) describing the image, then either save_image() (uncompressed) or save_compressed_image() based on the SF_NOCOMPRESS_MODE flag. The image is laid out on swap as a linked list of map pages:

struct swap_map_page {
	sector_t entries[MAP_PAGE_ENTRIES];
	sector_t next_swap;
};

Each swap_map_page holds an array of sector numbers (where the data pages were written) and a pointer to the next map page — a chain the resume code walks to find every page. Compression runs in parallel: save_compressed_image() spins up multiple worker threads (nr_threads = clamp_val(num_online_cpus() - 1, 1, CMP_THREADS)), each running crypto_comp_compress() with the configured algorithm, plus a separate CRC32 thread that checksums the uncompressed data so corruption is caught on resume.

6. mark_swapfiles() — planting the signature

The final write turns an ordinary swap header into a hibernation marker:

static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
{
	...
	if (!memcmp("SWAP-SPACE", swsusp_header->sig, 10) ||
	    !memcmp("SWAPSPACE2", swsusp_header->sig, 10)) {
		memcpy(swsusp_header->orig_sig, swsusp_header->sig, 10);
		memcpy(swsusp_header->sig, HIBERNATE_SIG, 10);
		swsusp_header->image = handle->first_sector;
		...
		error = hib_submit_io(REQ_OP_WRITE | REQ_SYNC,
				      swsusp_resume_block, swsusp_header, NULL);
	}
	...
}

The swap device normally carries the signature "SWAP-SPACE"/"SWAPSPACE2" in its header. Hibernation saves that original into orig_sig and overwrites the live sig field with HIBERNATE_SIG, which is the string "S1SUSPEND", and records handle->first_sector (the start of the image) in the image field. This one block-write is the breadcrumb a future boot uses to know “this swap area contains a hibernation image, here is where it starts.”

7. power_down() — actually turning off

static void power_down(void)
{
	...
	switch (hibernation_mode) {
	case HIBERNATION_REBOOT:
		kernel_restart(NULL);
		break;
	case HIBERNATION_PLATFORM:
		error = hibernation_platform_enter();
		...
		fallthrough;
	case HIBERNATION_SHUTDOWN:
		if (kernel_can_power_off())
			kernel_power_off();
		break;
	}
	kernel_halt();
	...
}

Depending on the mode selected in /sys/power/disk (next section), this powers the machine off (shutdown), reboots into resume (reboot), or uses ACPI platform support to enter S4 with firmware involvement (platform). There is also a HIBERNATION_SUSPEND case (handled earlier in the function) that writes the image and then enters suspend-to-RAM — a hybrid: if power holds you resume instantly from RAM, but if power is lost the disk image still saves you.

8. Resume — software_resume() on the next boot

On a later, ordinary boot, software_resume() runs (triggered by the resume= kernel parameter or a noresume-less default). It finds the resume device (find_resume_device() resolves resume= to a dev_t), then calls swsusp_check():

int swsusp_check(bool exclusive)
{
	...
	error = hib_submit_io(REQ_OP_READ, swsusp_resume_block,
				swsusp_header, NULL);
	...
	if (!memcmp(HIBERNATE_SIG, swsusp_header->sig, 10)) {
		memcpy(swsusp_header->sig, swsusp_header->orig_sig, 10);
		swsusp_header_flags = swsusp_header->flags;
		error = hib_submit_io(REQ_OP_WRITE | REQ_SYNC,
					swsusp_resume_block,
					swsusp_header, NULL);
	} else {
		error = -EINVAL;
	}
	...
}

It reads the swap header and checks for HIBERNATE_SIG ("S1SUSPEND"). If found, it immediately restores the original swap signature (copying orig_sig back over sig and writing it out) — so that if the resume itself crashes, the swap area reverts to ordinary swap and won’t trap the system in a resume loop. It records the image flags (which compression algorithm, CRC mode). Then software_resume() freezes its own (essentially empty) userspace with freeze_processes() and freeze_kernel_threads(), and load_image_and_restore() reads the image chain back into the right page frames and calls swsusp_arch_resume() to restore CPU state and jump into the saved image — the “second return” from swsusp_arch_suspend() described in step 3, now with in_suspend == 0. From that instant the booted resume kernel is overwritten by the hibernated session and execution continues as if the machine had never been off.


The /sys/power/disk Modes — Verified Against v6.12

/sys/power/disk selects what power_down() does after the image is written. The modes are not the stale testproc/test list that some older in-tree ABI docs still show. At the v6.12 tag, the array and its enum in hibernate.c are exactly:

enum {
	HIBERNATION_INVALID,
	HIBERNATION_PLATFORM,
	HIBERNATION_SHUTDOWN,
	HIBERNATION_REBOOT,
#ifdef CONFIG_SUSPEND
	HIBERNATION_SUSPEND,
#endif
	HIBERNATION_TEST_RESUME,
	__HIBERNATION_AFTER_LAST
};
 
static const char * const hibernation_modes[] = {
	[HIBERNATION_PLATFORM]		= "platform",
	[HIBERNATION_SHUTDOWN]		= "shutdown",
	[HIBERNATION_REBOOT]		= "reboot",
#ifdef CONFIG_SUSPEND
	[HIBERNATION_SUSPEND]		= "suspend",
#endif
	[HIBERNATION_TEST_RESUME]	= "test_resume",
};

So the values you can write to /sys/power/disk on a v6.12 kernel are platform, shutdown, reboot, suspend (only if CONFIG_SUSPEND is set), and test_resume — with the currently-selected one shown in [brackets]. There is no testproc and no bare test mode in v6.12.

  • platform — finish with ACPI/firmware help (hibernation_platform_enter()), letting the platform put devices into their lowest states and properly enter S4. The richest mode where firmware supports it.
  • shutdown — the common default: write the image, then kernel_power_off(). Plain and portable.
  • reboot — write the image, then reboot (used on some architectures where a reboot is the cleanest way to reach resume).
  • suspend — the hybrid sleep mode: write the disk image, then enter suspend-to-RAM. Fast resume from RAM normally, disk image as a safety net against power loss.
  • test_resume — a debugging mode that takes the snapshot, writes it, then immediately resumes from it without powering off, to test the full save/restore round-trip in one boot.

Two related knobs live alongside it. /sys/power/image_size sets the preferred maximum image size in bytes; its default is computed at init as two-fifths of total RAM:

void __init hibernate_image_size_init(void)
{
	image_size = ((totalram_pages() * 2) / 5) * PAGE_SIZE;
}

Setting image_size smaller makes the kernel work harder (freeing more pages, shrinking caches) to keep the image under the limit, trading snapshot time for a smaller disk write; setting it to 0 requests the smallest possible image. /sys/power/reserved_size reserves bytes for allocations device drivers make from their freeze callbacks so those allocations don’t fail mid-snapshot.


Image Compression — LZO by Default, LZ4 Optional

Hibernation compresses the image through the kernel crypto API. The algorithm is a build-time choice with a runtime override. The Kconfig at v6.12 defines a choice defaulting to LZO:

choice
	prompt "Default compressor"
	default HIBERNATION_COMP_LZO
	depends on HIBERNATION

config HIBERNATION_COMP_LZO
	bool "lzo"
	depends on CRYPTO_LZO

config HIBERNATION_COMP_LZ4
	bool "lz4"
	depends on CRYPTO_LZ4
endchoice

config HIBERNATION_DEF_COMP
	string
	default "lzo" if HIBERNATION_COMP_LZO
	default "lz4" if HIBERNATION_COMP_LZ4

So the default compressor is LZO unless the kernel was configured for LZ4. The two named string constants in hibernate.c are COMPRESSION_ALGO_LZO = "lzo" and COMPRESSION_ALGO_LZ4 = "lz4". At runtime the active algorithm can be overridden via the hibernate.compressor= kernel parameter (or the hib_comp_algo machinery), and the choice is recorded in the on-disk image flags (SF_COMPRESSION_ALG_LZO / SF_COMPRESSION_ALG_LZ4) so resume decompresses with the matching algorithm. LZO favours speed and low memory; LZ4 typically decompresses faster, which matters because resume reads and decompresses the entire image. The trade-off is between snapshot/restore wall-clock time and the size of the disk write.

Uncertain

Verify: the exact spelling and parsing of the runtime override (hibernate.compressor= boot parameter vs a /sys attribute) in v6.12, and whether the older LZO-only CONFIG_CRYPTO_LZO-based path is fully replaced by the crypto-API hib_comp_algo selection in this release. Reason: the hibernate.c source shows hibernate_compressor[] initialised from CONFIG_HIBERNATION_DEF_COMP and a hib_comp_algo global, and the LZO/LZ4 string constants, but I did not trace the full boot-parameter handler that writes hib_comp_algo end-to-end at the v6.12 tag. To resolve: read the hibernate_compressor/hib_comp_algo setup and the __setup/module_param handler in kernel/power/hibernate.c at v6.12. The default = LZO claim is source-verified (Kconfig above). uncertain


Failure Modes and Gotchas

  • “PM: Image mismatch” / resume aborts. The resume kernel and the hibernated kernel must be compatible — same kernel version and memory layout, and the image’s header checks must pass. A kernel upgrade between hibernate and resume typically invalidates the image; the kernel refuses it (and, because swsusp_check() already restored the original swap signature, you boot fresh instead of looping).
  • Swap too small. If the swap device cannot hold the image (default target two-fifths of RAM, but the minimum image can be larger under memory pressure), swsusp_write() fails and hibernation aborts. Swap must be at least as large as the image you will produce; the common rule of thumb is swap ≥ RAM for reliable hibernation, larger than the image_size target.
  • Freeze fails first. Hibernation freezes both userspace and kernel threads. A task that refuses to freeze (see The Freezer and Freezing Tasks) aborts the hibernation before any snapshot is taken — look for “Freezing of tasks failed” in dmesg.
  • resume= not set / wrong device. If the bootloader/initramfs doesn’t pass the correct resume= device (or passes noresume), the fresh boot never calls swsusp_check() on the right device, silently ignores the image, and boots normally — losing the hibernated session. On systemd systems the resume hook and the RESUME UUID must match the swap holding the image.
  • Secure Boot / lockdown. Under kernel lockdown (often enabled with UEFI Secure Boot), hibernation may be blocked because an unsigned, attacker-controlled image could be used to inject code into the resumed kernel. This is a deliberate security restriction, not a bug.
  • The “double return” confuses debuggers. Because swsusp_arch_suspend() appears to return twice (once on snapshot, once on restore), a stack trace taken near the atomic copy can look impossible. The in_suspend flag is the only reliable way to tell which side you are on.

Alternatives and When to Choose Them

  • Suspend-to-RAM (S3) — keep RAM powered, resume in a second or two. Choose for everyday “close the lid” on a laptop with battery; cannot survive total power loss. Far faster than hibernation but burns a trickle of power.
  • Suspend-to-Idle s2idle — the pure-software shallow suspend that dominates modern laptops where firmware S3 is absent. Lowest resume latency, least power saved of the RAM-retaining states.
  • Hibernation (this note) — choose when you must survive complete power loss (battery removal, multi-day off, desktop with no battery), or want zero standby power draw. Slowest to enter and resume.
  • Hybrid sleep (/sys/power/disk = suspend) — the best-of-both compromise: write the disk image and suspend to RAM. Instant resume from RAM if power holds, disk fallback if it doesn’t. Pays the cost of writing the image every time.

The decision is governed by Linux System Sleep States and selected through The sys-power sysfs Interface (/sys/power/state chooses disk vs mem; /sys/power/disk chooses the hibernation finish mode; /sys/power/mem_sleep chooses the suspend-to-RAM depth).


See Also