Re: [PATCH RFC 10/15] mm/mglru: make folio lru referenced times count a generic API

Lian Wang <[email protected]> Tue, 4 Aug 2026 15:49:27 +0800
Newsgroups gmane.linux.kernel,gmane.linux.kernel.mm,gmane.linux.kernel.cgroups
Message-ID <[email protected]>
From: "Lian Wang (ProcessMission)" <[email protected]>

Hi Kairui,

I am trying to understand the intended semantics of making the referenced
count a generic API, and would appreciate your guidance. My understanding is
that, with the new encoding, raw PG_referenced and PG_workingset users no
longer see simple boolean states for every reference count.

A few examples I found:

- damon_pa_pageout() still calls folio_clear_referenced(). With refs == 2 it
  clears nothing, and with refs == 3 it leaves refs == 2. Thus DAMOS pageout
  may retain workingset history instead of clearing the MGLRU reference state.
- EROFS zdata uses PageWorkingset() for PSI accounting. With the new encoding,
  the PG_workingset bit is clear for refs == 4 or 5 even though the folio is
  hot.
- /proc/kpageflags exports PG_referenced directly, so KPF_REFERENCED appears
  to become the parity of refs rather than a boolean referenced state.

Are these semantics intended? The DAMON case in particular looks similar to
the madvise conversion in patch 15. If my understanding is correct, would the
remaining raw-bit users need a tree-wide audit together with the API
conversion?

If I have misunderstood how these users are expected to behave, please feel
free to ignore these concerns.

Thanks,
Lian

On Tue, 04 Aug 2026 03:47:06 +0800 Kairui Song via B4 Relay <[email protected]> wrote:

> From: Kairui Song <[email protected]>
> 
> To prepare for unifying the API for checking folio referenced status,
> expose the referenced times counting as a generic API. For MGLRU this
> helps to adapt other subsystem based on the referenced times counting,
> for non-MGLRU this is still bitwise compatible and there won't be
> major behavior change.
> 
> Signed-off-by: Kairui Song <[email protected]>
> ---
>  include/linux/mm_inline.h | 233 ++++++++++++++++++++++++++++++----------------
>  mm/migrate.c              |   2 -
>  2 files changed, 155 insertions(+), 80 deletions(-)
> 
> diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
> index 944baa91bf18..a13b7d3c033a 100644
> --- a/include/linux/mm_inline.h
> +++ b/include/linux/mm_inline.h
> @@ -94,6 +94,161 @@ static __always_inline enum lru_list folio_lru_list(const struct folio *folio)
>  	return lru;
>  }
>  
> +/**
> + * lru_refs_from_flags - Return LRU referenced / access count from folio flags.
> + * @flags: folio flags
> + */
> +static inline int lru_refs_from_flags(unsigned long flags)
> +{
> +	int refs;
> +
> +	/*
> +	 * Return the total number of accesses. Also see the comment on
> +	 * LRU_REFS_FLAGS.
> +	 */
> +	refs = (flags & BIT(PG_referenced)) ? BIT(0) : 0;
> +	refs += (flags & BIT(PG_workingset)) ? BIT(1) : 0;
> +	refs += ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) << 2;
> +	return refs;
> +}
> +
> +/**
> + * lru_refs_set_flags - Set the LRU referenced / access count to specified folio flags.
> + * @flags: pointer to the folio flags
> + * @refs: referenced / access count number, between 0 and LRU_REFS_MAX, inclusive.
> + */
> +static inline void lru_refs_set_flags(unsigned long *flags, unsigned int refs)
> +{
> +	VM_WARN_ON_ONCE(refs > LRU_REFS_MAX);
> +	BUILD_BUG_ON((LRU_REFS_MAX >> 2) > (BIT(LRU_REFS_WIDTH) - 1));
> +	*flags &= ~LRU_REFS_FLAGS;
> +	if (refs & BIT(0))
> +		*flags |= BIT(PG_referenced);
> +	if (refs & BIT(1))
> +		*flags |= BIT(PG_workingset);
> +	*flags |= (((unsigned long)refs) >> 2) << LRU_REFS_PGOFF;
> +}
> +
> +static inline int folio_lru_refs(const struct folio *folio)
> +{
> +	return lru_refs_from_flags(READ_ONCE(*const_folio_flags(folio, 0)));
> +}
> +
> +static inline void folio_set_lru_refs(struct folio *folio, unsigned int refs)
> +{
> +	unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
> +
> +	do {
> +		new_flags = old_flags;
> +		lru_refs_set_flags(&new_flags, refs);
> +	} while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
> +}
> +
> +int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec);
> +
> +/**
> + * folio_is_referenced - Tell if a folio was accessed before.
> + * @folio: the folio.
> + *
> + * This helper currently only works as intended for MGLRU, as it checks
> + * all LRU_REFS_FLAGS. It might be fine for non-MGLRU to replace
> + * folio_test_referenced in some cases but the user should be careful.
> + *
> + * Returns: true if the folio's LRU referenced / accessed count > 0.
> + */
> +static inline bool folio_is_referenced(const struct folio *folio)
> +{
> +	return folio_lru_refs(folio) >= LRU_REFS_REFERENCED;
> +}
> +
> +/**
> + * folio_mark_referenced - Mark a folio as referenced.
> + * @folio: the folio.
> + *
> + * Ensures the folio's LRU referenced count is at least
> + * LRU_REFS_REFERENCED. Won't do anything if the count is already larger
> + * than that. This helper currently only works as intended for MGLRU.
> + * Not a drop-in replacement, but should be fine for non-MGLRU to replace
> + * folio_set_referenced with this after audit.
> + */
> +static inline void folio_mark_referenced(struct folio *folio)
> +{
> +	unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
> +
> +	do {
> +		new_flags = old_flags;
> +		if (lru_refs_from_flags(new_flags) >= LRU_REFS_REFERENCED)
> +			return;
> +		lru_refs_set_flags(&new_flags, LRU_REFS_REFERENCED);
> +	} while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
> +}
> +
> +/**
> + * folio_mark_referenced_by_bit - Mark a folio as referenced by bit.
> + * @folio: the folio.
> + *
> + * non-MGLRU may want to make use of the lowest LRU referenced count bit
> + * explicitly as a referenced mark.
> + */
> +static inline void folio_mark_referenced_by_bit(struct folio *folio)
> +{
> +	set_bit(PG_referenced, folio_flags(folio, 0));
> +}
> +
> +/**
> + * folio_clear_referenced_by_bit - Clear the referenced bit of a folio.
> + * @folio: the folio.
> + */
> +static inline void folio_clear_referenced_by_bit(struct folio *folio)
> +{
> +	clear_bit(PG_referenced, folio_flags(folio, 0));
> +}
> +
> +/**
> + * folio_test_clear_referenced_by_bit - Test and clear the referenced bit
> + * @folio: the folio.
> + */
> +static inline bool folio_test_clear_referenced_by_bit(struct folio *folio)
> +{
> +	return test_and_clear_bit(PG_referenced, folio_flags(folio, 0));
> +}
> +
> +/**
> + * folio_is_referenced_by_bit - Test if the referenced bit of a folio is set.
> + * @folio: the folio.
> + */
> +static inline bool folio_is_referenced_by_bit(const struct folio *folio)
> +{
> +	return test_bit(PG_referenced, const_folio_flags(folio, 0));
> +}
> +
> +/**
> + * folio_is_workingset - Tell if a folio is part of the workingset.
> + * @folio: the folio.
> + *
> + * Can be used to replace folio_test_workingset safely. For MGLRU the LRU
> + * referenced count tells if a folio is a workingset as intended. For non-MGLRU,
> + * the check below only holds true if the PG_workingset bit is set.
> + */
> +static inline bool folio_is_workingset(const struct folio *folio)
> +{
> +	return folio_lru_refs(folio) >= LRU_REFS_WORKINGSET;
> +}
> +
> +/**
> + * folio_mark_workingset_by_bit - Set the workingset bit of a folio.
> + * @folio: the folio.
> + */
> +static inline void folio_mark_workingset_by_bit(struct folio *folio)
> +{
> +	set_bit(PG_workingset, folio_flags(folio, 0));
> +}
> +
> +static inline void folio_migrate_refs(struct folio *new, const struct folio *old)
> +{
> +	folio_set_lru_refs(new, folio_lru_refs(old));
> +}
> +
>  #ifdef CONFIG_LRU_GEN
>  
>  static inline bool lru_gen_switching(void)
> @@ -171,58 +326,6 @@ static inline void lru_gen_set_flags(unsigned long *flags, int gen)
>  	*flags |= (gen + 1UL) << LRU_GEN_PGOFF;
>  }
>  
> -/**
> - * lru_refs_from_flags - Return LRU referenced / access count from folio flags.
> - * @flags: folio flags
> - */
> -static inline int lru_refs_from_flags(unsigned long flags)
> -{
> -	int refs;
> -
> -	/*
> -	 * Return the total number of accesses. Also see the comment on
> -	 * LRU_REFS_FLAGS.
> -	 */
> -	refs = (flags & BIT(PG_referenced)) ? BIT(0) : 0;
> -	refs += (flags & BIT(PG_workingset)) ? BIT(1) : 0;
> -	refs += ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) << 2;
> -	return refs;
> -}
> -
> -/**
> - * lru_refs_set_flags - Set the LRU referenced / access count to specified folio flags.
> - * @flags: pointer to the folio flags
> - * @refs: referenced / access count number, between 0 and LRU_REFS_MAX, inclusive.
> - */
> -static inline void lru_refs_set_flags(unsigned long *flags, unsigned int refs)
> -{
> -	VM_WARN_ON_ONCE(refs > LRU_REFS_MAX);
> -	BUILD_BUG_ON((LRU_REFS_MAX >> 2) > (BIT(LRU_REFS_WIDTH) - 1));
> -	*flags &= ~LRU_REFS_FLAGS;
> -	if (refs & BIT(0))
> -		*flags |= BIT(PG_referenced);
> -	if (refs & BIT(1))
> -		*flags |= BIT(PG_workingset);
> -	*flags |= (((unsigned long)refs) >> 2) << LRU_REFS_PGOFF;
> -}
> -
> -static inline int folio_lru_refs(const struct folio *folio)
> -{
> -	return lru_refs_from_flags(READ_ONCE(*const_folio_flags(folio, 0)));
> -}
> -
> -static inline void folio_set_lru_refs(struct folio *folio, unsigned int refs)
> -{
> -	unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
> -
> -	do {
> -		new_flags = old_flags;
> -		lru_refs_set_flags(&new_flags, refs);
> -	} while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
> -}
> -
> -int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec);
> -
>  static inline int folio_lru_gen(const struct folio *folio)
>  {
>  	return lru_gen_from_flags(READ_ONCE(*const_folio_flags(folio, 0)));
> @@ -369,11 +472,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
>  	return true;
>  }
>  
> -static inline void folio_migrate_refs(struct folio *new, const struct folio *old)
> -{
> -	folio_set_lru_refs(new, folio_lru_refs(old));
> -}
> -
>  #else /* !CONFIG_LRU_GEN */
>  
>  static inline bool lru_gen_enabled(void)
> @@ -401,27 +499,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
>  	return false;
>  }
>  
> -static inline int folio_lru_refs(const struct folio *folio)
> -{
> -	return 0;
> -}
> -
> -static inline void folio_set_lru_refs(struct folio *folio, unsigned int refs)
> -{
> -}
> -
> -static inline int folio_inc_lru_refs(struct folio *folio, bool promote, bool is_exec)
> -{
> -	return 0;
> -}
> -
> -static inline void folio_migrate_refs(struct folio *new, const struct folio *old)
> -{
> -	if (folio_test_referenced(old))
> -		folio_set_referenced(new);
> -	if (folio_test_workingset(old))
> -		folio_set_workingset(new);
> -}
>  #endif /* CONFIG_LRU_GEN */
>  
>  static __always_inline
> diff --git a/mm/migrate.c b/mm/migrate.c
> index c737d0682fa4..806f1e913a38 100644
> --- a/mm/migrate.c
> +++ b/mm/migrate.c
> @@ -786,8 +786,6 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)
>  		folio_set_active(newfolio);
>  	} else if (folio_test_clear_unevictable(folio))
>  		folio_set_unevictable(newfolio);
> -	if (folio_test_workingset(folio))
> -		folio_set_workingset(newfolio);
>  	if (folio_test_checked(folio))
>  		folio_set_checked(newfolio);
>  	/*
> 
> -- 
> 2.55.0
> 
> 
> 

Sent using hkml (https://github.com/sjp38/hackermail)