Re: [PATCH 0/2] mm/hugetlb: fix surplus accounting and availability checks during demotion

From: Longlong Xia

Date: Mon Aug 31 2026 - 08:14:08 EST



在 2026/8/29 7:10, Andrew Morton 写道:
On Sun, 23 Aug 2026 11:43:05 +0800 Longlong Xia<xialonglong2025@xxxxxxx> wrote:

This series fixes two problems in the hugetlb demote path.

Patch 1 fixes surplus accounting in the source hstate.
demote_pool_huge_page() removes every source folio as a persistent
folio, but a free folio may instead account for one of the source
hstate's surplus pages (for example after a vmemmap restoration
failure). Removing such a folio without adjusting surplus_huge_pages
makes the persistent count underflow, and later subtracting it from
max_huge_pages can underflow that counter as well.

Patch 2 fixes an availability overcount in the sysfs demote path. The
sysfs trigger checks whether any page is available but then passes the
entire request to demote_pool_huge_page(), which can remove free huge
pages that back existing reservations. With two free pages and one
reservation, a request for two pages removes both and leaves the
reservation without a backing page.
Thanks. Have you created and tested reproducers for these? Gemini was
trivially able to do this for me.

If so, it would be helpful to includes the details in the
changelogging.

Hi Andrew,

Yes, I created and tested reproducers for both bugs.

Patch 1 requires a vmemmap restoration failure, which is difficult to
trigger deterministically. For that test only, I added a one-shot fault
injection that makes the first attempt to restore the vmemmap of an
optimized 1 GiB folio fail with -ENOMEM. The injection does not modify
the demotion or accounting code, and it is one-shot so that the later
restore performed during demotion can succeed.

Patch 2 is reproducible entirely from userspace and requires no kernel
instrumentation.

Patch 1 (surplus accounting):

  1. Hack the kernel to force the first 1G vmemmap restore to fail:

     /* TEST ONLY: fail the first optimized 1G folio restore. */
     static atomic_t fail_next_1g_restore = ATOMIC_INIT(1);

     ... in __hugetlb_vmemmap_restore_folio():
     if (huge_page_size(h) == SZ_1G &&
         atomic_cmpxchg(&fail_next_1g_restore, 1, 0) == 1) {
             pr_info("TEST ONLY: forcing one 1G vmemmap restore failure\n");
             return -ENOMEM;
     }

  2. Boot QEMU with:
     hugepagesz=1G hugepages=0 hugetlb_cma=1G hugetlb_free_vmemmap=on

  3. Enable overcommit:
     echo 1 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_overcommit_hugepages

  4. Allocate one 1G hugepage:
     nr=1 surplus=1 free=0 resv=0

  5. Unmap it.  Due to the forced restore failure, the folio is left on
     the freelist but still accounted as surplus:
     nr=1 surplus=1 free=1 resv=0

  6. Demote one page:
     echo 1 > /sys/kernel/mm/hugepages/hugepages-1048576kB/demote

     Before fix:
       nr=0 surplus=1 free=0 resv=0
       surplus > nr  (wrong -- surplus not decremented)

     After fix:
       nr=0 surplus=0 free=0 resv=0
       accounts consistent

Patch 2 (cap demotion):

  1. Boot QEMU with:
     hugepagesz=1G hugepages=2
     nr=2 surplus=0 free=2 resv=0

  2. Reserve one 1G page (mmap hugetlbfs, do not touch):
     nr=2 surplus=0 free=2 resv=1

  3. Request demotion of 2 pages:
     echo 2 > /sys/kernel/mm/hugepages/hugepages-1048576kB/demote

     Before fix:
       nr=0 surplus=0 free=0 resv=1
       resv > free  (wrong -- reservation has no backing page)

     After fix:
       nr=1 surplus=0 free=1 resv=1
       resv == free  (correct -- request capped at available=1)

  4. Touch the reserved page and let the process exit:

     Before fix:
       Bus error (core dumped)
     nr=0 surplus=0 free=0 resv=0


     After fix:
       touch: OK (value=0x00)
       nr=1 surplus=0 free=1 resv=0

I'll include these reproducer details in the changelog of the next
version.

Thanks,
Longlong