[PATCH v3 19/21] memblock: make HugeTLB bootmem allocation work with KHO
Mike Rapoport
rppt at kernel.org
Wed Jul 15 02:35:19 PDT 2026
> Gigantic huge page allocation is somewhat broken currently when KHO is
> used.
>
> Firstly, they break KHO scratch size accounting. RSRV_KERN is used to
> track how much memory is reserved for use by the kernel. Since
> hugetlb::alloc_bootmem() calls the memblock_alloc*() APIs, the hugepages
> allocated also get marked as RSRV_KERN.
>
> Allocations marked RSRV_KERN are used by KHO to calculate how much
> scratch space it should reserve to make sure the next kernel has enough
> memory to boot when it is in scratch-only phase. Counting hugepages in
> that blows up scratch size, and can lead to the scratch allocation
> failing, making KHO unusable. This will show up when huge pages make up
> more than 50% of the system, which is a fairly common use case.
>
> Secondly, while not supported right now, huge pages are user memory and
> can be preserved via KHO. The scratch spaces should not have any
> preserved memory. Allocating hugepages from scratch (on a KHO boot) can
> lead to them being un-preservable.
>
> Introduce memblock_alloc_hugetlb(). This lets memblock tailor to the
> needs of hugetb without exposing those details to the general allocation
> routines.
>
> First, it does not use mirrored memory for hugetlb. Mirrored memory is a
> limited resource that is best saved for kernel data structures, not user
> memory.
>
> Second, if the free memory area found by memblock_find_in_range_node()
> is a part of a KHO scratch area, the free area is not used. Allocation
> is retried starting after the free area to ensure no hugepages come from
> KHO scratch.
>
> Third, it simplifies the argument list by baking in some hugetlb
> assumptions like alignment and exact_nid. This also simplifies
> allocation logic in alloc_bootmem().
>
> Also introduce MEMBLOCK_RSRV_HUGETLB to mark reservations made for
> HugeTLB. This will be used by KHO in future patches to correctly
> calculate scratch sizes.
>
> Refactor some of the preparation logic like kmemleak tracking and
> accepting memory into a separate helper memblock_prep_allocation(), and
> use it from both memblock_alloc_hugetlb() and the usual
> memblock_alloc_range_nid().
>
> Add a stub for kho_scratch_overlap to memblock tests to make sure it
> compiles.
>
> Signed-off-by: Pratyush Yadav (Google) <pratyush at kernel.org>
>
> diff --git a/include/linux/memblock.h b/include/linux/memblock.h
> index f142c8a8cd5b7..678fe466529a2 100644
> --- a/include/linux/memblock.h
> +++ b/include/linux/memblock.h
> @@ -51,6 +51,7 @@ extern unsigned long long max_possible_pfn;
> * memory reservations yet, so we get scratch memory from the previous
> * kernel that we know is good to use. It is the only memory that
> * allocations may happen from in this phase.
> + * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages
> */
> enum memblock_flags {
> MEMBLOCK_NONE = 0x0, /* No special request */
> @@ -61,6 +62,7 @@ enum memblock_flags {
> MEMBLOCK_RSRV_NOINIT = 0x10, /* don't initialize struct pages */
> MEMBLOCK_RSRV_KERN = 0x20, /* memory reserved for kernel use */
> MEMBLOCK_KHO_SCRATCH = 0x40, /* scratch memory for kexec handover */
> + MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */
> };
>
> /**
> @@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align,
> void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,
> phys_addr_t min_addr, phys_addr_t max_addr,
> int nid);
> +void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid);
>
> static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)
> {
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 571212b80835e..ab4afc818e8cb 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -3033,29 +3033,21 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)
> if (hugetlb_early_cma(h))
> m = hugetlb_cma_alloc_bootmem(h, &listnode, node_exact);
> else {
> - if (node_exact)
> - m = memblock_alloc_exact_nid_raw(huge_page_size(h),
> - huge_page_size(h), 0,
> - MEMBLOCK_ALLOC_ACCESSIBLE, nid);
> - else {
> - m = memblock_alloc_try_nid_raw(huge_page_size(h),
> - huge_page_size(h), 0,
> - MEMBLOCK_ALLOC_ACCESSIBLE, nid);
> + m = memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);
> + if (m) {
> + m->flags = 0;
> + m->cma = NULL;
> +
> /*
> * For pre-HVO to work correctly, pages need to be on
> * the list for the node they were actually allocated
> * from. That node may be different in the case of
> - * fallback by memblock_alloc_try_nid_raw. So,
> + * fallback by memblock_alloc_hugetlb_bootmem. So,
> * extract the actual node first.
> */
> - if (m)
> + if (!node_exact)
> listnode = early_pfn_to_nid(PHYS_PFN(__pa(m)));
> }
> -
> - if (m) {
> - m->flags = 0;
> - m->cma = NULL;
> - }
> }
>
> if (m) {
> diff --git a/mm/memblock.c b/mm/memblock.c
> index 8b2e551435ae9..ba6d887ea18eb 100644
> --- a/mm/memblock.c
> +++ b/mm/memblock.c
> @@ -1506,6 +1506,32 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size,
> return 0;
> }
>
> +static void memblock_prep_allocation(phys_addr_t start, phys_addr_t size,
> + bool leaktrace)
Let's call this kmemleak_trace
> +{
> + /*
> + * Skip kmemleak for those places like kasan_init() and
> + * early_pgtable_alloc() due to high volume.
> + */
> + if (leaktrace)
> + /*
> + * Memblock allocated blocks are never reported as
> + * leaks. This is because many of these blocks are
> + * only referred via the physical address which is
> + * not looked up by kmemleak.
> + */
> + kmemleak_alloc_phys(start, size, 0);
> +
> + /*
> + * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
> + * require memory to be accepted before it can be used by the
> + * guest.
> + *
> + * Accept the memory of the allocated buffer.
> + */
> + accept_memory(start, size);
> +}
> +
> /**
> * memblock_alloc_range_nid - allocate boot memory block
> * @size: size of memory block to be allocated in bytes
> @@ -1580,28 +1606,7 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
> return 0;
>
> done:
> - /*
> - * Skip kmemleak for those places like kasan_init() and
> - * early_pgtable_alloc() due to high volume.
> - */
> - if (end != MEMBLOCK_ALLOC_NOLEAKTRACE)
> - /*
> - * Memblock allocated blocks are never reported as
> - * leaks. This is because many of these blocks are
> - * only referred via the physical address which is
> - * not looked up by kmemleak.
> - */
> - kmemleak_alloc_phys(found, size, 0);
> -
> - /*
> - * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
> - * require memory to be accepted before it can be used by the
> - * guest.
> - *
> - * Accept the memory of the allocated buffer.
> - */
> - accept_memory(found, size);
> -
> + memblock_prep_allocation(found, size, end != MEMBLOCK_ALLOC_NOLEAKTRACE);
> return found;
> }
>
> @@ -1756,6 +1761,69 @@ void * __init memblock_alloc_try_nid_raw(
> false);
> }
>
> +/**
> + * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages
> + * @size: size of the memory to be allocated in bytes
> + * @nid: nid of the free memory to find, %NUMA_NO_NODE for any node
> + * @exact_nid: only allocate from the specified nid. If %false, the specified
> + * nid is tried first, and then all nodes are tried as fallback.
> + *
> + * HugeTLB pages are always aligned by their size, so the alignment matches
> + * @size. Since the memory is for userspace, mirrored memory is not used. The
> + * memory is not zeroed. Does not panic if request cannot be satisfied.
> + *
> + * Return:
> + * Virtual address of allocated memory block on success, %NULL on failure.
> + */
> +void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)
> +{
> + enum memblock_flags flags = choose_memblock_flags();
> + phys_addr_t addr, start = 0, end = MEMBLOCK_ALLOC_ACCESSIBLE;
> +
> + memblock_dbg("%s: %llu bytes, nid=%d, exact_nid=%d %pS\n", __func__,
> + (u64)size, nid, exact_nid, (void *)_RET_IP_);
> +
> + /* Don't waste mirrored memory on HugeTLB pages. */
> + flags &= ~MEMBLOCK_MIRROR;
> +retry:
> + /* HugeTLB pages are always aligned by their size. */
> + addr = memblock_find_in_range_node(size, size, start, end, nid, flags);
> + if (addr)
> + goto found;
> +
> + /* Try all nodes if allowed. */
> + if (numa_valid_node(nid) && !exact_nid) {
> + nid = NUMA_NO_NODE;
> + goto retry;
> + }
> +
> + /* Found nothing... :-( */
> + return NULL;
> +
> +found:
> + /*
> + * HugeTLB pages can be preserved with KHO and no preserved memory can
> + * be in scratch. So retry if found address overlaps with scratch.
> + *
> + * Scratch areas are normally not very large, so this shouldn't take too
> + * many retries.
> + */
> + if (kho_scratch_overlap(addr, size)) {
> + if (memblock_bottom_up())
> + start = addr + size;
> + else
> + start = addr - size;
> +
> + goto retry;
> + }
> +
> + if (__memblock_reserve(addr, size, nid, MEMBLOCK_RSRV_KERN | MEMBLOCK_RSRV_HUGETLB))
> + return NULL;
> +
> + memblock_prep_allocation(addr, size, true);
> + return phys_to_virt(addr);
> +}
> +
> /**
> * memblock_alloc_try_nid - allocate boot memory block
> * @size: size of memory block to be allocated in bytes
> diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h
> index b6b1d147fd750..e86bb9000b22f 100644
> --- a/tools/testing/memblock/internal.h
> +++ b/tools/testing/memblock/internal.h
> @@ -66,4 +66,9 @@ static inline void init_deferred_page(unsigned long pfn, int nid)
>
> #define __SetPageReserved(p) ((void)(p))
>
> +static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
> +{
> + return false;
> +}
> +
This should be in the patch that exposes kho_scratch_overlap() to kexec_handover.h
--
Sincerely yours,
Mike.
More information about the kexec
mailing list