From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B8DE400E11; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=XY1E2cLax1pUifv7JR5oAbpQQzeEPUYNjy2xKZ55hXNxi90UAExmfXKaOamW0r9Cqg/xYA8/BHjS9k/vV6CnKmlDQj8Tw3dbAoRctuHJRO+05PzFo+tsVlIZf8gqlPsNULSLK7Ytth3M87u9dzGFwDGMeN4rdkjO4k522mC3v2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=G7owv0Rh8aK9HMc13GyQyMnhYD7EJP9+jUjD/VdT8wY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=rmsdF/v4nlDePqTLAbI40Y8/1w9q6ty3MKaLE5k5z+mGiCbLWsKF9aV17DN72SAhoxnA3L5EADCNcNy8GqrIR93XtzBf6VY5+BHSgAxvUWD9j41XBhk5ZoQMUc5cq96RLqfXlhTxFEgs6EbJG68Asm7yg68P2YFQ81C6QfRZET0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Wq8FOEO8; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Wq8FOEO8" Received: by smtp.kernel.org (Postfix) with ESMTPS id DBC0DC2BCF7; Tue, 21 Jul 2026 02:53:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602388; bh=G7owv0Rh8aK9HMc13GyQyMnhYD7EJP9+jUjD/VdT8wY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Wq8FOEO8fWount63Mrrt4fbeFwHO5uXGjd1wZx0VDUEV955uJiVkGPqj3XoF0tFdW 22XbMfT3IiTljDe3zvEFdMxsKM75B168bU9xCqRnDpHOqhyFWjD+TUGrVjqVcbeTgF BQmytPP/ZHgE80maX8fRfrIVFuoJpgLQWU7xfLtaG5ehA6wBwXEafhgzjq9P5YzYUj EfAHtCf/IJN/FTJ6mSfK3BnpFeoT2epDigDzpRwNvLHYLwB4tbAxRTHM/XRfBd6IKv eMGVjvMMXO4hgr7+CO0hK6GDzMxk4oN/KdVaRQZyKMyeZtmyAHUfyPRdesYVhDwiyO 3SCJ2ASnXd5pQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id BC01BC4452D; Tue, 21 Jul 2026 02:53:08 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:04 -0700 Subject: [PATCH v3 01/13] mm: hugetlb: Track used_hpages when getting/putting pages from subpool Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-1-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng , stable@vger.kernel.org X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=8769; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=w2qajt1r8Q6gjrtYXuj4ovqvEP7oIX8X7JrtFo0nCxg=; b=sLajWR3ya/oU2guuR3A6UKK6t+gsg3meCcu2cmJNYfdGD5C15Gsco5sKlvP9dcBy6/F+UxTiR KmMFHjmgQJuBEw2lPu0g1TnTLQt8BIPsFy9NkDTPVao6+FuE3Yjsdfi X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng hugepage_subpool_put_pages() currently has two distinct responsibilities that conflict: 1. When size is specified for the mount, max_hpages !=3D -1: Keep track of total active pages (allocated + reserved) and decrement this count (used_hpages) when a page is freed or allocation fails. 2. When min_size is specified for the mount, min_hpages !=3D -1: Ensure we don't drop below the guaranteed minimum, and restore a reservation (rsv_hpages) if we do. This causes trouble because when allocation fails (refer to alloc_hugetlb_folio()) if gbl_chg =3D 1 (i.e. no subpool reservation was taken): + To keep used_hpages consistent, HugeTLB needs to call hugepage_subpool_put_pages() to restore undo used_hpages being incremented + But can't call hugepage_subpool_put_pages() if no reservation was consumed. One option would be to conditionally do subpool tracking updates outside of the hugepage_subpool_put_pages() function, but that would spread logic all over. Instead, always track used_hpages, regardless of whether a max_size was requested for the mount, so that the subpool always knows how many pages were allocated through it. Every page allocated through the subpool increments used_hpages, regardless of whether a reservation was taken from it. Conceptually, now, every allocation involving a subpool uses a page from the subpool, which must be returned to the subpool. Every page taken from the subpool tries to use a subpool reservation. Restoring a page to the subpool reservations only if the page was taken from subpool reservations. (If used_hpages >=3D min_hpages, the page must have not have been taken from the reservations.) Always tracking used_hpages provides the subpool with information of both used and reserved counts to make the correct decision for both max_size and min_size correctly. With used_hpages always tracked, + subpool_is_free() can be simplified, such that the subpool can be declared free if there are no more pages in use. + open-coding in hugetlb_reserve_pages() can be removed. Also update the documentation for used_hpages, since it no longer matters whether the used pages count against the maximum. Also update statfs reporting. Previously, if max_hpages is negative, used_hpages is static at 0, so returning max_hpages - used_hpages returns -1 and is always correct. Now, if the subpool doesn't have a maximum requested size, indicate no limit for free pages (-1). If it does have a maximum size, report the difference between the requested size and the number of used pages. This difference is always positive, because if the mount does have a maximum size, hugepage_subpool_get_pages() ensures that the subpool usage never exceeds the maximum. This fixes a bug in hugetlb_unreserve_pages(), where pages are returned to the subpool regardless of whether it consumed a reservation. The corresponding bug in the failure handling path of alloc_hugetlb_folio() was fixed in a833a693a490e. Fixes: 1c5ecae3a93fa ("hugetlbfs: add minimum size accounting to subpools") Cc: stable@vger.kernel.org Signed-off-by: Ackerley Tng --- fs/hugetlbfs/inode.c | 8 ++++-- include/linux/hugetlb.h | 4 +-- mm/hugetlb.c | 71 +++++++++++++++++++++++----------------------= ---- 3 files changed, 41 insertions(+), 42 deletions(-) diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 216e1a0dd0b23..26c0187340636 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -1109,8 +1109,12 @@ static int hugetlbfs_statfs(struct dentry *dentry, s= truct kstatfs *buf) =20 spin_lock_irq(&sbinfo->spool->lock); buf->f_blocks =3D sbinfo->spool->max_hpages; - free_pages =3D sbinfo->spool->max_hpages - - sbinfo->spool->used_hpages; + if (sbinfo->spool->max_hpages =3D=3D -1) { + free_pages =3D -1; + } else { + free_pages =3D sbinfo->spool->max_hpages - + sbinfo->spool->used_hpages; + } buf->f_bavail =3D buf->f_bfree =3D free_pages; spin_unlock_irq(&sbinfo->spool->lock); buf->f_files =3D sbinfo->max_inodes; diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 2abaf99321e90..34b9a3e1be0fa 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -38,8 +38,8 @@ struct hugepage_subpool { spinlock_t lock; long count; long max_hpages; /* Maximum huge pages or -1 if no maximum. */ - long used_hpages; /* Used count against maximum, includes */ - /* both allocated and reserved pages. */ + long used_hpages; /* Used page count, includes both */ + /* allocated and reserved pages. */ struct hstate *hstate; long min_hpages; /* Minimum huge pages or -1 if no minimum. */ long rsv_hpages; /* Pages reserved against global pool to */ diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 571212b80835e..eef9610a0593c 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -129,12 +129,8 @@ static inline bool subpool_is_free(struct hugepage_sub= pool *spool) { if (spool->count) return false; - if (spool->max_hpages !=3D -1) - return spool->used_hpages =3D=3D 0; - if (spool->min_hpages !=3D -1) - return spool->rsv_hpages =3D=3D spool->min_hpages; =20 - return true; + return spool->used_hpages =3D=3D 0; } =20 static inline void unlock_or_release_subpool(struct hugepage_subpool *spoo= l, @@ -205,15 +201,14 @@ static long hugepage_subpool_get_pages(struct hugepag= e_subpool *spool, =20 spin_lock_irq(&spool->lock); =20 - if (spool->max_hpages !=3D -1) { /* maximum size accounting */ - if ((spool->used_hpages + delta) <=3D spool->max_hpages) - spool->used_hpages +=3D delta; - else { - ret =3D -ENOMEM; - goto unlock_ret; - } + if (spool->max_hpages !=3D -1 && + spool->used_hpages + delta > spool->max_hpages) { + ret =3D -ENOMEM; + goto unlock_ret; } =20 + spool->used_hpages +=3D delta; + /* minimum size accounting */ if (spool->min_hpages !=3D -1 && spool->rsv_hpages) { if (delta > spool->rsv_hpages) { @@ -251,19 +246,24 @@ static long hugepage_subpool_put_pages(struct hugepag= e_subpool *spool, =20 spin_lock_irqsave(&spool->lock, flags); =20 - if (spool->max_hpages !=3D -1) /* maximum size accounting */ - spool->used_hpages -=3D delta; + spool->used_hpages -=3D delta; =20 /* minimum size accounting */ if (spool->min_hpages !=3D -1 && spool->used_hpages < spool->min_hpages) { - if (spool->rsv_hpages + delta <=3D spool->min_hpages) + /* + * limit is the maximum number of reservations that + * can be restored to this subpool. + */ + long limit =3D spool->min_hpages - spool->used_hpages; + + if (spool->rsv_hpages + delta <=3D limit) ret =3D 0; else - ret =3D spool->rsv_hpages + delta - spool->min_hpages; + ret =3D spool->rsv_hpages + delta - limit; =20 spool->rsv_hpages +=3D delta; - if (spool->rsv_hpages > spool->min_hpages) - spool->rsv_hpages =3D spool->min_hpages; + if (spool->rsv_hpages > limit) + spool->rsv_hpages =3D limit; } =20 /* @@ -6542,7 +6542,7 @@ long hugetlb_reserve_pages(struct inode *inode, struct vm_area_struct *vma, vma_flags_t vma_flags) { - long chg =3D -1, add =3D -1, spool_resv, gbl_resv; + long chg =3D -1, add =3D -1, gbl_resv; struct hstate *h =3D hstate_inode(inode); struct hugepage_subpool *spool =3D subpool_inode(inode); struct resv_map *resv_map; @@ -6687,26 +6687,21 @@ long hugetlb_reserve_pages(struct inode *inode, } return chg; =20 -out_put_pages: - spool_resv =3D chg - gbl_reserve; - if (spool_resv) { - /* put sub pool's reservation back, chg - gbl_reserve */ - gbl_resv =3D hugepage_subpool_put_pages(spool, spool_resv); - /* - * subpool's reserved pages can not be put back due to race, - * return to hstate. - */ - hugetlb_acct_memory(h, -gbl_resv); - } - /* Restore used_hpages for pages that failed global reservation */ - if (gbl_reserve && spool) { - unsigned long flags; + out_put_pages: + /* + * Return all that was requested from the subpool, let subpool + * tell us the new number of reservations that need to be + * returned to the global pool. + */ + gbl_resv =3D hugepage_subpool_put_pages(spool, chg); + /* + * There may be a difference between the number of + * reservations to consume and the number to restore now if + * there are multiple threads interacting with the subpool - + * restore the difference. + */ + hugetlb_acct_memory(h, -(gbl_resv - gbl_reserve)); =20 - spin_lock_irqsave(&spool->lock, flags); - if (spool->max_hpages !=3D -1) - spool->used_hpages -=3D gbl_reserve; - unlock_or_release_subpool(spool, flags); - } out_uncharge_cgroup: hugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h), chg * pages_per_huge_page(h), h_cg); --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B969400E1A; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=DT+QiwDwMQmhZnPWi5nS83eLnySv2C5HzecZ0Up0SuM5h6NHPBCM8JdV/O3W9IUSx9qgQjhklaZmSc+c6SRoAjnWk6TDDUg9k036/n2RGsBmtF/CtoKOS2wJ34wQ1908NOji9JgzRSFnxWyLUeVMSwx9sgdoy4UYr+KoVITsVUM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=9xYXc8cFALwl2scnPkDmoIcwhk2eGPLl2JPUNmDY2d0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DqDEw9bJ12Zz6+ytt+N367tFeDpipL8XW+Gd69CpI3obQ+9oZu92g9mdB+o9L1/IjSQatTN9YxnHdQbW8w1HCRkj/5jl7xmCSw9xQbZArQGjJvIIAg5se208xPkLHh6Ax5YgnnyWyBVulG27X6WHcpZV3rv9x3PNDAQt+lsRyWE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=RU5nIEo7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="RU5nIEo7" Received: by smtp.kernel.org (Postfix) with ESMTPS id ED0F2C2BCC9; Tue, 21 Jul 2026 02:53:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=9xYXc8cFALwl2scnPkDmoIcwhk2eGPLl2JPUNmDY2d0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=RU5nIEo7oWGGETmhUfdKPLm20rS3ih3Lz3nx7dwAbmJpFMYKLmt1sbPxm8M2BdFSb p+qcEBcKlKCYBeGTbYZvCWk0+kdvFH9uvPlxo+HCrjvzfA3TxKn/TSf+lvQK01A1dG 6bCBamepBRssAXw7K2FZqZE+8Pq1xa652OychT7XIwjDIyD9SfXYw04rOq0bNNF1kL fY0DPpYdi2kqYt6agKYJjpZBpnTg75KDWOxMZ1teiVfjCiYXeav3kklKOSKGInqqeF KMnOIr1LMuDTplYR35a/9ljd8NETimAmPoZiEBsL5JeRUAahmv6rCmmCODVa3gC9rc 018p7QpijnZRw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CCCBFC44524; Tue, 21 Jul 2026 02:53:08 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:05 -0700 Subject: [PATCH v3 02/13] mm: hugetlb: Return -ENOSPC on memcg charge failure Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-2-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng , stable@vger.kernel.org X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=2183; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=nuUhbZqkwDE9Ph8fILa8cD4bi/Ma9wFUhfWLAJKiQgk=; b=j57evNN0B3aJE2bGo4aYEop42SQgenXTgRg2PUpxRSDxLbx80RSNC2CSSJi9kG5+6PcjtSdOX U3od4V1MktRAPv3uMaKCMouffdnKbebP9hZr5h1kP/nwAVICniSzxVg X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng When mem_cgroup_charge_hugetlb() fails with -ENOMEM, alloc_hugetlb_folio() currently propagates this error. This results in the page fault handler returning VM_FAULT_OOM. Because HugeTLB allocations are high-order and use __GFP_RETRY_MAYFAIL, they bypass the OOM killer. Returning VM_FAULT_OOM to the #PF handler without triggering the OOM killer (or having it make progress) leads to an infinite loop of retrying the fault. Avoid this loop by returning -ENOSPC when charging fails, which maps to VM_FAULT_SIGBUS, terminating the process cleanly. Make mem_cgroup_charge_hugetlb() fault handling use a common error handling path, the same handling used for hugetlb_cgroup_uncharge_cgroup{,_rsvd}(), which also don't trigger the OOM killer and hence opt to terminate the process with a SIGBUS. Fixes: 991135774c0e0 ("memcg/hugetlb: introduce mem_cgroup_charge_hugetlb") Cc: stable@vger.kernel.org Signed-off-by: Ackerley Tng Reviewed-by: Muchun Song --- mm/hugetlb.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index eef9610a0593c..b32735b092a0a 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -2997,7 +2997,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_stru= ct *vma, =20 if (ret =3D=3D -ENOMEM) { free_huge_folio(folio); - return ERR_PTR(-ENOMEM); + goto err; } =20 return folio; @@ -3022,6 +3022,17 @@ struct folio *alloc_hugetlb_folio(struct vm_area_str= uct *vma, out_end_reservation: if (map_chg !=3D MAP_CHG_ENFORCED) vma_end_reservation(h, vma, addr); +err: + /* + * Return -ENOSPC when this function fails to allocate or + * charge a huge page. If a standard (PAGE_SIZE) page + * allocation fails, the OOM killer is given a chance to run, + * which may resolve the failure on retry. However, for + * HugeTLB allocations, the OOM killer is not triggered. + * Returning -ENOMEM (or anything resulting in VM_FAULT_OOM) + * would leak to the #PF handler, causing it to loop + * indefinitely retrying the fault. + */ return ERR_PTR(-ENOSPC); } =20 --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B9F7401486; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=jipnEbfWUMlqaIYKm/BPl9JkQikcfkEPrKkbuHyawqVfA8/8SWrYHuegT4OnxDdu+UDnIeRYxlPLSoPYapxBrHxHILFaJE6hKmAc/gGuG3H7GvUbHtTyseR7OOBCY/2p4uevd+ZbSR0qtVyUCpbfzHAeoC/AWCFkyGHkkNmTVbE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=S4PQRnwD9RgHZk6exqa/VQ/Z2Egij2virqZNxM9X6GQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DhmxeEK3LncR7a7N377w86gW1YitKLTLikkh/wLqG79nssVf4TzWeSNC59Vo0181FUBtxXv3Q2oDtyYGhhf/BLx/A9x/48ZPKjDMVrZNkjL5LHkNTBEi4ea84rewV8/51uvDuuWoNH/8cD+I/5ltoWKmLIVi77vrlxEfWbGwCBo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=hDEcz+0+; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="hDEcz+0+" Received: by smtp.kernel.org (Postfix) with ESMTPS id 070B6C2BCFC; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=S4PQRnwD9RgHZk6exqa/VQ/Z2Egij2virqZNxM9X6GQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=hDEcz+0+HbkjnptTfQiJrDlvEsFfV3su952r96K0JqrS/fctzIM+6fMHuClLe/aXn 8QmNGXxcW0lRKH3zM1j/LJSzBk01XLj9ll1xZ7alKaFc+0S5h2WQoTS+xWcC578er7 OgeezVi0DG4mmTCSqVPnDW9rMmYSwZ2TgKVWoPIGNfaCOKNM2DKLKypX8zgANLclFu thuqN24RPcS2cOvy3yhvLpFQKuuBK86D1zu8T6aOFCdQwvkqBoNMg+gO+vWEh+EfLm fbKlXsk9GrkvveGvr9iSnGrbQiYfpeR67CTnNtdE1OK+75YNSyjxKmAzKAKTKIfj32 1bYYL8LurFREQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id E09DAC44531; Tue, 21 Jul 2026 02:53:08 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:06 -0700 Subject: [PATCH v3 03/13] mm: hugetlb: Use try-commit-cancel protocol for memcg charge of folios Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-3-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng , stable@vger.kernel.org X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=10920; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=0AcwGphc3uan0N2TDSOh02phq0/whAnaLvUQOgTHT0c=; b=aGV+5Brhx8SclDNTFhVEBxin3LumpPpe6D0wnVa8FJWlaXPeCRzhdP5E/EcKZ94gkjloJEvSq +0FxmYsdgGWAIUAnTVc66ZyJk2YZ20AF+uZgiwHsgN+7gBwG73a3B6v X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Using mem_cgroup_charge_hugetlb() to charge a HugeTLB folio during page fault creates a reservation leak bug if the task hits its memory cgroup limit. When alloc_hugetlb_folio() commits the VMA reservation, the reserved page is removed from the reserve map. If a subsequent call to mem_cgroup_charge_hugetlb() returns -ENOMEM, the allocation is aborted and the physical folio is disposed of via free_huge_page(). However, because the VMA reservation was already consumed, the reservation count in the reserve map is lost. This causes subsequent faults in the VMA address range to fail with premature reservation exhaustion. Additionally, dropping the use of free_huge_folio() on the failure path fixes an issue where free_huge_folio() was incorrectly invoked on a folio with a refcount of 1, triggering refcount mismatches and kernel warnings. To fix this, introduce a try-commit-cancel protocol for memory cgroup charging of HugeTLB folios, matching the architecture used by the hugetlb cgroup controller. Invoking mem_cgroup_hugetlb_try_charge() before consuming the VMA reservation ensures that if the memory cgroup limit is reached, the allocation is aborted cleanly without leaking the reservation entry or having to dispose of a partially initialized folio. An alternative would be to retain the current usage of mem_cgroup_charge_hugetlb() and free_huge_page(), but freeing the folio performs reservation management for subpools and global hstate, which complicates rollback in alloc_hugetlb_folio(). Using a try-commit-cancel protocol is more consistent with the other charging performed in alloc_hugetlb_folio() and easier to understand. Fixes: 991135774c0e0 ("memcg/hugetlb: introduce mem_cgroup_charge_hugetlb") Cc: stable@vger.kernel.org Signed-off-by: Ackerley Tng --- include/linux/memcontrol.h | 36 ++++++++++++-- mm/hugetlb.c | 30 ++++++------ mm/memcontrol.c | 114 +++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 162 insertions(+), 18 deletions(-) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index e1f46a0016fcf..c4e63d0e03526 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -641,7 +641,16 @@ static inline int mem_cgroup_charge(struct folio *foli= o, struct mm_struct *mm, return __mem_cgroup_charge(folio, mm, gfp); } =20 -int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp); +int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp); +int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp, + struct mem_cgroup **memcg_p, + struct obj_cgroup **objcg_p); +void mem_cgroup_hugetlb_commit_charge(struct folio *folio, + struct mem_cgroup *memcg, + struct obj_cgroup *objcg); +void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages, + struct mem_cgroup *memcg, + struct obj_cgroup *objcg); =20 int mem_cgroup_swapin_charge_folio(struct folio *folio, unsigned short id, struct mm_struct *mm, gfp_t gfp); @@ -1128,9 +1137,30 @@ static inline int mem_cgroup_charge(struct folio *fo= lio, return 0; } =20 -static inline int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp) +static inline int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp) +{ + return 0; +} + +static inline int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp= _t gfp, + struct mem_cgroup **memcg_p, + struct obj_cgroup **objcg_p) +{ + *memcg_p =3D NULL; + *objcg_p =3D NULL; + return 0; +} + +static inline void mem_cgroup_hugetlb_commit_charge(struct folio *folio, + struct mem_cgroup *memcg, + struct obj_cgroup *objcg) +{ +} + +static inline void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages, + struct mem_cgroup *memcg, + struct obj_cgroup *objcg) { - return 0; } =20 static inline int mem_cgroup_swapin_charge_folio(struct folio *folio, diff --git a/mm/hugetlb.c b/mm/hugetlb.c index b32735b092a0a..061d7250c202d 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -38,6 +38,7 @@ #include #include #include +#include =20 #include #include @@ -2863,6 +2864,8 @@ struct folio *alloc_hugetlb_folio(struct vm_area_stru= ct *vma, int ret, idx; struct hugetlb_cgroup *h_cg =3D NULL; struct hugetlb_cgroup *h_cg_rsvd =3D NULL; + struct mem_cgroup *mem_cg =3D NULL; + struct obj_cgroup *obj_cg =3D NULL; gfp_t gfp =3D htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL; =20 idx =3D hstate_index(h); @@ -2922,6 +2925,11 @@ struct folio *alloc_hugetlb_folio(struct vm_area_str= uct *vma, if (ret) goto out_uncharge_cgroup_reservation; =20 + ret =3D mem_cgroup_hugetlb_try_charge(pages_per_huge_page(h), gfp, + &mem_cg, &obj_cg); + if (ret) + goto out_uncharge_cgroup; + spin_lock_irq(&hugetlb_lock); /* * glb_chg is passed to indicate whether or not a page must be taken @@ -2933,7 +2941,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_stru= ct *vma, spin_unlock_irq(&hugetlb_lock); folio =3D alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr); if (!folio) - goto out_uncharge_cgroup; + goto out_uncharge_cgroup_memcg; spin_lock_irq(&hugetlb_lock); list_add(&folio->lru, &h->hugepage_activelist); folio_ref_unfreeze(folio, 1); @@ -2960,6 +2968,9 @@ struct folio *alloc_hugetlb_folio(struct vm_area_stru= ct *vma, =20 spin_unlock_irq(&hugetlb_lock); =20 + mem_cgroup_hugetlb_commit_charge(folio, mem_cg, obj_cg); + lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h)); + hugetlb_set_folio_subpool(folio, spool); =20 if (map_chg !=3D MAP_CHG_ENFORCED) { @@ -2987,21 +2998,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_st= ruct *vma, } } =20 - ret =3D mem_cgroup_charge_hugetlb(folio, gfp); - /* - * Unconditionally increment NR_HUGETLB here. If it turns out that - * mem_cgroup_charge_hugetlb failed, then immediately free the page and - * decrement NR_HUGETLB. - */ - lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h)); - - if (ret =3D=3D -ENOMEM) { - free_huge_folio(folio); - goto err; - } - return folio; =20 +out_uncharge_cgroup_memcg: + mem_cgroup_hugetlb_cancel_charge(pages_per_huge_page(h), mem_cg, obj_cg); out_uncharge_cgroup: hugetlb_cgroup_uncharge_cgroup(idx, pages_per_huge_page(h), h_cg); out_uncharge_cgroup_reservation: @@ -3022,7 +3022,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_stru= ct *vma, out_end_reservation: if (map_chg !=3D MAP_CHG_ENFORCED) vma_end_reservation(h, vma, addr); -err: + /* * Return -ENOSPC when this function fails to allocate or * charge a huge page. If a standard (PAGE_SIZE) page diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 6dc4888a90f3f..0beee5c0ce93b 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -5180,6 +5180,120 @@ int mem_cgroup_charge_hugetlb(struct folio *folio, = gfp_t gfp) return ret; } =20 +/** + * mem_cgroup_hugetlb_try_charge - Try to charge the memcg for a hugetlb f= olio + * @nr_pages: number of base pages to charge + * @gfp: reclaim mode + * @memcg_p: Output pointer to the charged mem_cgroup (if successful and e= nabled) + * @objcg_p: Output pointer to the charged obj_cgroup (if successful and e= nabled) + * + * Prepares and tries to reserve the memory counter for the folio from the= current + * task's memcg. If successful, both *memcg_p and *objcg_p are populated a= nd their + * references are pinned until a subsequent call to mem_cgroup_hugetlb_com= mit_charge + * or mem_cgroup_hugetlb_cancel_charge. + * + * Returns ENOMEM if the memcg is already full. + * Returns 0 if either the charge was successful, or if we skip charging. + */ +int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp, + struct mem_cgroup **memcg_p, + struct obj_cgroup **objcg_p) +{ + struct mem_cgroup *memcg; + struct obj_cgroup *objcg; + int ret =3D 0; + + *memcg_p =3D NULL; + *objcg_p =3D NULL; + + if (mem_cgroup_disabled() || !memcg_accounts_hugetlb() || + !cgroup_subsys_on_dfl(memory_cgrp_subsys)) + return 0; + + memcg =3D get_mem_cgroup_from_current(); + if (!memcg) + return 0; + + objcg =3D get_obj_cgroup_from_memcg(memcg); + if (!objcg) + goto put_memcg; + + if (!obj_cgroup_is_root(objcg)) { + ret =3D try_charge_memcg(memcg, gfp, nr_pages); + if (ret) + goto put_objcg; + } + + *memcg_p =3D memcg; + *objcg_p =3D objcg; + return 0; + +put_objcg: + obj_cgroup_put(objcg); +put_memcg: + mem_cgroup_put(memcg); + return ret; +} + +/** + * mem_cgroup_hugetlb_commit_charge - Commit the memcg charge for a hugetl= b folio + * @folio: folio being charged + * @memcg: Target mem_cgroup obtained from mem_cgroup_hugetlb_try_charge + * @objcg: Target obj_cgroup obtained from mem_cgroup_hugetlb_try_charge + * + * Finalizes the memory and statistics charging for the folio in the speci= fied memcg. + * Transfers the pinned objcg reference to the folio structure (for automa= tic + * uncharging upon freeing via mem_cgroup_uncharge). Releases the try-comm= it reference + * on memcg. + */ +void mem_cgroup_hugetlb_commit_charge(struct folio *folio, + struct mem_cgroup *memcg, + struct obj_cgroup *objcg) +{ + if (!memcg || !objcg) + return; + + commit_charge(folio, objcg); + memcg1_commit_charge(folio, memcg); + + /* + * Drop our try-commit-cancel protocol reference on memcg. + * The objcg reference is TRANSFERRED to the folio by commit_charge, + * so it will be put automatically by __mem_cgroup_uncharge() when + * the folio is freed. + */ + mem_cgroup_put(memcg); +} + +/** + * mem_cgroup_hugetlb_cancel_charge - Cancel and undo a hugetlb folio memc= g charge + * @nr_pages: number of base pages to uncharge + * @memcg: Target mem_cgroup obtained from mem_cgroup_hugetlb_try_charge + * @objcg: Target obj_cgroup obtained from mem_cgroup_hugetlb_try_charge + * + * Cancels and safely rolls back the prepared memory charge for the folio = in the + * specified memcg. Releases the try-commit pinned references on both memc= g and objcg. + */ +void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages, + struct mem_cgroup *memcg, + struct obj_cgroup *objcg) +{ + if (!memcg || !objcg) + return; + + if (!obj_cgroup_is_root(objcg)) + refill_stock(memcg, nr_pages); + + /* + * Drop our try-commit-cancel protocol references on both objcg + * and memcg, since this mapping attempt was aborted and the folio + * was never committed. + */ + obj_cgroup_put(objcg); + mem_cgroup_put(memcg); +} + + /** * mem_cgroup_swapin_charge_folio - Charge a newly allocated folio for swa= pin. * @folio: the folio to charge --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B844221FB4; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=DuxCd+r2LouCqBRscS6JR1n5RZO/c3BHRpQ0d2BVjvZSfw80G69FPS4h2KgsewrtLC6GR9xux0PqND9B0JVrGR7d1LY5S+lmKmgCkQwQERDtxt1CT+95zpi9ccy+tyGEwRsf+5INGOtQYRjkx4V0UfBt9lTllQMl1MC4AJS0k+w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=Us68nlmiczQXSuV6e/jQLLsnNhiOlkPZQjCHR8+MEVg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pfb3KdQ+6N1m9GLOqOc481OO4blHcFEWAJqRi0O0VBCJpuICjdI1JiHvqAWU0HBh0rn0KvpLrhQ7v9Ay57JLjQhRPrdnVwXVJQ9gxXjjjv7TnQpzAesuZbyD4SG+V0qmE5ysGTldrTyw2PzwrUW4jJkfjWededFIVT3bhWVPSz4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gKJMT4Hu; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="gKJMT4Hu" Received: by smtp.kernel.org (Postfix) with ESMTPS id 110A4C2BCFB; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=Us68nlmiczQXSuV6e/jQLLsnNhiOlkPZQjCHR8+MEVg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=gKJMT4HuK026T3JHk7a9omtQ18OWp7GnSYZ76oYiGMUHyN/YdDGNNMYhGuVVLq99T qwlx0+DbEt+sMM7NZbLbAt9/O3iqNz4Ww/SdV+yYeA5n1sgHapjwFPQRWzD5hIz8gt k83Rew8CVsBVlBKrkMEYTm1H3mb5sU2Cc8CvsFGV+d9RlXFpOEwFlGLUnheMPenPZ8 GXqkLWZSqbMTO7eXkIWGNAfzJxFsb1rzAEpVi4iywlm3zXttdHTptmhK2dS8uUQHWM oh17kGa0Pnr1DXUiiZKfE5+QXpQpC8nsihL8pMAN6bYUFTDt0p3m8t0VHabtieI0l1 5cmNKkWJOJ8Ww== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id F29B3C44532; Tue, 21 Jul 2026 02:53:08 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:07 -0700 Subject: [PATCH v3 04/13] mm: hugetlb: Remove unused mem_cgroup_charge_hugetlb function Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-4-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=2906; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=YxYG1OGXEwJP8y6Z/IuFu/IygfIhB7TL//GqeViUvNo=; b=ODtz7IxzrmEVm8ITYNPhXWX2tkSFDb0Wo55iHcQRNwKOI1rX9vcyL7wqj2XEfXYcaIBA0galC iwpBY5zcPvkCL3uWuHWuJCKXlOZCeXKd8EuisExKDwVIE1ZR8hjkV60 X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Now that the alloc_hugetlb_folio path has been successfully migrated to the new try-commit-cancel memcg charging protocol, the old mem_cgroup_charge_hugetlb function and its associated header and static inline declarations are completely unused. Remove them to clean up the memory controller's codebase. Signed-off-by: Ackerley Tng --- include/linux/memcontrol.h | 6 ------ mm/memcontrol.c | 34 ---------------------------------- 2 files changed, 40 deletions(-) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index c4e63d0e03526..23fbbbc8e11f2 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -641,7 +641,6 @@ static inline int mem_cgroup_charge(struct folio *folio= , struct mm_struct *mm, return __mem_cgroup_charge(folio, mm, gfp); } =20 -int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp); int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp, struct mem_cgroup **memcg_p, struct obj_cgroup **objcg_p); @@ -1137,11 +1136,6 @@ static inline int mem_cgroup_charge(struct folio *fo= lio, return 0; } =20 -static inline int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp) -{ - return 0; -} - static inline int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp= _t gfp, struct mem_cgroup **memcg_p, struct obj_cgroup **objcg_p) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 0beee5c0ce93b..6764ff041c196 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -5146,40 +5146,6 @@ int __mem_cgroup_charge(struct folio *folio, struct = mm_struct *mm, gfp_t gfp) return ret; } =20 -/** - * mem_cgroup_charge_hugetlb - charge the memcg for a hugetlb folio - * @folio: folio being charged - * @gfp: reclaim mode - * - * This function is called when allocating a huge page folio, after the pa= ge has - * already been obtained and charged to the appropriate hugetlb cgroup - * controller (if it is enabled). - * - * Returns ENOMEM if the memcg is already full. - * Returns 0 if either the charge was successful, or if we skip the chargi= ng. - */ -int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp) -{ - struct mem_cgroup *memcg =3D get_mem_cgroup_from_current(); - int ret =3D 0; - - /* - * Even memcg does not account for hugetlb, we still want to update - * system-level stats via lruvec_stat_mod_folio. Return 0, and skip - * charging the memcg. - */ - if (mem_cgroup_disabled() || !memcg_accounts_hugetlb() || - !memcg || !cgroup_subsys_on_dfl(memory_cgrp_subsys)) - goto out; - - if (charge_memcg(folio, memcg, gfp)) - ret =3D -ENOMEM; - -out: - mem_cgroup_put(memcg); - return ret; -} - /** * mem_cgroup_hugetlb_try_charge - Try to charge the memcg for a hugetlb f= olio * @nr_pages: number of base pages to charge --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 816004028EE; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=HF/u06Zz/8jbP0v+tnt0ls2gSKZ2oa93k/ZZwf1JnmZRO74El1nLHxYlvPFxRVKhzKXuIauZP3IPn8uQRYatqvizi2q6bBBO1dHCs+ew9Swpthbo193as9RSdSsGcV5P5NOvRq+5BAzh/eHXTZH0a4koGQm/YrnKX5nAC1NkNSo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=kSh9BQgS5QmHyKYjO89WZ2+3iTBXW5J+wxxG9vDVAJE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ByeBTkhczV81HC6cwaiNgHoHgjUdMnZFJ9jaerEfbvBWKi7hpvdDVp7cXcFY5H/SEtBo1qe1ZYsxloykB3iyDL0j7pv5BCygK4Yb7mWDZ/dpmCEwbbWqrMfbUPsTfhbgTviKN1z0mtxbIbn37+Dz/yzIcLNGt3K/3A+cXocamhs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XEHybAo+; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="XEHybAo+" Received: by smtp.kernel.org (Postfix) with ESMTPS id 23057C2BCFF; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=kSh9BQgS5QmHyKYjO89WZ2+3iTBXW5J+wxxG9vDVAJE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=XEHybAo+2kZNVVGNyUv5FLdCH48YFNU3aR9zcL663YF8cgoDm5tEZG8C6kZrrD9ZQ pUEKKPPhBB7T6MqJfWl8D1DxrKjorfKgwda1tcObkLEIva/ABl+y2OTShLxOpWAUfb QGduIUi/81ESq7KJdMoxhAiAHjQNOB0vy63QE/d3RLQf52zMHPqaeVLUSCIrxY8MWQ dda5HhbojUX3i6VuawQDBg8/VAFZfiNWnUISIbCVxcOkhCyA/wA09bHEvklUdRiWL1 2w8kogzEW4PrGK1QROYUWNaocIGPYtbMtZS3DnV3h1H2Tc2VCrX22TRJsV3ZO2u6oe SKlAsthmOF/vg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0EF6AC44533; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:08 -0700 Subject: [PATCH v3 05/13] mm: hugetlb: Fix subpool usage leak on allocation failure Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-5-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng , stable@vger.kernel.org X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=2183; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=TxFD58wSA2jJAufxG4ptcnmkIwsdZTWOv5gzU++/HSs=; b=Cuw9IMu+njvxpvmnRk8QFmkwsKKjZPzSykOuLnRokUuqa3Z2CbGxGweoxUKCN7Lg7QoL0WSrH bxvolGurs8gCNt/+T6VZcrW5RuSxaR+OKnxfVIbStbxrn7DKeQ8rJkq X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng When alloc_hugetlb_folio() fails early (e.g. buddy allocation failure or hugetlb cgroup charging failure) and gbl_chg =3D=3D 1 (meaning a reservation was not used, but a global page was allocated instead), the subpool page acquired via hugepage_subpool_get_pages() must still be returned. Currently, the error path out_subpool_put: only calls hugepage_subpool_put_pages() if !gbl_chg is true. If gbl_chg is 1, it skips it, permanently leaking the subpool's used_hpages counter. With the earlier patch to always track used_hpages in the subpool, always call hugepage_subpool_put_pages() if map_chg is true to consistently restore the page to the subpool. Only call hugetlb_acct_memory() to adjust global reservations if gbl_chg =3D=3D 0 since gbl_chg =3D=3D 0 indicates a subpool (and global) reservation was used. Fixes: a833a693a490e ("mm: hugetlb: fix incorrect fallback for subpool") Cc: stable@vger.kernel.org Signed-off-by: Ackerley Tng --- mm/hugetlb.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 061d7250c202d..3f6189d2d0188 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -2859,7 +2859,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_stru= ct *vma, struct hugepage_subpool *spool =3D subpool_vma(vma); struct hstate *h =3D hstate_vma(vma); struct folio *folio; - long retval, gbl_chg, gbl_reserve; + long retval, gbl_chg; map_chg_state map_chg; int ret, idx; struct hugetlb_cgroup *h_cg =3D NULL; @@ -3009,13 +3009,11 @@ struct folio *alloc_hugetlb_folio(struct vm_area_st= ruct *vma, hugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h), h_cg_rsvd); out_subpool_put: - /* - * put page to subpool iff the quota of subpool's rsv_hpages is used - * during hugepage_subpool_get_pages. - */ - if (map_chg && !gbl_chg) { - gbl_reserve =3D hugepage_subpool_put_pages(spool, 1); - hugetlb_acct_memory(h, -gbl_reserve); + if (map_chg) { + long gbl_reserve =3D hugepage_subpool_put_pages(spool, 1); + + if (!gbl_chg) + hugetlb_acct_memory(h, -gbl_reserve); } =20 =20 --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7F5224028EB; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=AGIMhm++TSF2xcNlllS7MwsYHgTsIHNy0Mnydtv022edT1Vu8uPrVFqSRsHoAjDo1391xmwIiyDr4Px8YZk+j/IJ/kNSUPN5VkIxKTaqhBNcvVCAje3/dGQbOXq5wPWlQz2qYAuPMT9SgX1LohtKf0aXuuPR7GtAii0MsiTLDSw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=Wu07gWj3erS7hK5FPPelwtFUrfvOki8Pg8B+z3neeSc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pJtR/lFioE5AA/JY9QHjBXByUefhSIK49bVAwwG9L632LkzRq3dxO2I261OlJyXWMfr6CC/UEtOiLEqlcAMGon6rg9tOU/VYuH8bd2zBqQPVLrrlBZGipOWS7S+AbkK+4r0mgdWadH0TdjvSx3YKnDOIOCU3WEPU/e54EQ19e9w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=M/yiDBJ2; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="M/yiDBJ2" Received: by smtp.kernel.org (Postfix) with ESMTPS id 332BAC4AF0B; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=Wu07gWj3erS7hK5FPPelwtFUrfvOki8Pg8B+z3neeSc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=M/yiDBJ2nGMs3YeIqFjgXDdqQaBtXuvBCS9Liqauek39MEy/kPmh5zzJIF5z9fd2O St7/2uvZpPVnOVnQOp/w3Ml6r3lHlj3nvEwul35ZGR9Hcqo0IDyJfoqOPBd6VNe1vi B0OcR2imFFVlMHCWsCl7i6Uu2tmo4lj54xjUeiyTYo+JzZUP/0cytjRdXZV+zk/whV nngHfIM/9oj3vyg3Oog4lRl1tmO7fch//zkmYoTdyqNK+DsoNFDhm0/Q+7TcXhKPca AI7Ad9J68yfVnRBn8ZLBO/YpNzD1qufpBr/0QGkRnmzFdBbnHuJC3qu7r2It6XKvzm cpDQkWhLQsGvA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1F989C4452F; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:09 -0700 Subject: [PATCH v3 06/13] WIP: mm: hugetlb: Move subpool functions to hugetlb_subpool.c Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-6-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=13070; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=PqvaRfss4aqadaeJH8AH48w85toFWMCvvNrNEPaAyKc=; b=6BBdWsqAWtEKlq9TQ6riYxZR+DYxf0X+5j+MmIiRrX5Qew2yTCc0lofrYvLoqZxxd15dJ9XzO rp96xiTX4M0BBhoisnSQr1lEnbII6WuKTLr/kYov9HrK9LNWk5icisR X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Move all HugeTLB subpool lifecycle, page reservation, and accounting routin= es out of `mm/hugetlb.c` and into their own dedicated, encapsulated translatio= n unit at `mm/hugetlb_subpool.c`. Also introduces the internal `mm/hugetlb_subpool.h` header for holding the subpool-local APIs, allowing `fs/hugetlbfs` and `mm/` to access the subpool= functions cleanly. The subpool internal layout structures remain in `include/linux/hugetlb.h` = until getters are introduced. Signed-off-by: Ackerley Tng --- fs/hugetlbfs/inode.c | 1 + include/linux/hugetlb.h | 4 +- mm/Makefile | 2 +- mm/hugetlb.c | 156 +------------------------------------------- mm/hugetlb_subpool.c | 168 ++++++++++++++++++++++++++++++++++++++++++++= ++++ mm/hugetlb_subpool.h | 17 +++++ 6 files changed, 191 insertions(+), 157 deletions(-) diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 26c0187340636..8c1caad74c409 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -25,6 +25,7 @@ #include #include #include +#include "../../mm/hugetlb_subpool.h" #include #include #include diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 34b9a3e1be0fa..f36be371c6e88 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -114,9 +114,7 @@ extern int hugetlb_max_hstate __read_mostly; #define for_each_hstate(h) \ for ((h) =3D hstates; (h) < &hstates[hugetlb_max_hstate]; (h)++) =20 -struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_h= pages, - long min_hpages); -void hugepage_put_subpool(struct hugepage_subpool *spool); +int hugetlb_acct_memory(struct hstate *h, long delta); =20 void hugetlb_dup_vma_private(struct vm_area_struct *vma); void clear_vma_resv_huge_pages(struct vm_area_struct *vma); diff --git a/mm/Makefile b/mm/Makefile index eff9f9e7e061c..3965c959e5099 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -78,7 +78,7 @@ endif obj-$(CONFIG_SWAP) +=3D page_io.o swap_state.o swapfile.o obj-$(CONFIG_ZSWAP) +=3D zswap.o obj-$(CONFIG_HAS_DMA) +=3D dmapool.o -obj-$(CONFIG_HUGETLBFS) +=3D hugetlb.o hugetlb_sysfs.o hugetlb_sysctl.o +obj-$(CONFIG_HUGETLBFS) +=3D hugetlb.o hugetlb_subpool.o hugetlb_sysfs.o h= ugetlb_sysctl.o ifdef CONFIG_CMA obj-$(CONFIG_HUGETLBFS) +=3D hugetlb_cma.o endif diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 3f6189d2d0188..e87f26a3e1f3e 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -39,6 +39,7 @@ #include #include #include +#include "hugetlb_subpool.h" =20 #include #include @@ -114,8 +115,7 @@ __cacheline_aligned_in_smp DEFINE_SPINLOCK(hugetlb_lock= ); static int num_fault_mutexes __ro_after_init; struct mutex *hugetlb_fault_mutex_table __ro_after_init; =20 -/* Forward declaration */ -static int hugetlb_acct_memory(struct hstate *h, long delta); +/* Forward declarations */ static void hugetlb_vma_lock_free(struct vm_area_struct *vma); static void hugetlb_vma_lock_alloc(struct vm_area_struct *vma); static void __hugetlb_vma_unlock_write_free(struct vm_area_struct *vma); @@ -126,156 +126,6 @@ static void hugetlb_unshare_pmds(struct vm_area_struc= t *vma, unsigned long start, unsigned long end, bool take_locks); static struct resv_map *vma_resv_map(struct vm_area_struct *vma); =20 -static inline bool subpool_is_free(struct hugepage_subpool *spool) -{ - if (spool->count) - return false; - - return spool->used_hpages =3D=3D 0; -} - -static inline void unlock_or_release_subpool(struct hugepage_subpool *spoo= l, - unsigned long irq_flags) -{ - spin_unlock_irqrestore(&spool->lock, irq_flags); - - /* If no pages are used, and no other handles to the subpool - * remain, give up any reservations based on minimum size and - * free the subpool */ - if (subpool_is_free(spool)) { - if (spool->min_hpages !=3D -1) - hugetlb_acct_memory(spool->hstate, - -spool->min_hpages); - kfree(spool); - } -} - -struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_h= pages, - long min_hpages) -{ - struct hugepage_subpool *spool; - - spool =3D kzalloc_obj(*spool); - if (!spool) - return NULL; - - spin_lock_init(&spool->lock); - spool->count =3D 1; - spool->max_hpages =3D max_hpages; - spool->hstate =3D h; - spool->min_hpages =3D min_hpages; - - if (min_hpages !=3D -1 && hugetlb_acct_memory(h, min_hpages)) { - kfree(spool); - return NULL; - } - spool->rsv_hpages =3D min_hpages; - - return spool; -} - -void hugepage_put_subpool(struct hugepage_subpool *spool) -{ - unsigned long flags; - - spin_lock_irqsave(&spool->lock, flags); - BUG_ON(!spool->count); - spool->count--; - unlock_or_release_subpool(spool, flags); -} - -/* - * Subpool accounting for allocating and reserving pages. - * Return -ENOMEM if there are not enough resources to satisfy the - * request. Otherwise, return the number of pages by which the - * global pools must be adjusted (upward). The returned value may - * only be different than the passed value (delta) in the case where - * a subpool minimum size must be maintained. - */ -static long hugepage_subpool_get_pages(struct hugepage_subpool *spool, - long delta) -{ - long ret =3D delta; - - if (!spool) - return ret; - - spin_lock_irq(&spool->lock); - - if (spool->max_hpages !=3D -1 && - spool->used_hpages + delta > spool->max_hpages) { - ret =3D -ENOMEM; - goto unlock_ret; - } - - spool->used_hpages +=3D delta; - - /* minimum size accounting */ - if (spool->min_hpages !=3D -1 && spool->rsv_hpages) { - if (delta > spool->rsv_hpages) { - /* - * Asking for more reserves than those already taken on - * behalf of subpool. Return difference. - */ - ret =3D delta - spool->rsv_hpages; - spool->rsv_hpages =3D 0; - } else { - ret =3D 0; /* reserves already accounted for */ - spool->rsv_hpages -=3D delta; - } - } - -unlock_ret: - spin_unlock_irq(&spool->lock); - return ret; -} - -/* - * Subpool accounting for freeing and unreserving pages. - * Return the number of global page reservations that must be dropped. - * The return value may only be different than the passed value (delta) - * in the case where a subpool minimum size must be maintained. - */ -static long hugepage_subpool_put_pages(struct hugepage_subpool *spool, - long delta) -{ - long ret =3D delta; - unsigned long flags; - - if (!spool) - return delta; - - spin_lock_irqsave(&spool->lock, flags); - - spool->used_hpages -=3D delta; - - /* minimum size accounting */ - if (spool->min_hpages !=3D -1 && spool->used_hpages < spool->min_hpages) { - /* - * limit is the maximum number of reservations that - * can be restored to this subpool. - */ - long limit =3D spool->min_hpages - spool->used_hpages; - - if (spool->rsv_hpages + delta <=3D limit) - ret =3D 0; - else - ret =3D spool->rsv_hpages + delta - limit; - - spool->rsv_hpages +=3D delta; - if (spool->rsv_hpages > limit) - spool->rsv_hpages =3D limit; - } - - /* - * If hugetlbfs_put_super couldn't free spool due to an outstanding - * quota reference, free it now. - */ - unlock_or_release_subpool(spool, flags); - - return ret; -} - static inline struct hugepage_subpool *subpool_vma(struct vm_area_struct *= vma) { return subpool_inode(file_inode(vma->vm_file)); @@ -4602,7 +4452,7 @@ unsigned long hugetlb_total_pages(void) return nr_total_pages; } =20 -static int hugetlb_acct_memory(struct hstate *h, long delta) +int hugetlb_acct_memory(struct hstate *h, long delta) { int ret =3D -ENOMEM; =20 diff --git a/mm/hugetlb_subpool.c b/mm/hugetlb_subpool.c new file mode 100644 index 0000000000000..b12c16aa7e4c5 --- /dev/null +++ b/mm/hugetlb_subpool.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Subpool and reserve accounting for HugeTLB folios. + * Extracted from mm/hugetlb.c + */ + +#include +#include +#include +#ifdef __KERNEL__ +#include +#endif +#include +#include + +#include "hugetlb_subpool.h" + +static inline bool subpool_is_free(struct hugepage_subpool *spool) +{ + if (spool->count) + return false; + + return spool->used_hpages =3D=3D 0; +} + +static inline void unlock_or_release_subpool(struct hugepage_subpool *spoo= l, + unsigned long irq_flags) +{ + spin_unlock_irqrestore(&spool->lock, irq_flags); + + /* + * If no pages are used, and no other handles to the subpool + * remain, give up any reservations based on minimum size and + * free the subpool. + */ + if (subpool_is_free(spool)) { + if (spool->min_hpages !=3D -1) + hugetlb_acct_memory(spool->hstate, + -spool->min_hpages); + kfree(spool); + } +} + +struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_h= pages, + long min_hpages) +{ + struct hugepage_subpool *spool; + + spool =3D kzalloc_obj(*spool); + if (!spool) + return NULL; + + spin_lock_init(&spool->lock); + spool->count =3D 1; + spool->max_hpages =3D max_hpages; + spool->hstate =3D h; + spool->min_hpages =3D min_hpages; + + if (min_hpages !=3D -1 && hugetlb_acct_memory(h, min_hpages)) { + kfree(spool); + return NULL; + } + spool->rsv_hpages =3D min_hpages; + + return spool; +} + +void hugepage_put_subpool(struct hugepage_subpool *spool) +{ + unsigned long flags; + + spin_lock_irqsave(&spool->lock, flags); + BUG_ON(!spool->count); + spool->count--; + unlock_or_release_subpool(spool, flags); +} + +/* + * Subpool accounting for allocating and reserving pages. + * Return -ENOMEM if there are not enough resources to satisfy the + * request. Otherwise, return the number of pages by which the + * global pools must be adjusted (upward). The returned value may + * only be different than the passed value (delta) in the case where + * a subpool minimum size must be maintained. + */ +long hugepage_subpool_get_pages(struct hugepage_subpool *spool, + long delta) +{ + long ret =3D delta; + + if (!spool) + return ret; + + spin_lock_irq(&spool->lock); + + if (spool->max_hpages !=3D -1 && + spool->used_hpages + delta > spool->max_hpages) { + ret =3D -ENOMEM; + goto unlock_ret; + } + + spool->used_hpages +=3D delta; + + /* minimum size accounting */ + if (spool->min_hpages !=3D -1 && spool->rsv_hpages) { + if (delta > spool->rsv_hpages) { + /* + * Asking for more reserves than those already taken on + * behalf of subpool. Return difference. + */ + ret =3D delta - spool->rsv_hpages; + spool->rsv_hpages =3D 0; + } else { + ret =3D 0; /* reserves already accounted for */ + spool->rsv_hpages -=3D delta; + } + } + +unlock_ret: + spin_unlock_irq(&spool->lock); + return ret; +} + +/* + * Subpool accounting for freeing and unreserving pages. + * Return the number of global page reservations that must be dropped. + * The return value may only be different than the passed value (delta) + * in the case where a subpool minimum size must be maintained. + */ +long hugepage_subpool_put_pages(struct hugepage_subpool *spool, + long delta) +{ + long ret =3D delta; + unsigned long flags; + + if (!spool) + return delta; + + spin_lock_irqsave(&spool->lock, flags); + + spool->used_hpages -=3D delta; + + /* minimum size accounting */ + if (spool->min_hpages !=3D -1 && spool->used_hpages < spool->min_hpages) { + /* + * limit is the maximum number of reservations that + * can be restored to this subpool. + */ + long limit =3D spool->min_hpages - spool->used_hpages; + + if (spool->rsv_hpages + delta <=3D limit) + ret =3D 0; + else + ret =3D spool->rsv_hpages + delta - limit; + + spool->rsv_hpages +=3D delta; + if (spool->rsv_hpages > limit) + spool->rsv_hpages =3D limit; + } + + /* + * If hugetlbfs_put_super couldn't free spool due to an outstanding + * quota reference, free it now. + */ + unlock_or_release_subpool(spool, flags); + + return ret; +} diff --git a/mm/hugetlb_subpool.h b/mm/hugetlb_subpool.h new file mode 100644 index 0000000000000..be1f1cf012c9c --- /dev/null +++ b/mm/hugetlb_subpool.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _MM_HUGETLB_SUBPOOL_H +#define _MM_HUGETLB_SUBPOOL_H + +#include +#include + +struct hstate; +struct hugepage_subpool; + +struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_h= pages, + long min_hpages); +void hugepage_put_subpool(struct hugepage_subpool *spool); +long hugepage_subpool_get_pages(struct hugepage_subpool *spool, long delta= ); +long hugepage_subpool_put_pages(struct hugepage_subpool *spool, long delta= ); + +#endif /* _MM_HUGETLB_SUBPOOL_H */ --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7D4994028E9; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=lZfZ1J9N9AYZjqPZbDgMhe/atm8/7Jop1TI34v3uebbWebRlnagEcoeC5LvK232CkYVVfI3xFGH2/nU/oIoqJhIcmVfDLPThM1XhwXiPNlwvclUxo++zCJMMhEQWT+UvroBNtQSccxMnbH+3WNL/nseP6yxHdaoDeJP6uXVPUpY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=dQJa64TA7n7wPBuMQAm33gNRQMnt8IhQlLQye0FISNY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=lS+J8zILTvPIq8mwvEBM41FptudMThql6aNeN4Lb2XTKtEisc4mc8kkE3CeUxnzOBSqnUrK0Er/FJj5cuVKuJZPvxGjR89K1myj7Wf3fkdR2BYgjUm+16N1wsWyEsy2I+2MbugyNzqKUM9rQ3ePzmXfl6qFIQGIFSxCaNx199eY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=t8H9z6J2; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="t8H9z6J2" Received: by smtp.kernel.org (Postfix) with ESMTPS id 41D1FC32781; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=dQJa64TA7n7wPBuMQAm33gNRQMnt8IhQlLQye0FISNY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=t8H9z6J2NlgIVtT1kugXvkDJmSfCnl/14OIJqMCSkY5iPUGGV5RRk2b+MHWBPTbJo yyry6m/JVZK797uqnu8HKVDr1Xl42r2ISMOMd5VXjFgNBiX50jEE1ORfGgpDPjNyDW kAAdqn0bVNdwa3uYLfDf1DNbklQXsAldsYGHhydTXSDoS7Q2/z9NC2W9pl5LS4FN7N wxh7JQks4WkF6PacqhAlSTeKW4ZBrusHlS5NAy83+VGrWXGKSkkmxHmLD2XtTxcScz J0MI1U3Uk7YpK6x9HNj973LLvA5eQb0CAl4V4wSoe02QoeD6n3XXObj//MtqK7gB4P 88E6ZqK3OhaIQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2FFA6C44524; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:10 -0700 Subject: [PATCH v3 07/13] WIP: fs: hugetlbfs: Refactor subpool getters and integrate with hugetlb_subpool API Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-7-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=5388; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=/bVMwhDzkKWXm/PUQ4Oz0DueIPwM1LmPE5uQODdNRt4=; b=e9KVJDUd1/O3E5VwDVW5E9FSn4k3+pT0IRfemqqYsyY5VfHIO+b7wVSj17bceSsnoRwaed+Zi i/0O5pqbxyBCVCbC2l3qDsBLYdKyysNubSNU1FyLXhC3GzBd6/ng4aw X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Refactor the direct field accesses to `spool->max_hpages`, `spool->min_hpag= es`, and calculate subpool properties using dedicated, encapsulated accessor get= ters inside `mm/hugetlb_subpool.c`. Introduces and exports the following harmonized subpool getters to `mm/huge= tlb_subpool.h`: - `hugepage_subpool_free_hpages()` - `hugepage_subpool_max_size()` - `hugepage_subpool_min_size()` - `hugepage_subpool_max_hpages()` Signed-off-by: Ackerley Tng --- fs/hugetlbfs/inode.c | 28 +++++++++------------------- mm/hugetlb_subpool.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- mm/hugetlb_subpool.h | 4 ++++ 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 8c1caad74c409..8abe0574b1c7e 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -1060,7 +1060,6 @@ static int hugetlbfs_show_options(struct seq_file *m,= struct dentry *root) struct hugetlbfs_sb_info *sbinfo =3D HUGETLBFS_SB(root->d_sb); struct hugepage_subpool *spool =3D sbinfo->spool; unsigned long hpage_size =3D huge_page_size(sbinfo->hstate); - unsigned hpage_shift =3D huge_page_shift(sbinfo->hstate); char mod; =20 if (!uid_eq(sbinfo->uid, GLOBAL_ROOT_UID)) @@ -1082,12 +1081,13 @@ static int hugetlbfs_show_options(struct seq_file *= m, struct dentry *root) } seq_printf(m, ",pagesize=3D%lu%c", hpage_size, mod); if (spool) { - if (spool->max_hpages !=3D -1) - seq_printf(m, ",size=3D%llu", - (unsigned long long)spool->max_hpages << hpage_shift); - if (spool->min_hpages !=3D -1) - seq_printf(m, ",min_size=3D%llu", - (unsigned long long)spool->min_hpages << hpage_shift); + unsigned long long max_size =3D hugepage_subpool_max_size(spool); + unsigned long long min_size =3D hugepage_subpool_min_size(spool); + + if (max_size !=3D -1ULL) + seq_printf(m, ",size=3D%llu", max_size); + if (min_size !=3D -1ULL) + seq_printf(m, ",min_size=3D%llu", min_size); } return 0; } @@ -1106,18 +1106,8 @@ static int hugetlbfs_statfs(struct dentry *dentry, s= truct kstatfs *buf) /* If no limits set, just report 0 or -1 for max/free/used * blocks, like simple_statfs() */ if (sbinfo->spool) { - long free_pages; - - spin_lock_irq(&sbinfo->spool->lock); - buf->f_blocks =3D sbinfo->spool->max_hpages; - if (sbinfo->spool->max_hpages =3D=3D -1) { - free_pages =3D -1; - } else { - free_pages =3D sbinfo->spool->max_hpages - - sbinfo->spool->used_hpages; - } - buf->f_bavail =3D buf->f_bfree =3D free_pages; - spin_unlock_irq(&sbinfo->spool->lock); + buf->f_blocks =3D hugepage_subpool_max_hpages(sbinfo->spool); + buf->f_bavail =3D buf->f_bfree =3D hugepage_subpool_free_hpages(sbinfo-= >spool); buf->f_files =3D sbinfo->max_inodes; buf->f_ffree =3D sbinfo->free_inodes; } diff --git a/mm/hugetlb_subpool.c b/mm/hugetlb_subpool.c index b12c16aa7e4c5..99e7975911079 100644 --- a/mm/hugetlb_subpool.c +++ b/mm/hugetlb_subpool.c @@ -12,7 +12,6 @@ #endif #include #include - #include "hugetlb_subpool.h" =20 static inline bool subpool_is_free(struct hugepage_subpool *spool) @@ -41,6 +40,50 @@ static inline void unlock_or_release_subpool(struct huge= page_subpool *spool, } } =20 +long hugepage_subpool_free_hpages(struct hugepage_subpool *spool) +{ + long free_pages; + + spin_lock_irq(&spool->lock); + if (spool->max_hpages =3D=3D -1) + free_pages =3D -1; + else + free_pages =3D spool->max_hpages - spool->used_hpages; + spin_unlock_irq(&spool->lock); + + return free_pages; +} + +static unsigned int hugepage_subpool_hpage_shift(struct hugepage_subpool *= spool) +{ + return huge_page_shift(spool->hstate); +} + +unsigned long long hugepage_subpool_max_size(struct hugepage_subpool *spoo= l) +{ + if (spool->max_hpages =3D=3D -1) + return -1ULL; + return (unsigned long long)spool->max_hpages << hugepage_subpool_hpage_sh= ift(spool); +} + +unsigned long long hugepage_subpool_min_size(struct hugepage_subpool *spoo= l) +{ + if (spool->min_hpages =3D=3D -1) + return -1ULL; + return (unsigned long long)spool->min_hpages << hugepage_subpool_hpage_sh= ift(spool); +} + +long hugepage_subpool_max_hpages(struct hugepage_subpool *spool) +{ + long max_hpages; + + spin_lock_irq(&spool->lock); + max_hpages =3D spool->max_hpages; + spin_unlock_irq(&spool->lock); + + return max_hpages; +} + struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_h= pages, long min_hpages) { diff --git a/mm/hugetlb_subpool.h b/mm/hugetlb_subpool.h index be1f1cf012c9c..41d22239f2c3e 100644 --- a/mm/hugetlb_subpool.h +++ b/mm/hugetlb_subpool.h @@ -13,5 +13,9 @@ struct hugepage_subpool *hugepage_new_subpool(struct hsta= te *h, long max_hpages, void hugepage_put_subpool(struct hugepage_subpool *spool); long hugepage_subpool_get_pages(struct hugepage_subpool *spool, long delta= ); long hugepage_subpool_put_pages(struct hugepage_subpool *spool, long delta= ); +long hugepage_subpool_free_hpages(struct hugepage_subpool *spool); +long hugepage_subpool_max_hpages(struct hugepage_subpool *spool); +unsigned long long hugepage_subpool_max_size(struct hugepage_subpool *spoo= l); +unsigned long long hugepage_subpool_min_size(struct hugepage_subpool *spoo= l); =20 #endif /* _MM_HUGETLB_SUBPOOL_H */ --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 85AEE4028F7; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=LbOzW2wDiG01X6KzeCWIDVVclBA9Cc+l0j2YMzNSHFxbBevdGhyx5/ImGIBsolOn9YAPyBpwi1zDutdWWHp+6sshGAOk6M5W0JXR7/bOOVWAI26F3djXXsANbbCIYQkpPaP6G12ihUjY6Xz8nxlDVCo4xAyS6c5W7snw6TG816Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=g68oSCwzT15OMQimNW45wJYcvcPo4quFGfzqB+zzwcI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=BHbReAIjdZ5q+LcdfCHydI3fIU+d2cl2VpyYuLQVmrbhxAoKJ95qABNJ/NLY9PQp7niiewX9PHtaGNDEp583QI1rbFKxkF25xxdhz3JOS16LZm4sSf5hXPBFPbo+6TmcC7tXyXjuqVqjDrISDWti3MUFKKIEVoshtp3xtB8OMt0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=MlNg/RAp; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="MlNg/RAp" Received: by smtp.kernel.org (Postfix) with ESMTPS id 54D73C2BCFD; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=g68oSCwzT15OMQimNW45wJYcvcPo4quFGfzqB+zzwcI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=MlNg/RAplDNaPb6B7XrjBfgB8eW3yOcgJlJvX7BL0G09YFKuqHNP/4nPewX0AQgO7 eDhNTLNuTDB0PCaO96TUEM8vZ+B6eQ+sXxmU92O6OEUIqQrcJj6SaSNOIrcVCvorcL 0L40/v2oj/mqmSRuWL0DaTU8/gnsPCgbTN0+tzliOew4yBI1R2Fj3igEpDNmmC+Lzb oGzXMQR5/WEj4U/WtW2QwFft9saQpoMoY7/evIv6zAaN41Pt5b1tcsBSamTEQStM1h H2BU4Rvi3fnbRcCuOiB0zYIq6BsVZw+egex94yTJ8HH7qhG/MghtPOiyRSPZPnJZ4z UVIkCYVK1EwBw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 43133C44531; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:11 -0700 Subject: [PATCH v3 08/13] WIP: mm: hugetlb: Make struct hugepage_subpool private to hugetlb_subpool.c Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-8-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=2362; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=M6CZbHlay9O7mPyDIiOB40w5xsbkP4SdrC/EYdyZfC0=; b=EKSNPcPFErEcErK9ZdAcLB9AUma7YsDD5mhlF9ptqMRDEPyM8LE51vREzhRtzj/WSlU3plrqD WQwh+ogZMhrC8TVS/THJDOBsQh9QXXvOAFq7sC5ey7BszOhs0v7FFYF X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Now that all filesystem and memory management subpool layout inspections ac= ross `fs/hugetlbfs/inode.c` and `mm/` leverage the exported, harmonized APIs, tr= ansition `struct hugepage_subpool` out of the public header `include/linux/hugetlb.h= ` and encapsulate it privately inside `mm/hugetlb_subpool.c`. Replace the header definition with a forward declaration, completely protec= ting the underlying subpool allocation counters, reservations, and spinlock boundari= es. Signed-off-by: Ackerley Tng --- include/linux/hugetlb.h | 13 ++----------- mm/hugetlb_subpool.c | 12 ++++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index f36be371c6e88..074a45903973e 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -18,6 +18,7 @@ =20 struct mmu_gather; struct node; +struct hstate; =20 void free_huge_folio(struct folio *folio); =20 @@ -34,17 +35,7 @@ void free_huge_folio(struct folio *folio); */ #define __NR_USED_SUBPAGE 3 =20 -struct hugepage_subpool { - spinlock_t lock; - long count; - long max_hpages; /* Maximum huge pages or -1 if no maximum. */ - long used_hpages; /* Used page count, includes both */ - /* allocated and reserved pages. */ - struct hstate *hstate; - long min_hpages; /* Minimum huge pages or -1 if no minimum. */ - long rsv_hpages; /* Pages reserved against global pool to */ - /* satisfy minimum size. */ -}; +struct hugepage_subpool; =20 struct resv_map { struct kref refs; diff --git a/mm/hugetlb_subpool.c b/mm/hugetlb_subpool.c index 99e7975911079..eb5472bdf2192 100644 --- a/mm/hugetlb_subpool.c +++ b/mm/hugetlb_subpool.c @@ -14,6 +14,18 @@ #include #include "hugetlb_subpool.h" =20 +struct hugepage_subpool { + spinlock_t lock; + long count; + long max_hpages; /* Maximum huge pages or -1 if no maximum. */ + long used_hpages; /* Used page count, includes both */ + /* allocated and reserved pages. */ + struct hstate *hstate; + long min_hpages; /* Minimum huge pages or -1 if no minimum. */ + long rsv_hpages; /* Pages reserved against global pool to */ + /* satisfy minimum size. */ +}; + static inline bool subpool_is_free(struct hugepage_subpool *spool) { if (spool->count) --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8F27B4028FF; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=XHlfJOB5MNi5qlQUZPzB257ywcxhiJvhnYTh0ihLxANCeLu2K8g8yir1KrUuDvlP/drSwQqs5JEYEjLUuu/H87bjmkKNq8Kv6QdcqSFvMu0MMku4pt3WDcG51cZFkBVREh8D5JslOlduHv43Z9K1YZWANTcjdaGfmGXOHG4f3Os= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=NsP4bO+Np2GrL+oWvDifXZAKzspn8YRaupnG+LJNNUM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uTT8S8i3tNgScAzcxFrE9A+bcjm5UkVCIs2EqbuTwksRHlVABHuU28GR34XQFT73GMDwlA0P3ze9nrrSW1nyK7KdRmqvoq3XVne6J+innDrqHhM3DlA5SiCx5yfAL4yvnayEzPwRVFW2QXJo7xM7Ds/uP/rmVm3HqjDa6489GRU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AS6myZWE; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="AS6myZWE" Received: by smtp.kernel.org (Postfix) with ESMTPS id 646F9C2BD01; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=NsP4bO+Np2GrL+oWvDifXZAKzspn8YRaupnG+LJNNUM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=AS6myZWEiDBFdwBOogr223g6WvMaJtwO3CUWLZipjYsqcBlkK55ocePNDZCUsZ3LB d+GBjHw+ZhN60HkdJScQTkr75IM2DpR5uIDBrN2p03guIORUblfqvKJWHRBfviHqT6 QQVmQrqcrYaTPEHwsea84qeXeh/IaXQH80Jz8zwpJUueTxHc5xObLQIen0aQ+mZs4D BuMjN4xSZi0ezfjeoeJUR4iPJxIufJSW8kFTurc0q4368YlfyjyXAm0URiy1ne4+WD 00/aQHewU7aYUYR8s4AYzQhxQcz2+9jn6Tl1qCTIIjb+ZFbk1bjHmQcmt20d2uQk1q RseXU6lCwQKew== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 54697C4452D; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:12 -0700 Subject: [PATCH v3 09/13] WIP: tools: testing: Add userspace unit tests for HugeTLB subpools Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-9-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=14766; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=AFhB3BYtnUMT5tXegPxhvbG8FQDY9Ut+0oGZYrpvwok=; b=mrCruZP1FMarqd1HWOG5AX+9jUQaU5ylqrHxi315b0QQ25YCYRIDJnh37ncg1zwrkEKn4cXbn tf1wcSSWxEXDYUnpi6W9u0d33J+eZV2xvMufZECFIv66dcAVvFWMUei X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Introduce a standalone Userspace Unit Testing Suite under `tools/testing/hugetlb_subpool/` to exercise and stress-test the new internal `mm/hugetlb_subpool.c` API boundaries. Reuses the private kernel `struct hugepage_subpool` struct layout natively by embedding the implementation directly, avoiding structural definition drift between the implementation and testing mock environments. TAG=3Dagy CONV=3D5f2e8401-48d5-42c5-ab78-6b2f1aa16aff Signed-off-by: Ackerley Tng --- tools/testing/hugetlb_subpool/.gitignore | 1 + tools/testing/hugetlb_subpool/Makefile | 18 ++ tools/testing/hugetlb_subpool/test_subpool.c | 400 +++++++++++++++++++++++= ++++ 3 files changed, 419 insertions(+) diff --git a/tools/testing/hugetlb_subpool/.gitignore b/tools/testing/huget= lb_subpool/.gitignore new file mode 100644 index 0000000000000..7348c2c72f1e1 --- /dev/null +++ b/tools/testing/hugetlb_subpool/.gitignore @@ -0,0 +1 @@ +test_subpool diff --git a/tools/testing/hugetlb_subpool/Makefile b/tools/testing/hugetlb= _subpool/Makefile new file mode 100644 index 0000000000000..1bdb7e2635614 --- /dev/null +++ b/tools/testing/hugetlb_subpool/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +.PHONY: all clean test + +CC =3D gcc +CFLAGS =3D -Wall -O2 -I../shared -I. -I../../include -I../../arch/x86/incl= ude -pthread +KERNEL_SUBPOOL_H =3D ../../../mm/hugetlb_subpool.h +KERNEL_SUBPOOL_C =3D ../../../mm/hugetlb_subpool.c + +all: test + +test_subpool: test_subpool.c $(KERNEL_SUBPOOL_C) $(KERNEL_SUBPOOL_H) + $(CC) $(CFLAGS) test_subpool.c -o test_subpool + +test: test_subpool + ./test_subpool + +clean: + rm -f test_subpool diff --git a/tools/testing/hugetlb_subpool/test_subpool.c b/tools/testing/h= ugetlb_subpool/test_subpool.c new file mode 100644 index 0000000000000..78900274cc264 --- /dev/null +++ b/tools/testing/hugetlb_subpool/test_subpool.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +/* Mocked Userspace implementation for Kernel Subpool allocation dependenc= ies */ +struct hstate { + int dummy; +}; + +#undef kzalloc_obj +#undef kzalloc_objs +#define kzalloc_obj(P, ...) malloc(sizeof(P)) +#define kzalloc_objs(P, COUNT, ...) malloc(sizeof(P) * (COUNT)) + +#define kfree free +#define kmalloc malloc + +#define huge_page_shift(h) (21 + (0 * ((unsigned long)(h) & 0))) +#define huge_page_size(h) (1UL << huge_page_shift(h)) + +static bool hugetlb_acct_memory_called; +static struct hstate *hugetlb_acct_memory_h; +static long hugetlb_acct_memory_delta; + +static int hugetlb_acct_memory(struct hstate *h, long delta) +{ + hugetlb_acct_memory_called =3D true; + hugetlb_acct_memory_h =3D h; + hugetlb_acct_memory_delta =3D delta; + return 0; +} + +static void reset_hugetlb_acct_memory_mock(void) +{ + hugetlb_acct_memory_called =3D false; + hugetlb_acct_memory_h =3D NULL; + hugetlb_acct_memory_delta =3D 0; +} + +static void assert_hugetlb_acct_memory_called(struct hstate *h, long delta) +{ + assert(hugetlb_acct_memory_called); + assert(hugetlb_acct_memory_h =3D=3D h); + assert(hugetlb_acct_memory_delta =3D=3D delta); + + reset_hugetlb_acct_memory_mock(); +} + +static void assert_hugetlb_acct_memory_not_called(void) +{ + assert(!hugetlb_acct_memory_called); +} + +#include "../../../mm/hugetlb_subpool.h" +#include "../../../mm/hugetlb_subpool.c" + +static void test_subpool_new_put_no_min_limit(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + + spool =3D hugepage_new_subpool(&h, 10, -1); + assert(spool !=3D NULL); + assert(spool->max_hpages =3D=3D 10); + assert(spool->min_hpages =3D=3D -1); + assert(spool->rsv_hpages =3D=3D -1); + assert(spool->count =3D=3D 1); + assert_hugetlb_acct_memory_not_called(); + + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_not_called(); +} + +static void test_subpool_new_put_with_min_limit(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + + spool =3D hugepage_new_subpool(&h, 20, 5); + assert(spool !=3D NULL); + assert(spool->max_hpages =3D=3D 20); + assert(spool->min_hpages =3D=3D 5); + assert(spool->rsv_hpages =3D=3D 5); + assert(spool->count =3D=3D 1); + assert_hugetlb_acct_memory_called(&h, 5); + + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); +} + +static void test_subpool_get_pages_below_min(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + long ret; + + /* Let's initialize: min_hpages =3D 10, used_hpages =3D 9, rsv_hpages =3D= 1 */ + spool =3D hugepage_new_subpool(&h, -1, 10); + assert_hugetlb_acct_memory_called(&h, 10); + + ret =3D hugepage_subpool_get_pages(spool, 9); + assert(ret =3D=3D 0); + assert(spool->used_hpages =3D=3D 9); + assert(spool->rsv_hpages =3D=3D 1); + + /* Invoke Get (Consumes the remaining 1 subpool reserve!) */ + ret =3D hugepage_subpool_get_pages(spool, 1); + assert(ret =3D=3D 0); /* Covered by subpool reserve! */ + assert(spool->used_hpages =3D=3D 10); + assert(spool->rsv_hpages =3D=3D 0); + + /* Invoke Put (Replenishes the subpool reserve!) */ + ret =3D hugepage_subpool_put_pages(spool, 1); + assert(ret =3D=3D 0); /* Kept by subpool reserve! */ + assert(spool->used_hpages =3D=3D 9); + assert(spool->rsv_hpages =3D=3D 1); + + /* Cleanup: Return used_hpages to 0 so the subpool frees symmetrically! */ + hugepage_subpool_put_pages(spool, 9); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -10); +} + +static void test_subpool_get_pages_crossing_min(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + long ret; + + /* Let's initialize: min_hpages =3D 10, used_hpages =3D 10, rsv_hpages = =3D 0 */ + spool =3D hugepage_new_subpool(&h, -1, 10); + assert_hugetlb_acct_memory_called(&h, 10); + + hugepage_subpool_get_pages(spool, 10); + assert(spool->used_hpages =3D=3D 10); + assert(spool->rsv_hpages =3D=3D 0); + + /* Invoke Get (Triggers a request for a Global Buddy/Surplus page!) */ + ret =3D hugepage_subpool_get_pages(spool, 1); + assert(ret =3D=3D 1); /* Requires global page! */ + assert(spool->used_hpages =3D=3D 11); + assert(spool->rsv_hpages =3D=3D 0); + + /* Invoke Put (Above minimum, so it releases the page to the Global Pool!= ) */ + ret =3D hugepage_subpool_put_pages(spool, 1); + assert(ret =3D=3D 1); /* Dropped to global pool! */ + assert(spool->used_hpages =3D=3D 10); + assert(spool->rsv_hpages =3D=3D 0); + + /* Cleanup */ + hugepage_subpool_put_pages(spool, 10); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -10); +} + +static void test_subpool_get_pages_crossing_min_multi(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + long ret; + + /* Scenario 1: Crossing entirely into surplus territory by a delta > 1 */ + /* Let's initialize: min_hpages =3D 10, used_hpages =3D 8, rsv_hpages =3D= 2 */ + spool =3D hugepage_new_subpool(&h, -1, 10); + assert_hugetlb_acct_memory_called(&h, 10); + + ret =3D hugepage_subpool_get_pages(spool, 8); + assert(ret =3D=3D 0); + assert(spool->used_hpages =3D=3D 8); + assert(spool->rsv_hpages =3D=3D 2); + + /* Invoke Get with delta =3D 5 (Crosses min limit of 10 up to 13) */ + ret =3D hugepage_subpool_get_pages(spool, 5); + assert(ret =3D=3D 3); /* (8 + 5) - 10 =3D 3 global pages required! */ + assert(spool->used_hpages =3D=3D 13); + assert(spool->rsv_hpages =3D=3D 0); + + /* Invoke Put with delta =3D 5 (Drops from 13 down to 8) */ + ret =3D hugepage_subpool_put_pages(spool, 5); + assert(ret =3D=3D 3); /* 3 surplus pages released to the global pool! */ + assert(spool->used_hpages =3D=3D 8); + assert(spool->rsv_hpages =3D=3D 2); /* 2 subpool reserves perfectly resto= red! */ + + /* Scenario 2: Landing exactly on the min_hpages boundary with delta > 1 = */ + ret =3D hugepage_subpool_get_pages(spool, 2); + assert(ret =3D=3D 0); /* Perfectly covered by remaining 2 subpool reserve= s! */ + assert(spool->used_hpages =3D=3D 10); + assert(spool->rsv_hpages =3D=3D 0); + + ret =3D hugepage_subpool_put_pages(spool, 2); + assert(ret =3D=3D 0); /* Swallowed perfectly to replenish the 2 subpool r= eserves! */ + assert(spool->used_hpages =3D=3D 8); + assert(spool->rsv_hpages =3D=3D 2); + + /* Cleanup */ + hugepage_subpool_put_pages(spool, 8); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -10); +} + +static void test_subpool_get_pages_max_limit(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + long ret; + + spool =3D hugepage_new_subpool(&h, 5, -1); + assert_hugetlb_acct_memory_not_called(); + + ret =3D hugepage_subpool_get_pages(spool, 5); + assert(ret =3D=3D 5); + assert(spool->used_hpages =3D=3D 5); + assert(spool->rsv_hpages =3D=3D -1); + + /* Invoke Get (Should trigger -ENOMEM due to max cap limit exceeded!) */ + ret =3D hugepage_subpool_get_pages(spool, 1); + assert(ret =3D=3D -ENOMEM); + assert(spool->used_hpages =3D=3D 5); /* Unchanged */ + + /* Cleanup */ + hugepage_subpool_put_pages(spool, 5); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_not_called(); +} + +static void test_subpool_get_pages_no_limits(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + long ret; + + spool =3D hugepage_new_subpool(&h, -1, -1); + assert_hugetlb_acct_memory_not_called(); + + hugepage_subpool_get_pages(spool, 5); + assert(spool->used_hpages =3D=3D 5); + + /* Invoke Get (Surplus Global Territory) */ + ret =3D hugepage_subpool_get_pages(spool, 2); + assert(ret =3D=3D 2); + assert(spool->used_hpages =3D=3D 7); + + /* Invoke Put */ + ret =3D hugepage_subpool_put_pages(spool, 2); + assert(ret =3D=3D 2); + assert(spool->used_hpages =3D=3D 5); + + /* Cleanup */ + hugepage_subpool_put_pages(spool, 5); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_not_called(); +} + +static void test_subpool_free_hpages(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + + /* Test that free_hpages with NO min_size works perfectly */ + spool =3D hugepage_new_subpool(&h, 15, -1); + hugepage_subpool_get_pages(spool, 3); + assert(hugepage_subpool_free_hpages(spool) =3D=3D 12); + hugepage_subpool_put_pages(spool, 3); + hugepage_put_subpool(spool); + + /* Test that free_hpages with a min_size configured is COMPLETELY UNAFFEC= TED by it */ + spool =3D hugepage_new_subpool(&h, 15, 5); + assert_hugetlb_acct_memory_called(&h, 5); + hugepage_subpool_get_pages(spool, 3); + assert(hugepage_subpool_free_hpages(spool) =3D=3D 12); /* Should still be= 15 - 3 =3D 12! */ + hugepage_subpool_put_pages(spool, 3); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + spool =3D hugepage_new_subpool(&h, -1, -1); + hugepage_subpool_get_pages(spool, 3); + assert(hugepage_subpool_free_hpages(spool) =3D=3D -1); + hugepage_subpool_put_pages(spool, 3); + hugepage_put_subpool(spool); + + /* Test that free_hpages with a min_size configured and NO max size retur= ns -1 */ + spool =3D hugepage_new_subpool(&h, -1, 5); + assert_hugetlb_acct_memory_called(&h, 5); + hugepage_subpool_get_pages(spool, 3); + assert(hugepage_subpool_free_hpages(spool) =3D=3D -1); + hugepage_subpool_put_pages(spool, 3); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + spool =3D hugepage_new_subpool(&h, 3, -1); + hugepage_subpool_get_pages(spool, 3); + assert(hugepage_subpool_free_hpages(spool) =3D=3D 0); + hugepage_subpool_put_pages(spool, 3); + hugepage_put_subpool(spool); +} + +static void test_subpool_max_hpages(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + + spool =3D hugepage_new_subpool(&h, 123, -1); + assert(hugepage_subpool_max_hpages(spool) =3D=3D 123); + hugepage_put_subpool(spool); + + /* Test that max_hpages with a min_size configured is COMPLETELY UNAFFECT= ED by it */ + spool =3D hugepage_new_subpool(&h, 123, 5); + assert_hugetlb_acct_memory_called(&h, 5); + assert(hugepage_subpool_max_hpages(spool) =3D=3D 123); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + spool =3D hugepage_new_subpool(&h, -1, -1); + assert(hugepage_subpool_max_hpages(spool) =3D=3D -1); + hugepage_put_subpool(spool); + + spool =3D hugepage_new_subpool(&h, -1, 5); + assert_hugetlb_acct_memory_called(&h, 5); + assert(hugepage_subpool_max_hpages(spool) =3D=3D -1); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + spool =3D hugepage_new_subpool(&h, 0, -1); + assert(hugepage_subpool_max_hpages(spool) =3D=3D 0); + hugepage_put_subpool(spool); +} + +static void test_subpool_max_size(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + + spool =3D hugepage_new_subpool(&h, 10, -1); + assert(hugepage_subpool_max_size(spool) =3D=3D (10ULL << 21)); + hugepage_put_subpool(spool); + + /* Test that max_size with a min_size configured is COMPLETELY UNAFFECTED= by it */ + spool =3D hugepage_new_subpool(&h, 10, 5); + assert_hugetlb_acct_memory_called(&h, 5); + assert(hugepage_subpool_max_size(spool) =3D=3D (10ULL << 21)); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + spool =3D hugepage_new_subpool(&h, -1, -1); + assert(hugepage_subpool_max_size(spool) =3D=3D -1ULL); + hugepage_put_subpool(spool); + + spool =3D hugepage_new_subpool(&h, 0, -1); + assert(hugepage_subpool_max_size(spool) =3D=3D 0ULL); + hugepage_put_subpool(spool); +} + +static void test_subpool_min_size(void) +{ + struct hstate h; + struct hugepage_subpool *spool; + + spool =3D hugepage_new_subpool(&h, -1, 5); + assert_hugetlb_acct_memory_called(&h, 5); + assert(hugepage_subpool_min_size(spool) =3D=3D (5ULL << 21)); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + /* Test that min_size with a max_size configured is COMPLETELY UNAFFECTED= by it */ + spool =3D hugepage_new_subpool(&h, 20, 5); + assert_hugetlb_acct_memory_called(&h, 5); + assert(hugepage_subpool_min_size(spool) =3D=3D (5ULL << 21)); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, -5); + + spool =3D hugepage_new_subpool(&h, -1, -1); + assert(hugepage_subpool_min_size(spool) =3D=3D -1ULL); + hugepage_put_subpool(spool); + + spool =3D hugepage_new_subpool(&h, -1, 0); + assert_hugetlb_acct_memory_called(&h, 0); + assert(hugepage_subpool_min_size(spool) =3D=3D 0ULL); + hugepage_put_subpool(spool); + assert_hugetlb_acct_memory_called(&h, 0); +} + +int main(void) +{ + test_subpool_new_put_no_min_limit(); + test_subpool_new_put_with_min_limit(); + test_subpool_get_pages_below_min(); + test_subpool_get_pages_crossing_min(); + test_subpool_get_pages_crossing_min_multi(); + test_subpool_get_pages_max_limit(); + test_subpool_get_pages_no_limits(); + test_subpool_free_hpages(); + test_subpool_max_hpages(); + test_subpool_max_size(); + test_subpool_min_size(); + + return 0; +} --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A46CB402BB6; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=rzm8a24loJwnDSryRMVcfb2eIsM+PLnvRRycSMNt7/Kbt7M+rO7xJuRYI2ehrOkVUR73qfaPrkftZyYU7058SxYgkfTmTwNScR8+eBmcinX857FQJVDgff077FTXCuZZxi5IAOvYqt25+vDg+ehmm56SI9SNcI7OCjadl4nGxvg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=JN6H6TTEJzbvYH25RrfMzyPfNqVedceCGM/RcbDdIwA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=a4OxhpRx26iRMVNiHZp1cudKX32PZkCcKVdhAulIC2OGwO/HZp6oRXtO+pT9wDtG88/n8K1KET7+9Z6Cl0tnfww+2jDwplYv9NQC9XFsmZznFAnHIZMonvl+yfSrZ5oo0HUJBc/clSSbqgbPLQrLqeNcspziaLukmwXd/B3v7XM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=RAEtU+hJ; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="RAEtU+hJ" Received: by smtp.kernel.org (Postfix) with ESMTPS id 779C7C2BCB8; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=JN6H6TTEJzbvYH25RrfMzyPfNqVedceCGM/RcbDdIwA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=RAEtU+hJ7H3VJ5PXA1C675J/RR2yxJobycpgjyOQWFOwjT8kE0E7aoJfcc+mBJjB8 TNpjdZy5r6QfjZ1Gz0Pdll5sR7HzX33gZ//x1GD7jHDOR7E7hGp2qjkGvoFqNiqja0 ch+cse6HE6Znd23sIP2NJWxQBZf4nuHRM2KJfqfatm5loh3rMF7b0D75AhzJir/ykL CRG1fO5Z0BqZ5HGAV46fsATdeY+4C1nDxXB85gYTJbuyV2uzhkR8GoalOWoBlV5few BLZJzNM1lcJo/ot7BQHNEAg6uPYcjyZBABwNNIjJ41z44w1kDEgQMRI7tdaoCkmMPs VC+VIxvTf2wpg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 659C1C4452F; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:13 -0700 Subject: [PATCH v3 10/13] WIP: Reproducer for allocation failure due to cgroup v2 memory limits Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-10-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=7657; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=xlDHVWmQGlscLY1/vH8LU+p8GX41QLH6dh7Iau2jVGw=; b=pv1kHsyVab3V469NIs8r8t5Xu97t1OOkzogw/RCbJoFx5KM4tBpOxZYITemukdcxyGKrFYabG QV/4bwcsTqYDepVn6nKC1eniBcH5Toj295QFm8PkaHx229yv1ncKufR X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng (This reproducer was hacked up and not meant to be merged.) cgroup_v2_allocation_failure.c triggers HugeTLB allocation failure by explo= iting cgroup v2 memory limits. This allows testing the error paths in the kernel = when memory control charging fails, even when physical huge pages are available. The program performs the following steps to trigger the failure: 1. Enable hugetlb accounting in cgroup v2. + The program checks if memory_hugetlb_accounting is enabled in the cg= roup2 mount options. If not, it remounts /sys/fs/cgroup with this option enabled. This ensures that HugeTLB allocations are charged against t= he cgroup memory limits. 2. Create a test cgroup and set limits. + The program creates a new cgroup subdirectory named test_reproducer = under /sys/fs/cgroup. + It sets the memory.max limit of this cgroup to 1MB (which is less th= an the 2MB huge page size). 3. Fork a child process and move it to the test cgroup. + The program forks a child process. + The child process moves itself into the test_reproducer cgroup by wr= iting its PID (using 0 for current process) to cgroup.procs in the test cg= roup directory. 4. Attempt to allocate and touch a 2MB huge page. + The child process maps a 2MB anonymous huge page using mmap with MAP_PRIVATE, MAP_ANONYMOUS, and MAP_HUGETLB. + The child process writes to the mapped address, triggering a page fa= ult. 5. Triggering the kernel bugs. + The page fault handler calls alloc_hugetlb_folio to allocate the huge page. + The allocation of the physical page from buddy allocator succeeds (assuming nr_hugepages is sufficient). + The kernel then attempts to charge this allocation to the child proc= ess's cgroup by calling mem_cgroup_charge_hugetlb. + Since the child's cgroup memory limit is 1MB and the page is 2MB, the charge fails and mem_cgroup_charge_hugetlb returns -ENOMEM. + This triggers the error path in alloc_hugetlb_folio where the bugs (= folio refcount mismatch, infinite loop on ENOMEM, and reservation leaks) a= re handled. --- cgroup_v2_allocation_failure.c | 160 +++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 160 insertions(+) diff --git a/cgroup_v2_allocation_failure.c b/cgroup_v2_allocation_failure.c new file mode 100644 index 0000000000000..938cbf02ae6f7 --- /dev/null +++ b/cgroup_v2_allocation_failure.c @@ -0,0 +1,160 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CGROUP_PATH "/sys/fs/cgroup" +#define TEST_CGROUP "test_reproducer" +#define TEST_CGROUP_PATH CGROUP_PATH "/" TEST_CGROUP + +void write_file(const char *path, const char *val) { + int fd =3D open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + exit(1); + } + if (write(fd, val, strlen(val)) < 0) { + fprintf(stderr, "Failed to write %s to %s: %s\n", val, path, strer= ror(errno)); + close(fd); + exit(1); + } + close(fd); +} + +int is_hugetlb_accounting_enabled() { + FILE *fp =3D fopen("/proc/mounts", "r"); + if (!fp) { + perror("fopen /proc/mounts"); + return -1; + } + + char line[1024]; + int enabled =3D 0; + while (fgets(line, sizeof(line), fp)) { + char spec[256], file[256], type[256], opts[512]; + if (sscanf(line, "%255s %255s %255s %511s", spec, file, type, opts= ) =3D=3D 4) { + if (strcmp(file, CGROUP_PATH) =3D=3D 0 && strcmp(type, "cgroup= 2") =3D=3D 0) { + if (strstr(opts, "memory_hugetlb_accounting") !=3D NULL) { + enabled =3D 1; + } + break; + } + } + } + fclose(fp); + return enabled; +} + +int enable_hugetlb_accounting() { + printf("Attempting to remount cgroup2 with memory_hugetlb_accounting..= .\n"); + int ret =3D system("mount -o remount,memory_hugetlb_accounting " CGROU= P_PATH); + if (ret !=3D 0) { + fprintf(stderr, "Failed to remount: system() returned %d\n", ret); + return -1; + } + return 0; +} + +int main() { + struct stat st; + if (stat(CGROUP_PATH, &st) !=3D 0 || !S_ISDIR(st.st_mode)) { + fprintf(stderr, "cgroup v2 not mounted at %s\n", CGROUP_PATH); + return 1; + } + + int enabled =3D is_hugetlb_accounting_enabled(); + if (enabled < 0) { + return 1; + } + if (!enabled) { + if (enable_hugetlb_accounting() !=3D 0) { + fprintf(stderr, "Could not enable memory_hugetlb_accounting\n"= ); + return 1; + } + // Re-check + enabled =3D is_hugetlb_accounting_enabled(); + if (enabled <=3D 0) { + fprintf(stderr, "Failed to enable memory_hugetlb_accounting (r= e-check failed)\n"); + return 1; + } + printf("Successfully enabled memory_hugetlb_accounting\n"); + } else { + printf("memory_hugetlb_accounting is already enabled\n"); + } + + // Enable memory controller in subtree + int fd =3D open(CGROUP_PATH "/cgroup.subtree_control", O_WRONLY); + if (fd >=3D 0) { + if (write(fd, "+memory", 7) < 0) { + // Might fail if already enabled or not supported, ignore for = now + } + close(fd); + } + + if (mkdir(TEST_CGROUP_PATH, 0755) !=3D 0) { + if (errno !=3D EEXIST) { + perror("mkdir test_reproducer"); + return 1; + } + } + + // Set memory limit to 1MB (less than 2MB hugepage) + write_file(TEST_CGROUP_PATH "/memory.max", "1M"); + + pid_t pid =3D fork(); + if (pid < 0) { + perror("fork"); + return 1; + } + + if (pid =3D=3D 0) { + // Child + // Move to cgroup + write_file(TEST_CGROUP_PATH "/cgroup.procs", "0"); + + printf("Child: Attempting to allocate and touch 2MB hugepage...\n"= ); + // Allocate 2MB hugepage + size_t size =3D 2 * 1024 * 1024; + void *addr =3D mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0= ); + if (addr =3D=3D MAP_FAILED) { + perror("Child: mmap MAP_HUGETLB"); + exit(1); + } + + printf("Child: mmap succeeded at %p, touching it now (should trigg= er fault)...\n", addr); + // This should trigger the fault and call alloc_hugetlb_folio -> m= em_cgroup_charge_hugetlb + // which should fail and trigger the bug. + *(volatile char *)addr =3D 1; + + printf("Child: Successfully touched page (bug not triggered?).\n"); + munmap(addr, size); + exit(0); + } + + // Parent + int status; + waitpid(pid, &status, 0); + + printf("Parent: Child exited. Cleaning up.\n"); + rmdir(TEST_CGROUP_PATH); + + if (WIFSIGNALED(status)) { + printf("Parent: Child killed by signal %d (%s)\n", + WTERMSIG(status), strsignal(WTERMSIG(status))); + if (WTERMSIG(status) =3D=3D SIGBUS) { + printf("Parent: Child got SIGBUS as expected (if kernel didn't= crash).\n"); + } + } else if (WIFEXITED(status)) { + printf("Parent: Child exited with status %d\n", WEXITSTATUS(status= )); + } + + return 0; +} --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A3DC8402BB3; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=dQXXzC1kfij/TgaBC0beXNH12z2NDUTfnr2cEYq4dcTPifx1njVj6ZOjkCYxxSNOjRqwM6I1B4i1v9CAPmQOMGHbCnmE9m+xtQVkesHFvejiueWo+z40ZddguG8ccVmSO60ujH1J8BRmODT1Bk3LnEgISWwD9j0nmurUIoSS0tk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=kttlEIrRapL9va7fSljR3xrwSzOf2bb6snXuzZxqZ30=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=iMXzVgoVnb9MXWaGLBjf/mXvWD81s7NA63jg4L6F8mF000macsCL4V/86J53XhuJP693hzZanBcGlHOqvviAO6P+PHbI7MKxvqoXIsyWbV4fTqmevIWSr0Ub8oWxEKw8aNV2rBcbwLQIVnmZkPqI/KHIIYn5/FbGbOWX6Ae2OqU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ZOhggKih; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ZOhggKih" Received: by smtp.kernel.org (Postfix) with ESMTPS id 881F8C2BCF7; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=kttlEIrRapL9va7fSljR3xrwSzOf2bb6snXuzZxqZ30=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ZOhggKih7xOk/MyAuJWaa130N+z1Q9x1F+XEfuajwBORDjzg1QKcUta3uHiqj+QLu vXPnHaoq/uE8J/O1kfEKI1trcY97vfO1cjJWnRJ5bVIInHaZKKPS7o/onuZMvZHEl9 LAwiKs1u5gGTP7z4Ryki0KHNtoZjge6VdqF30jE+gdXuNxEbmj1DVXW5r0C0RYOudc PD9DBKg7uiMcqKXwVe7X6R2Ql3/vjd9aXtc55UfZJYuLdnIEiRN7YRFPzHzeWx+1SX tNqIrk8XWy6dodqh62rbSfIA/XoCoKUGkTpjdK2cYvI0HZilpYvioLpoO72CBstkUT wYy9P+MUM4i7w== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 74A0BC44524; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:14 -0700 Subject: [PATCH v3 11/13] WIP: Reproducer for subpool usage leak Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-11-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=4607; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=inX7G11YaaxbwEU3Aj5PB0crlLMuo9T1V2I+32/3MYg=; b=2snhi0Fk67JboBtC3TcQ7c/BnoASl8jauKmvWjHDI4B5pTaAhEL4c/GXuRyTy283zI1c7EcHa Em1FnEeYordAxOBRStKhF9bHrxmCLw+F4rYPMhqRBYEn6LJ0/c0I9V8 X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng (This reproducer was hacked up and not meant to be merged.) The kernel leaks subpool usage and the subpool structure itself if a HugeTL= Bfs mount specifying size (which sets max_hpages on the subpool) is created. subpool_leak_max_size.sh reproduces this with the following steps: 1. Create mount, specifying size=3D2M (1 page). This sets max_hpages =3D 1 = on the subpool, but does not reserve any pages. 2. Set nr_hugepages =3D 0 and nr_overcommit_hugepages =3D 0 so that physical allocations will fail. 3. Run fallocate -l 2M on a file in the mount. + This calls hugetlbfs_fallocate, which attempts to allocate a page by calling alloc_hugetlb_folio. + alloc_hugetlb_folio calls hugepage_subpool_get_pages to track the allocation against the subpool limit. This increments used_hpages to= 1. + Physical allocation fails because nr_hugepages is 0. + Before patch (Buggy): + The error path in alloc_hugetlb_folio sees gbl_chg is 1 (indicat= ing we tried to allocate a global page) and incorrectly skips calling hugepage_subpool_put_pages. + fallocate fails and returns to userspace, but the subpool used_h= pages counter remains leaked at 1. + After patch: + The error path always calls hugepage_subpool_put_pages if map_ch= g is true, restoring used_hpages to 0. 4. Unmount the filesystem. + During unmount, the kernel calls unlock_or_release_subpool to clean = up the subpool. + It checks if the subpool is free using subpool_is_free, which returns whether used_hpages is 0. + Before patch (Buggy): + Since used_hpages leaked and is 1, subpool_is_free returns false. + The kernel skips freeing the subpool structure, leaking the hugepage_subpool structure in kernel memory. + After patch: + Since used_hpages is 0, subpool_is_free returns true, and the su= bpool structure is correctly freed. Signed-off-by: Ackerley Tng --- subpool_leak_max_size.sh | 71 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 71 insertions(+) diff --git a/subpool_leak_max_size.sh b/subpool_leak_max_size.sh new file mode 100755 index 0000000000000..bfafa1ba074ea --- /dev/null +++ b/subpool_leak_max_size.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +if [ "$EUID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +MNT_PATH=3D"/tmp/mnt_hugetlb" +FILE_PATH=3D"$MNT_PATH/test_file" + +# Save original values +orig_nr=3D$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages) +orig_overcommit=3D$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overc= ommit_hugepages) + +cleanup() { + echo "Cleaning up..." + rm -f "$FILE_PATH" + umount "$MNT_PATH" 2>/dev/null + rmdir "$MNT_PATH" 2>/dev/null + echo "$orig_nr" > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepag= es + echo "$orig_overcommit" > /sys/kernel/mm/hugepages/hugepages-2048kB/nr= _overcommit_hugepages + echo "Cleanup done." +} +trap cleanup EXIT + +# 1. Mount hugetlbfs with size=3D2M (1 page) +mkdir -p "$MNT_PATH" +if ! mount -t hugetlbfs -o size=3D2M none "$MNT_PATH"; then + echo "Failed to mount hugetlbfs" + exit 1 +fi + +# 2. Set nr_hugepages to 0, overcommit to 0 +echo 0 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages +echo 0 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages + +# Check subpool usage before running +read total free < <(stat -f -c "%b %f" "$MNT_PATH") +used_before=3D$((total - free)) +echo "Before test - Subpool total blocks: $total" +echo "Before test - Subpool free blocks: $free" +echo "Before test - Subpool used blocks: $used_before" +if [ "$used_before" -ne 0 ]; then + echo "ERROR: Subpool is not clean before test starts!" + exit 1 +fi + +# Run fallocate (expecting failure) +echo "Running fallocate (expecting failure)..." +if fallocate -l 2M "$FILE_PATH" 2>/dev/null; then + echo "ERROR: fallocate succeeded but should have failed (nr_hugepages = is 0)" + exit 1 +fi + +# Check subpool usage via statfs +# %b: Total blocks +# %f: Free blocks +read total free < <(stat -f -c "%b %f" "$MNT_PATH") +used=3D$((total - free)) + +echo "Subpool total blocks: $total" +echo "Subpool free blocks: $free" +echo "Subpool used blocks (leaked if > 0): $used" + +if [ "$used" -gt 0 ]; then + echo "RESULT: LEAK DETECTED (FAIL)" + exit 1 +else + echo "RESULT: NO LEAK (PASS)" + exit 0 +fi --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B27E240312F; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=Tu1XQQOPmoH1AM03vQQv5fM4y1DeabH87QLNkRlb58pDUMQqaXiPMg9njdar+C38KF10OCneFva09MxP2MUwljiC2wez+IB4Ay3Aoqke+tQ9avySj4mXIYxgaelU5yEnaR/KedglArsHUOH9KOS1tNLDt25Qg/nSH/HoO7o1cZY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=BRUUYfu8mt0VqxcSHCOHu8zZh6KmZhzrsSjDA7yculM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=jH+5lcfbc11ZrMD5DlEBgghZO3vVkhrtAH4armLd/rYFs7pHTj2bTUCMOwt8CRZblxAgq1mFmo9yBqOVdb46Z/OXE4k/41xULvJNSiSGeQxY0FIOXGUleRc6VsTyp6lUghI3PL+mtMiOHh19x5ppHznZ8Nft2ZXyRPO3hCXYoL8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=U0Rb1i3I; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="U0Rb1i3I" Received: by smtp.kernel.org (Postfix) with ESMTPS id 97407C2BD04; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=BRUUYfu8mt0VqxcSHCOHu8zZh6KmZhzrsSjDA7yculM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=U0Rb1i3I4XCunQzSR9CWtsgw5t2PsaZfqd+AH4Wjmd+5+2MjJWYdfsa6SP6lzH64t W+uFtzZcn7DNizsTNM36wD5f7XpV99fN9pqclwUrAEADDn9AxxMfoTjJOIy9myAzi8 wiJ+QVC1MYJkI+iwwtooyUvnXwJLPFo7te+RFbEdDNy46yCMJK3t9+QdKEJTsUS7l3 w33oICbgDrNZ3NkLxuJJrTq3QWurhwNLeHAirnJVhB0Atx5EO4xm7K4U7n2T3asAFE lZ+2xkJhqTYQVbwHAt9flzLGi+qGMJWVZleZUVJ23JHnLrmr3Ku7OLRGeLGYN98Qow bONkGbdAqOYag== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 85CD9C44531; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:15 -0700 Subject: [PATCH v3 12/13] WIP: Reproducer for false restoration on shared HugeTLB mappings Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-12-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=7498; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=LHVH6iro3tu1LWvMV8EW8sUeU8cR100fPFYCaB/S9Ok=; b=oFS3CkZitpG+alLvyZpYlk3k8E2vLTClk7USUVHxwZULgXsGKKxJ5wYxunf0kfaIdDuqnt2Uz PLmIfYDnrhzAc+mzA9TR3qrqdQy11l2rm1O1H9SIo0pkl7ppy7gim/B X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng (This reproducer was hacked up and not meant to be merged.) hugetlb_unreserve_pages() unconditionally returns reservations to the subpo= ol (via hugepage_subpool_put_pages()). This means that regardless of whether a subpool reservation was actually used, the reservation is processed by the subpool structure. To create a false restoration, the reproducer performs these steps: 1. Mount with min_size=3D2M (1 page). Global resv_hugepages becomes 1. 2. The program maps 4MB (2 pages) shared (which also grows the file to 4MB). Global resv_hugepages becomes 2 (1 from the mount, 1 new global reservation). 3. The program populates only the first page. Global resv_hugepages decrem= ents to 1 (reservation consumed by allocation). 4. The program exits (closing VMAs/fds). For shared mappings, reservations= are associated with the file inode, so they remain active. Global resv_huge= pages remains 1. 5. The script truncates the file to 2MB (truncate -s 2M). + This synchronously triggers hugetlb_unreserve_pages() to release the reservation of the truncated range (the unallocated 2nd page). + It calls hugepage_subpool_put_pages(spool, 1). + On Vanilla Kernel (Buggy): + used_hpages is 0 (not tracked). + used_hpages (0) < min_hpages (1) is TRUE. + The subpool incorrectly restores the reservation (spool->rsv_hp= ages becomes 1), even though Page 0 is still allocated and satisfies= the mount's minimum guarantee. + hugepage_subpool_put_pages() returns 0, skipping hugetlb_acct_memory(h, -1). + Result: Global resv_hugepages remains stuck at 1 (Leak). + On Fixed Kernel: + used_hpages is tracked and is initially 2. + hugepage_subpool_put_pages(1) decrements used_hpages to 1. + used_hpages (1) < min_hpages (1) is FALSE. + The subpool does not restore the reservation. + hugepage_subpool_put_pages() returns 1. + hugetlb_acct_memory(h, -1) is called. + Result: Global resv_hugepages decrements to 0 (No leak). When the filesystem is unmounted, hugetlbfs_put_super drops the subpool reference. Since the filesystem is being unmounted, the reference count dro= ps to 0, triggering unlock_or_release_subpool. Inside unlock_or_release_subpool, the kernel checks if the subpool is free = using subpool_is_free. + On the buggy kernel, subpool_is_free checks if spool->rsv_hpages is equal= to spool->min_hpages. Because of the phantom reservation, spool->rsv_hpages = was restored to 1. Since min_hpages is 1, the check (1 =3D=3D 1) returns true. + Since the subpool is considered free, the kernel releases the initial mount-time reservation by calling hugetlb_acct_memory to decrement resv_huge_pages by spool->min_hpages (which is 1). + This decrement reduces resv_huge_pages from 1 (the leaked state) to 0. As a result, the leaked reservation is cleaned up during unmount and does n= ot persist afterward. Signed-off-by: Ackerley Tng --- subpool_shared_leak.c | 29 +++++++++++++++++ subpool_shared_leak.sh | 86 ++++++++++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 115 insertions(+) diff --git a/subpool_shared_leak.c b/subpool_shared_leak.c new file mode 100644 index 0000000000000..5811e18d7f8be --- /dev/null +++ b/subpool_shared_leak.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include + +#define HPAGE_SIZE (2 * 1024 * 1024) + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + const char *file_path =3D argv[1]; + + int fd =3D open(file_path, O_CREAT | O_RDWR, 0666); + if (fd < 0) { perror("open"); return 1; } + + void *addr =3D mmap(NULL, 2 * HPAGE_SIZE, PROT_READ | PROT_WRITE, MAP_= SHARED, fd, 0); + if (addr =3D=3D MAP_FAILED) { perror("mmap"); close(fd); return 1; } + + *(volatile char *)addr =3D 1; // Allocate 1st page only. 2nd page rema= ins unallocated (but reserved). + + munmap(addr, 2 * HPAGE_SIZE); + close(fd); + + return 0; +} diff --git a/subpool_shared_leak.sh b/subpool_shared_leak.sh new file mode 100755 index 0000000000000..46c622b18559a --- /dev/null +++ b/subpool_shared_leak.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +if [ "$EUID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +MNT_PATH=3D"/tmp/mnt_hugetlb_shared_leak" +FILE_PATH=3D"$MNT_PATH/test_file" + +# Save original values +orig_nr=3D$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages) + +cleanup() { + echo "Cleaning up..." + rm -f "$FILE_PATH" + umount "$MNT_PATH" 2>/dev/null + rmdir "$MNT_PATH" 2>/dev/null + echo "$orig_nr" > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepag= es + echo "Cleanup done." +} +trap cleanup EXIT + +# 1. Set nr_hugepages to 2 +echo 2 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages + +# 2. Mount hugetlbfs with min_size=3D2M (1 page) +mkdir -p "$MNT_PATH" +if ! mount -t hugetlbfs -o min_size=3D2M none "$MNT_PATH"; then + echo "Failed to mount hugetlbfs" + exit 1 +fi + +# Check resv_hugepages after mount (should be 1) +initial_resv=3D$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepa= ges) +echo "Initial resv_hugepages (after mount): $initial_resv" +if [ "$initial_resv" -ne 1 ]; then + echo "ERROR: Initial resv_hugepages is not 1!" + exit 1 +fi + +# Verify reproducer binary exists +if [ ! -x ./subpool_shared_leak ]; then + echo "reproducer binary './subpool_shared_leak' not found or not execu= table." + echo "Please compile it first: gcc -static -o subpool_shared_leak subp= ool_shared_leak.c" + exit 1 +fi + +# 3. Run helper to map 4MB, allocate 2MB, and close. +# This creates 2 reservations, consumes 1 (by allocating Page 0). +# The unallocated Page 1 reservation remains active in the inode's resv_ma= p. +echo "Running helper..." +./subpool_shared_leak "$FILE_PATH" + +resv_after_helper=3D$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_h= ugepages) +echo "resv_hugepages after helper (should be 1): $resv_after_helper" +# Page 0 is allocated (no longer reserved). Page 1 is reserved. +# So resv_hugepages should be 1. +if [ "$resv_after_helper" -ne 1 ]; then + echo "ERROR: resv_hugepages is not 1 after helper run!" + exit 1 +fi + +# 4. Truncate file to 2MB (releases Page 1 reservation) +echo "Truncating file to 2MB (releasing 1 page reservation)..." +truncate -s 2M "$FILE_PATH" + +# Check resv_hugepages after truncate. +# Since Page 0 is still allocated (and in page cache), and satisfies the +# min_size=3D2M guarantee, we should have 0 reservations remaining. +# If the bug is present, the truncate path will incorrectly restore the +# reservation to the subpool and skip releasing it globally, leaving +# resv_hugepages at 1. +final_resv=3D$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepage= s) +echo "Final resv_hugepages (after 2MB truncate): $final_resv" + +if [ "$final_resv" -eq 1 ]; then + echo "RESULT: LEAK DETECTED (FAIL)" + exit 1 +elif [ "$final_resv" -eq 0 ]; then + echo "RESULT: NO LEAK (PASS)" + exit 0 +else + echo "RESULT: UNEXPECTED STATE ($final_resv)" + exit 2 +fi --=20 2.55.0.229.g6434b31f56-goog From nobody Sat Jul 25 01:35:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C59AD40315F; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; cv=none; b=qfD0Watwizf/f03yMUGUi+Zw4dPa+NgiZ/NzUBtITm70ZL1ey8CyT4168T5YpD9EFAzbyVkRygdE1gPHjVlG+UoUhWBeZbPX8PzoouSNQ/xa5jjAKuQkCXPc42p+UBco7/Jf/WLkskVnSkiTGjDAoK9WzFTF0AD882jygWulRFE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784602389; c=relaxed/simple; bh=JpV22Go91sm1l5ZmXhWEkSXKUuaRnYmpd8p8KT1aw60=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UxnQRSxvQFNehE9AOwnQFxlQ+is77jcHeOslqSs9oSm77h4uZK5fKpaFcnlmc/Xz1+npNedAreuQ712GxrSzLzYkYR0tyx0H9rEUPzBn7pLOJDywMK6uJAfhW+aDYQUXyxM70woRW/L807fu1d0t+qpGcO5qiBMCjCd873FzVlo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=OpCXHLn0; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="OpCXHLn0" Received: by smtp.kernel.org (Postfix) with ESMTPS id A882CC2BCFF; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784602389; bh=JpV22Go91sm1l5ZmXhWEkSXKUuaRnYmpd8p8KT1aw60=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=OpCXHLn0A3bJ+aa4MSCA7kUV7uxqiXXIHvcVUPlhOaV6rW/hWTJmuZBsf+xrNpnNu CiQAOCxPsCbbbBEJd54KRvYlI82kTX3/oZD5SyI520eoBfneSQTM5mN4NcNz5USZmp NkwxBsEvZ0D0aWlbRrJAshs+wikoaIIhHYL8noEKlxlMjD1B+qtXAWitnr5YFbaI2g S/PaJKCoiQcPaQeJiREfIzTmpuB7udzRgNo5QAK1Yq9sxcoF4uwBrEWnyQiqWvrjgv 6nL2NjUI6QVS3PRr5SGYteQRaCi74yLa8C1srWFbpu718vcNmPwf8bR8sQ9G+2NCz/ 3+JfTzZLQVWMA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 96A4CC44532; Tue, 21 Jul 2026 02:53:09 +0000 (UTC) From: Ackerley Tng via B4 Relay Date: Mon, 20 Jul 2026 17:25:16 -0700 Subject: [PATCH v3 13/13] WIP: Reproducer for out_put_pages subpool reserve leakage Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-hugetlb-alloc-failure-fixes-v3-13-7d2a169aa9ee@google.com> References: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> In-Reply-To: <20260720-hugetlb-alloc-failure-fixes-v3-0-7d2a169aa9ee@google.com> To: Muchun Song , Oscar Salvador , David Hildenbrand , Joshua Hahn , Shakeel Butt , Nhat Pham , Andrew Morton , Peter Xu , Wupeng Ma , fvdl@google.com, rientjes@google.com, jthoughton@google.com, Mike Kravetz , Johannes Weiner , Michal Hocko , Roman Gushchin Cc: vannapurve@google.com, erdemaktas@google.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, Ackerley Tng X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784602387; l=9148; i=ackerleytng@google.com; s=20260225; h=from:subject:message-id; bh=C8+RXWPxhEZ0iEc4milJz3Gk4NI1eHcFg/eirCB7vkk=; b=q6N14FCm455WC6E/wtqOt21WQ9EGTtEaTl7luRiBu/0vYOPvnC8ObL8G6bHnPziiAVISRL15X x6XxCY8nTsCCLmEJMu1UapNUs92xUAIHuzRe/7jowOLIze6pP5/0MsE X-Developer-Key: i=ackerleytng@google.com; a=ed25519; pk=sAZDYXdm6Iz8FHitpHeFlCMXwabodTm7p8/3/8xUxuU= X-Endpoint-Received: by B4 Relay for ackerleytng@google.com/20260225 with auth_id=649 X-Original-From: Ackerley Tng Reply-To: ackerleytng@google.com From: Ackerley Tng Add a highly precise C reproducer and accompanying bash execution script to exercise, validate, and stress-test the `out_put_pages` error path rollback semantics in `hugetlb_reserve_pages()`. How it works: 1. The bash script sets the system-wide HugeTLB pool to a highly constrained baseline of exactly `nr_hugepages =3D 1` and `nr_overcommit_hugepages = =3D 0`. 2. It mounts a `hugetlbfs` instance with `-o pagesize=3D2M,min_size=3D2M,si= ze=3D4M`, which causes the kernel to immediately consume the 1 available global pa= ge as the subpool's mount-time minimum size reserve (`rsv_hugepages` become= s 1). 3. The C reproducer then attempts a shared `mmap()` for `4M` (2 pages). - `hugepage_subpool_get_pages()` requests 2 pages, sees 1 reserved, and requests 1 additional global page. - `hugetlb_acct_memory()` attempts to secure that global page but immedi= ately fails with `-ENOMEM` because the pool is exhausted. - The kernel jumps to the `out_put_pages` error path, calling `hugepage_subpool_put_pages()` to symmetrically roll back the reservat= ion. 4. The script verifies that the subpool successfully retains its 1reserved = page during the failure and returns it cleanly to the global pool upon unmoun= t, proving that no underflow, double-free, or reserve leakage occurs in the `out_put_pages` boundary path. Signed-off-by: Ackerley Tng --- hugetlb_reserve_pages_out_put_pages.c | 49 +++++++++++ hugetlb_reserve_pages_out_put_pages.sh | 153 +++++++++++++++++++++++++++++= ++++ 2 files changed, 202 insertions(+) diff --git a/hugetlb_reserve_pages_out_put_pages.c b/hugetlb_reserve_pages_= out_put_pages.c new file mode 100644 index 0000000000000..9e63fc8997d57 --- /dev/null +++ b/hugetlb_reserve_pages_out_put_pages.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *file_path; + size_t size; + int fd; + void *addr; + + if (argc < 3) { + fprintf(stderr, "Usage: %s \n", + argv[0]); + return 1; + } + + file_path =3D argv[1]; + size =3D strtoull(argv[2], NULL, 0); + + fd =3D open(file_path, O_CREAT | O_RDWR, 0666); + if (fd < 0) + err(1, "open"); + + printf("Attempting to mmap %zu bytes shared on %s...\n", size, + file_path); + addr =3D mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (addr =3D=3D MAP_FAILED) { + if (errno =3D=3D ENOMEM) { + printf("mmap failed with ENOMEM as expected.\n"); + close(fd); + return 0; + } + perror("mmap failed with unexpected error"); + close(fd); + return 1; + } + + printf("ERROR: mmap SUCCEEDED unexpectedly at %p\n", addr); + munmap(addr, size); + close(fd); + return 1; +} diff --git a/hugetlb_reserve_pages_out_put_pages.sh b/hugetlb_reserve_pages= _out_put_pages.sh new file mode 100755 index 0000000000000..030e1915539b4 --- /dev/null +++ b/hugetlb_reserve_pages_out_put_pages.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +set -e + +if [ "$EUID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +SCRIPT_DIR=3D"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Detect default hugepage size to support both 2MB and 1GB pages robustly +hpz=3D$(grep -i hugepagesize /proc/meminfo | awk '{print $2}') +kb=3D$hpz +mb=3D$((kb / 1024)) +hpage_size_bytes=3D$((kb * 1024)) + +hpage_dir=3D"hugepages-${kb}kB" +SYSFS_PATH=3D"/sys/kernel/mm/hugepages/$hpage_dir" + +MNT_PATH=3D"/tmp/mnt_hugetlb_repro" +FILE_PATH=3D"$MNT_PATH/test_file" + +# Save original values for safe restoration +orig_nr=3D$(cat "$SYSFS_PATH/nr_hugepages") +orig_overcommit=3D$(cat "$SYSFS_PATH/nr_overcommit_hugepages") + +cleanup() { + echo "Cleaning up..." + rm -f "$FILE_PATH" + umount "$MNT_PATH" 2>/dev/null + rmdir "$MNT_PATH" 2>/dev/null + echo "$orig_nr" > "$SYSFS_PATH/nr_hugepages" + echo "$orig_overcommit" > "$SYSFS_PATH/nr_overcommit_hugepages" + echo "Cleanup done." +} +trap cleanup EXIT + +# Verify reproducer binary exists +if [ ! -x ./hugetlb_reserve_pages_out_put_pages ]; then + echo "reproducer binary './hugetlb_reserve_pages_out_put_pages' not fo= und or not executable." + echo "Please compile it first: gcc -static -o hugetlb_reserve_pages_ou= t_put_pages hugetlb_reserve_pages_out_put_pages.c" + exit 1 +fi + +# 1. Set global pool such that only the mount-time reservation can succeed +echo 1 > "$SYSFS_PATH/nr_hugepages" +echo 0 > "$SYSFS_PATH/nr_overcommit_hugepages" + +initial_resv=3D$(cat "$SYSFS_PATH/resv_hugepages") +echo "Initial resv_hugepages (before mount): $initial_resv" + +# 2. Mount with min_size =3D 1 page, max size =3D 2 pages +min_size_str=3D"${mb}M" +max_size_str=3D"$((mb * 2))M" +mmap_size_bytes=3D$((hpage_size_bytes * 2)) + +mkdir -p "$MNT_PATH" +echo "Mounting hugetlbfs with pagesize=3D${mb}M, min_size=3D$min_size_str,= size=3D$max_size_str..." +if ! mount -t hugetlbfs -o "pagesize=3D${mb}M,min_size=3D$min_size_str,siz= e=3D$max_size_str" none "$MNT_PATH"; then + echo "Failed to mount hugetlbfs" + exit 1 +fi + +resv_after_mount=3D$(cat "$SYSFS_PATH/resv_hugepages") +echo "resv_hugepages after mount: $resv_after_mount" +expected_after_mount=3D$((initial_resv + 1)) +if [ "$resv_after_mount" !=3D "$expected_after_mount" ]; then + echo "ERROR: resv_hugepages is not $expected_after_mount after mount (= actual: $resv_after_mount)!" + exit 1 +fi + +# Check mount stats after mount +expected_bsize=3D$hpage_size_bytes +bsize_S=3D$(stat -f -c "%S" "$MNT_PATH") +bsize_s=3D$(stat -f -c "%s" "$MNT_PATH") +echo "Mount block size after mount: $bsize_S / $bsize_s (expected: $expect= ed_bsize)" +if [ "$bsize_S" !=3D "$expected_bsize" ] && [ "$bsize_s" !=3D "$expected_b= size" ]; then + echo "ERROR: Unexpected mount block size after mount (actual S:$bsize_= S s:$bsize_s, expected: $expected_bsize)" + exit 1 +fi + +actual_stats_mount=3D$(stat -f -c "%b %f %a" "$MNT_PATH") +expected_stats_mount=3D"2 2 2" +echo "Mount stats after mount (total free avail): $actual_stats_mount (exp= ected: $expected_stats_mount)" +if [ "$actual_stats_mount" !=3D "$expected_stats_mount" ]; then + echo "ERROR: Unexpected mount stats after mount: $actual_stats_mount (= expected: $expected_stats_mount)" + exit 1 +fi + +# 3. Run the reproducer to trigger the out_put_pages failure path +echo "Running reproducer (expecting mmap failure with ENOMEM)..." +if ./hugetlb_reserve_pages_out_put_pages "$FILE_PATH" "$mmap_size_bytes"; = then + echo "Reproducer finished successfully." + resv_after_mmap=3D$(cat "$SYSFS_PATH/resv_hugepages") + echo "resv_hugepages after failed mmap: $resv_after_mmap" + expected_after_mmap=3D$expected_after_mount + if [ "$resv_after_mmap" =3D "$expected_after_mmap" ]; then + echo "RESULT: out_put_pages EXERCISED (resv_hugepages preserved at= $expected_after_mmap as expected)" + + # Check mount stats + expected_bsize=3D$hpage_size_bytes + bsize_S=3D$(stat -f -c "%S" "$MNT_PATH") + bsize_s=3D$(stat -f -c "%s" "$MNT_PATH") + echo "Mount block size: $bsize_S / $bsize_s (expected: $expected_b= size)" + if [ "$bsize_S" !=3D "$expected_bsize" ] && [ "$bsize_s" !=3D "$ex= pected_bsize" ]; then + echo "ERROR: Unexpected mount block size (actual S:$bsize_S s:= $bsize_s, expected: $expected_bsize)" + exit 1 + fi + + actual_stats=3D$(stat -f -c "%b %f %a" "$MNT_PATH") + expected_stats=3D"2 2 2" + echo "Mount stats (total free avail): $actual_stats (expected: $ex= pected_stats)" + if [ "$actual_stats" !=3D "$expected_stats" ]; then + echo "RESULT: Unexpected mount stats after failed mmap (FAIL)" + exit 1 + else + echo "RESULT: Mount stats restored to $expected_stats as expec= ted (PASS)" + fi + else + echo "RESULT: Unexpected resv_hugepages value: $resv_after_mmap (e= xpected: $expected_after_mmap)" + exit 1 + fi +else + echo "FAIL: Reproducer returned non-zero (mmap didn't fail with ENOMEM= )" + exit 1 +fi + +# 4. Disable trap and do manual cleanup to check for final unmount underfl= ow +trap - EXIT + +echo "Unmounting..." +umount "$MNT_PATH" +rmdir "$MNT_PATH" + +final_resv=3D$(cat "$SYSFS_PATH/resv_hugepages") +echo "Final resv_hugepages (after unmount): $final_resv" + +# Restore original values +echo "Restoring original hugepage settings..." +echo "$orig_nr" > "$SYSFS_PATH/nr_hugepages" +echo "$orig_overcommit" > "$SYSFS_PATH/nr_overcommit_hugepages" + +if [ "$final_resv" =3D "$initial_resv" ]; then + echo "RESULT: State restored to $initial_resv (or cleaned up if fixed)" + echo "ALL DONE." + exit 0 +else + echo "RESULT: Underflow/Leak/Incorrect state detected! (final_resv =3D= $final_resv, expected =3D $initial_resv)" + echo "ALL DONE." + exit 1 +fi --=20 2.55.0.229.g6434b31f56-goog