mm/mm_init: fix out-of-range first_deferred_pfn
PATCH v2 1/1 mm/mm_init: fix out-of-range first_deferred_pfn
With CONFIG_DEFERRED_STRUCT_PAGE_INIT enabled, deferred_grow_zone() undefers struct page ranges early in boot to satisfy an allocation. With a large CMA reservation in place, the ranges it finds may not add up to the allocation it was asked for, and we end up undeferring all available RAM and still fall short. That is fine in itself: the function accounts for it and leaves the caller to decide whether it now has enough memory.
The function also remembers where undeferring is to continue next, in pgdat->first_deferred_pfn, and it walks in PAGES_PER_SECTION (128M) chunks. If the node's RAM does not end 128M aligned and we undeferred everything, that "next" points past the end of the node's RAM.
deferred_init_memmap() later picks up from first_deferred_pfn and hits a BUG_ON(), because it expects a pfn within its node:
kernel BUG at mm/mm_init.c:2131!
CPU: 3 UID: 0 PID: 36 Comm: pgdatinit0 Not tainted 7.2.0-rc6 #1 RIP: 0010:deferred_init_memmap+0x1b8/0x1c0 RAX: 0000000000236000 R13: 0000000000238000 Call Trace: kthread+0xdf/0x120 ret_from_fork+0x187/0x250
Detect the end of RAM in deferred_grow_zone() instead of assuming that more deferral is always available. When the walk leaves the zone (which is where this node's RAM ends), record ULONG_MAX, the value that tells deferred_init_memmap() the memory map is already initialized.
To reproduce with CONFIG_DEFERRED_STRUCT_PAGE_INIT=y and CONFIG_CMA=y:
qemu-system-x86_64 -enable-kvm -m 8032M -kernel bzImage \
-append "nokaslr cma=4768M@0x100000000"The above command panics on every boot without this patch and boots reliably with it applied.
---
Notes:
Changes since v1: changelog rewritten for readability per review feedback. No functional change; the diff is byte-identical to v1.
v1: https://lore.kernel.org/linux-mm/[email protected]/
Applies unchanged to 6.18.y, 6.19.y, 7.0.y and 7.1.y (checked against v6.18.39, v6.19.14, v7.0.14 and v7.1.4); the deferred_init_memmap_chunk() signature change in cbbbf7795fc3 sits outside the hunk context, so stable needs no separate backport.
mm/mm_init.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
mm/mm_init.c +6 -3
@@ -2214,10 +2214,13 @@ bool __init deferred_grow_zone(struct zone *zone, unsigned int order) }/* -* There were no pages to initialize and free which means the zone's -* memory map is completely initialized. +* The loop only tests spfn before entering an iteration, so on exit it +* may point up to a section past the end of the zone. When it does, +* the rest of the zone has already been handed to +* deferred_init_memmap_chunk() and nothing is left to initialize. */ - pgdat->first_deferred_pfn = nr_pages ? spfn :ULONG_MAX ; + pgdat->first_deferred_pfn = + spfn <zone_end_pfn (zone) ? spfn :ULONG_MAX ;pgdat_resize_unlock (pgdat, &flags);
base-commit: 0d839570765118029aa8bf4a95444c6a11aacf85
Signature · 1 line
2.47.1
Mike Rapoport
maintainerQuoted 8 lines · Alexander Graf
With
CONFIG_DEFERRED_STRUCT_PAGE_INITenabled,deferred_grow_zone()undefers struct page ranges early in boot to satisfy an allocation. With a large CMA reservation in place, the ranges it finds may not add up to the allocation it was asked for, and we end up undeferring all available RAM and still fall short. That is fine in itself: the function accounts for it and leaves the caller to decide whether it now has enough memory.[...]
I reworded the code comment and the changelog even more and applied to for-next branch of memblock.git tree, thanks!
[1/1] mm/mm_init: fix out-of-range first_deferred_pfn
commit: 97090500d776c3f6d08e857e3a0a7cf092999094
tree: https://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock
branch: for-next
--
Sincerely yours,
Mike.