[RESEND v7 21/29] mm/madvise: keep PMD swap entries whole for MADV_GUARD_INSTALL/REMOVE

From: Usama Arif

Date: Mon Sep 14 2026 - 09:06:44 EST


Both callbacks test bare pmd_trans_huge(), which is false for a PMD swap
entry, so they return without consuming the PMD and walk_pmd_range() splits
it - only for the PTE callbacks to find the range populated and abort, or
to find no guard markers and do nothing.

Both already short-circuit at PMD level for a present THP. Do the same for
a swapped-out one: it is just as populated, and just as free of guard
markers.

Signed-off-by: Usama Arif <usama.arif@xxxxxxxxx>
---
mm/madvise.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/mm/madvise.c b/mm/madvise.c
index d3b5ed7e3cffb..64019668d69fb 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -1269,8 +1269,12 @@ static int guard_install_pmd_entry(pmd_t *pmd, unsigned long addr,
{
pmd_t pmdval = pmdp_get(pmd);

- /* If huge return >0 so we abort the operation + zap. */
- return pmd_trans_huge(pmdval);
+ /*
+ * If huge return >0 so we abort the operation + zap. A PMD swap entry
+ * is a swapped-out THP: also populated, and splitting it here would
+ * only demote it before the zap.
+ */
+ return pmd_trans_huge(pmdval) || pmd_is_swap_entry(pmdval);
}

static int guard_install_pte_entry(pte_t *pte, unsigned long addr,
@@ -1409,8 +1413,12 @@ static int guard_remove_pmd_entry(pmd_t *pmd, unsigned long addr,
{
pmd_t pmdval = pmdp_get(pmd);

- /* If huge, cannot have guard pages present, so no-op - skip. */
- if (pmd_trans_huge(pmdval))
+ /*
+ * If huge, cannot have guard pages present, so no-op - skip. The same
+ * is true of a PMD swap entry, which must not be split just to discover
+ * there is nothing to remove.
+ */
+ if (pmd_trans_huge(pmdval) || pmd_is_swap_entry(pmdval))
walk->action = ACTION_CONTINUE;

return 0;
--
2.53.0-Meta