Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Baoquan He" <baoquan.he@linux.dev>, linux-mm@kvack.org
Cc: akpm@linux-foundation.org, david@kernel.org, kasong@tencent.com,
	baohua@kernel.org, baolin.wang@linux.alibaba.com,
	qi.zheng@linux.dev, shakeel.butt@linux.dev,
	axelrasmussen@google.com, yuanchu@google.com, weixugc@google.com,
	"Baoquan He" <hebaoquan@kylinos.cn>,
	"Kairui Song" <ryncsn@gmail.com>
Subject: Re: [PATCH v3 3/4] mm/mglru: skip cold PUD subtrees during aging
Date: Sat, 14 Nov 2026 03:42:21 +0000	[thread overview]
Message-ID: <sashiko-outbox-214125@kernel.org> (raw)
In-Reply-To: <20261113084953.412919-4-baoquan.he@linux.dev>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] mm/mglru: walk_pmd_range() marks PUD young on non-leaf PMD
  accessed bit
- [Medium] mm/mglru: walk_pmd_range() marks PUD young for THP before
  memcg and notifier checks
- [Medium] mm/mglru: PUDs with PMD-mapped THPs are suppressed
  indefinitely once skipped
--

commit 31572808d36982038b4b945e98794001c99a5d21
Author: Baoquan He <hebaoquan@kylinos.cn>

mm/mglru: skip cold PUD subtrees during aging

This commit introduces a PUD-level Bloom filter keyed by (mm, PUD-aligned
address) to skip PUD subtrees that had no young pages in the previous
generation during MGLRU aging walks, and updates it from the rmap feedback
path in lru_gen_look_around().

> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index a0332586b3d83..79d533ebfd108 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c

[ ... ]

> @@ -3730,10 +3732,11 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area
>  	*first = -1;
>  }
>  
> -static void walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
> +static bool walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
>  			   struct mm_walk *args)
>  {
>  	int i;
> +	bool young = false;
>  	pmd_t *pmd;
>  	unsigned long next;
>  	unsigned long addr;
> @@ -3770,8 +3773,10 @@ static void walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
>  
>  			walk->mm_stats[MM_LEAF_TOTAL]++;
>  
> -			if (pfn != -1)
> +			if (pfn != -1) {
>  				walk_pmd_range_locked(pud, addr, vma, args, bitmap, &first);
> +				young = true;
> +			}
>  			continue;
>  		}

[Severity: Medium]
Does setting young = true as soon as get_pmd_pfn() != -1 mark the PUD young
before checking memcg ownership or MMU notifiers?

get_pmd_pfn() only checks that the PFN is on pgdat and that pmd_young() or
mm_has_notifiers() is true, while the memcg check in get_pfn_folio() and
the notifier young check in pmdp_test_and_clear_young_notify() are deferred
to walk_pmd_range_locked().  A PUD with PMD-mapped THPs belonging to
another memcg on the same node, or cold THPs in an mm with MMU notifiers,
will therefore be recorded in BLOOM_FILTER_PUD on every generation and
never skipped.

> @@ -3781,6 +3786,7 @@ static void walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
>  				continue;
>  
>  			walk_pmd_range_locked(pud, addr, vma, args, bitmap, &first);
> +			young = true;
>  		}
>  
>  		if (!walk->force_scan &&
> @@ -3793,6 +3799,7 @@ static void walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
>  			continue;
>  
>  		walk->mm_stats[MM_NONLEAF_ADDED]++;
> +		young = true;
>  
>  		/* carry over to the next generation */
>  		update_bloom_filter(mm_state, BLOOM_FILTER_PMD, walk->seq + 1, pmd + i);

[Severity: Medium]
Should young = true be omitted from the non-leaf pmd_young(val) block?

When should_clear_pmd_young() is true, setting young = true there happens
before checking BLOOM_FILTER_PMD or calling walk_pte_range().  Because CPU
page table walks set the accessed bit on non-leaf PMDs regardless of which
node or memcg owns the underlying PTEs, any accessed non-leaf PMD will
cause walk_pmd_range() to return true and carry the PUD into
BLOOM_FILTER_PUD even when every PMD in the subtree fails the PMD Bloom
filter or has no folios in walk->lruvec.  The young = true after
walk_pte_range() already handles non-leaf PMDs that pass suitable_to_scan().

> @@ -3802,6 +3809,8 @@ static void walk_pmd_range(pud_t *pud, unsigned long start, unsigned long end,
>  
>  	if (i < PTRS_PER_PMD && get_next_vma(PUD_MASK, PMD_SIZE, args, &start, &end))
>  		goto restart;
> +
> +	return young;
>  }
>  
>  static int walk_pud_range(p4d_t *p4d, unsigned long start, unsigned long end,
> @@ -3812,6 +3821,7 @@ static int walk_pud_range(p4d_t *p4d, unsigned long start, unsigned long end,
>  	unsigned long addr;
>  	unsigned long next;
>  	struct lru_gen_mm_walk *walk = args->private;
> +	struct lru_gen_mm_state *mm_state = get_mm_state(walk->lruvec);
>  
>  	VM_WARN_ON_ONCE(p4d_leaf(*p4d));
>  
> @@ -3819,13 +3829,29 @@ static int walk_pud_range(p4d_t *p4d, unsigned long start, unsigned long end,
>  restart:
>  	for (i = pud_index(start), addr = start; addr != end; i++, addr = next) {
>  		pud_t val = pudp_get(pud + i);
> +		void *pud_key;
>  
>  		next = pud_addr_end(addr, end);
>  
>  		if (!pud_present(val) || WARN_ON_ONCE(pud_leaf(val)))
>  			continue;
>  
> -		walk_pmd_range(pud + i, addr, next, args);
> +		/*
> +		 * Use (mm, PUD-aligned address) as the filter key instead of
> +		 * the page table pointer. It is stable across the aging walk
> +		 * and the rmap feedback path, and avoids walking the page
> +		 * tables in lru_gen_look_around().
> +		 */
> +		pud_key = (void *)((unsigned long)args->mm ^ (addr & PUD_MASK));
> +
> +		/* Skip a subtree that had nothing young for this lruvec last gen */
> +		if (!walk->force_scan &&
> +		    !test_bloom_filter(mm_state, BLOOM_FILTER_PUD, walk->seq, pud_key))
> +			continue;
> +
> +		if (walk_pmd_range(pud + i, addr, next, args))
> +			update_bloom_filter(mm_state, BLOOM_FILTER_PUD, walk->seq + 1,
> +					    pud_key);

[Severity: Medium]
Once a PUD is absent from BLOOM_FILTER_PUD, can PMD-mapped THPs inside it
ever be scanned by page-table aging again without force_scan?

Previously, BLOOM_FILTER_PMD was checked after pmd_trans_huge() in
walk_pmd_range(), so PMD-mapped THPs were never gated by a Bloom filter.
Gating walk_pmd_range() on BLOOM_FILTER_PUD skips the entire PUD when
pud_key is not present, and iterate_mm_list() clears the next generation's
filter via reset_bloom_filter(mm_state, BLOOM_FILTER_PUD, walk->seq + 1).
The only rmap feedback that populates BLOOM_FILTER_PUD is
lru_gen_look_around(), which folio_referenced_one() only calls when
pvmw.pte is non-NULL:

mm/rmap.c:folio_referenced_one() {
    ...
		if (lru_gen_enabled() && !lru_gen_switching() && pvmw.pte &&
		    pte_present(pteval)) {
			if (lru_gen_look_around(&pvmw, nr))
				referenced++;
		} else if (pvmw.pte) {
    ...
		} else if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) {
			if (pmdp_clear_flush_young_notify(vma, address,
						pvmw.pmd))
				referenced++;
    ...
}

For PMD-mapped THPs (pvmw.pte == NULL), rmap eviction checks never update
BLOOM_FILTER_PUD, so any PUD that goes cold for one generation or receives
newly faulted or collapsed THPs after the initial force_scan will be
skipped by aging indefinitely.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113084953.412919-1-baoquan.he@linux.dev?part=3


  reply	other threads:[~2026-11-14  3:42 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-13  8:49 [PATCH v3 0/4] skip empty PUD subtrees during aging with a PUD-level Bloom filter Baoquan He
2026-11-13  8:49 ` [PATCH v3 1/4] mm/mglru: add MM_WALK_EMPTY stats and tracepoint Baoquan He
2026-11-13  8:53   ` Baoquan He
2026-11-14  3:42   ` sashiko-bot
2026-11-13  8:49 ` [PATCH v3 2/4] mm/mglru: add PUD-level Bloom filter state and generic helpers Baoquan He
2026-11-13  8:49 ` [PATCH v3 3/4] mm/mglru: skip cold PUD subtrees during aging Baoquan He
2026-11-14  3:42   ` sashiko-bot [this message]
2026-11-13  8:49 ` [PATCH v3 4/4] mm/mglru: count PUD subtrees skipped by the PUD-level filter Baoquan He

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-214125@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=axelrasmussen@google.com \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baoquan.he@linux.dev \
    --cc=david@kernel.org \
    --cc=hebaoquan@kylinos.cn \
    --cc=kasong@tencent.com \
    --cc=linux-mm@kvack.org \
    --cc=qi.zheng@linux.dev \
    --cc=ryncsn@gmail.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=weixugc@google.com \
    --cc=yuanchu@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox