[PATCH v2] PM: hibernate: exclude unusable free pages from image preallocation
From: Nhat-Trieu Huynh-Pham
Date: Thu Oct 01 2026 - 04:41:03 EST
hibernate_preallocate_memory() derives the number of page frames to
preallocate for the image from the per-zone NR_FREE_PAGES counters. Those
counters count every free pageblock regardless of its migratetype,
including MIGRATE_HIGHATOMIC and MIGRATE_CMA pageblocks. Such pages are
free from the buddy allocator's point of view, but they cannot be used by
the order-0, non-movable, non-reserve GFP_KERNEL allocations with which the
image is preallocated (GFP_IMAGE = GFP_KERNEL|__GFP_NOWARN).
(*) Reported free vs usable free:
The mm subsystem already accounts for this in
__zone_watermark_unusable_free() [1]. For order-0 GFP_KERNEL neither
ALLOC_RESERVES nor ALLOC_CMA is set, so the allocator subtracts the free
high-order atomic [2], and the free CMA pages from the free count before
checking the watermarks. The hibernation preallocation, however, computes
its target from the unadjusted NR_FREE_PAGES value, so it asks for about
half of the unusable pages more than the allocator can deliver. On arm64
(no CONFIG_HIGHMEM) the highmem fallback in the failure path is a no-op,
hence even a small shortfall aborts hibernation, see [4] for example.
(*) Why those pages are not usable by GFP_KERNEL:
(**) CMA is reserved for movable allocations, so GFP_KERNEL has no
ALLOC_CMA, and MIGRATE_CMA is not in the unmovable fallback list. Free CMA
pages are therefore never handed to these allocations.
(**) The high-order atomic reserve is only handed out to atomic, order > 0,
__GFP_HIGH allocations (ALLOC_HIGHATOMIC), which GFP_KERNEL order-0 is not.
These pages can be unreserved under memory pressure [3], but only all but
one pageblock per zone and only after an allocation has already failed, so
counting them as available still overestimates what the preallocation can
obtain without failing first. Excluding them is conservative.
(*) Consequence of not excluding the unusable pages:
With M = managed, S = saveable, F = free, U = highatomic + CMA and
R = reclaimable, count = S + F - totalreserve and alloc is about count / 2,
while the allocator can actually provide about (F - U) + R pages. The
shortfall is therefore inflated by exactly U compared with the usable
memory.
>From [4], with U = 11430 unusable free pages (5120 highatomic + 6310 CMA),
the preallocation target is inflated by about U / 2 = 5715 pages, while the
allocator ends up 1247 pages short and hibernation is aborted even though
the system is able to create the image.
(*) What changes after excluding the unusable pages:
Subtract U from the per-zone free accounting used to compute count and
avail_normal. Then count' = count - U and, since max_size is roughly
count / 2, max_size' = max_size - U / 2 and alloc' = alloc - U / 2.
max_size' remains well above minimum_image_size() in practice, so the
resulting image is effectively unchanged while hibernation no longer aborts
unnecessarily.
Testing
=======
(*) SA6155P running on AAOS with S2D feature
(*) backport locally on GKI 6.1
References
==========
[1] commit f27ce0e14088 ("page_alloc: consider highatomic reserve in watermark fast")
[2] commit c928807f6f6b ("mm/page_alloc: keep track of free highatomic")
[3] commit ac3f3b0a5551 ("mm: page_alloc: unreserve highatomic page blocks before oom")
[4] Example of an abort:
Mem-Info:
free:344621 free_pcp:931 free_cma:6310
DMA32 free:973864kB reserved_highatomic:8192KB free_cma:25240kB
Normal free:404620kB reserved_highatomic:12288KB free_cma:0kB
PM: hibernation: Image allocation is 1247 pages short
Signed-off-by: Nhat-Trieu Huynh-Pham <trieu2.huynh@xxxxxxx>
---
Changes since v1:
(*) Add a reference to commit c928807f6f6b, and correct the words and
quotes in the description as well.
(*) Switched from zone->nr_reserved_highatomic to
READ_ONCE(zone->nr_free_highatomic) to accurately fetch the active free
highatomic counter, reflecting c928807f6f6b (Reported by: Sashiko).
Link to v1:
(*) https://lore.kernel.org/lkml/20260921101446.1193565-1-trieu2.huynh@xxxxxxx/
kernel/power/snapshot.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index b209712cb2c3..195db45ce80f 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -1867,11 +1867,32 @@ int hibernate_preallocate_memory(void)
highmem = save_highmem;
size = 0;
for_each_populated_zone(zone) {
+ unsigned long free, unusable;
+
size += snapshot_additional_pages(zone);
+
+ /*
+ * Pages reserved for high-order atomic allocations
+ * (MIGRATE_HIGHATOMIC) and free CMA pages cannot be used by
+ * the order-0 non-movable GFP_KERNEL allocations that
+ * preallocate the image, so do not count them as available.
+ * This mirrors __zone_watermark_unusable_free().
+ */
+ free = zone_page_state(zone, NR_FREE_PAGES);
+ unusable = READ_ONCE(zone->nr_free_highatomic);
+#ifdef CONFIG_CMA
+ unusable += zone_page_state(zone, NR_FREE_CMA_PAGES);
+#endif
+ /*
+ * nr_reserved_highatomic counts whole reserved pageblocks,
+ * so it can exceed the actual free pages.
+ */
+ unusable = min(unusable, free);
+
if (is_highmem(zone))
- highmem += zone_page_state(zone, NR_FREE_PAGES);
+ highmem += free - unusable;
else
- count += zone_page_state(zone, NR_FREE_PAGES);
+ count += free - unusable;
}
avail_normal = count;
count += highmem;
--
2.43.0