| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/03 21:26 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG",
"TRANSPARENT_HUGEPAGE",
"SWAP"
],
"FocusSymbols": [
"folio_inc_lru_refs",
"lru_gen_look_around",
"workingset_refault",
"workingset_eviction",
"swap_read_folio",
"__split_huge_pmd",
"madvise_collapse",
"do_madvise"
],
"Reasoning": "This patch introduces a new LRU reference tracking mechanism for both MGLRU and non-MGLRU, replacing PG_referenced and PG_workingset with a multi-bit reference count. It modifies core memory management paths including page fault handling, page cache read-ahead, swap, THP splitting, and madvise behaviors. The changes are highly functional and affect the core page reclaim and aging logic, making it worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/03 21:26 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit e6f118540b92cb2dd2b6d3c07a4d82301148a6cf\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 3 21:26:32 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c\nindex ffb6b52863a78..e756403e8bd5d 100644\n--- a/fs/btrfs/compression.c\n+++ b/fs/btrfs/compression.c\n@@ -21,6 +21,7 @@\n #include \u003clinux/sched/mm.h\u003e\n #include \u003clinux/log2.h\u003e\n #include \u003clinux/shrinker.h\u003e\n+#include \u003clinux/mm_inline.h\u003e\n #include \"misc.h\"\n #include \"ctree.h\"\n #include \"fs.h\"\n@@ -448,7 +449,7 @@ static noinline int add_ra_bio_folios(struct inode *inode, u64 compressed_end,\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (!*memstall \u0026\u0026 folio_test_workingset(folio)) {\n+\t\tif (!*memstall \u0026\u0026 folio_is_workingset(folio)) {\n \t\t\tpsi_memstall_enter(pflags);\n \t\t\t*memstall = 1;\n \t\t}\ndiff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c\nindex 817e3e0f91943..c5c96c5232918 100644\n--- a/fs/proc/task_mmu.c\n+++ b/fs/proc/task_mmu.c\n@@ -944,6 +944,22 @@ static void smaps_page_accumulate(struct mem_size_stats *mss,\n \t}\n }\n \n+static bool smap_check_folio_referenced(struct folio *folio)\n+{\n+\tif (lru_gen_enabled())\n+\t\treturn folio_is_referenced(folio);\n+\telse\n+\t\treturn folio_is_referenced_by_bit(folio);\n+}\n+\n+static void smap_clear_folio_referenced(struct folio *folio)\n+{\n+\tif (lru_gen_enabled())\n+\t\tfolio_set_lru_refs(folio, 0);\n+\telse\n+\t\tfolio_clear_referenced_by_bit(folio);\n+}\n+\n static void smaps_account(struct mem_size_stats *mss, struct page *page,\n \t\tbool compound, bool young, bool dirty, bool locked,\n \t\tbool present)\n@@ -970,7 +986,7 @@ static void smaps_account(struct mem_size_stats *mss, struct page *page,\n \n \tmss-\u003eresident += size;\n \t/* Accumulate the size in pages that have been accessed. */\n-\tif (young || folio_test_young(folio) || folio_test_referenced(folio))\n+\tif (young || folio_test_young(folio) || smap_check_folio_referenced(folio))\n \t\tmss-\u003ereferenced += size;\n \n \t/*\n@@ -1791,7 +1807,7 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,\n \t\t/* Clear accessed and referenced bits. */\n \t\tpmdp_test_and_clear_young(vma, addr, pmd);\n \t\tfolio_test_clear_young(folio);\n-\t\tfolio_clear_referenced(folio);\n+\t\tsmap_clear_folio_referenced(folio);\n out:\n \t\tspin_unlock(ptl);\n \t\treturn 0;\n@@ -1820,7 +1836,7 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,\n \t\t/* Clear accessed and referenced bits. */\n \t\tptep_test_and_clear_young(vma, addr, pte);\n \t\tfolio_test_clear_young(folio);\n-\t\tfolio_clear_referenced(folio);\n+\t\tsmap_clear_folio_referenced(folio);\n \t}\n \tpte_unmap_unlock(pte - 1, ptl);\n \tcond_resched();\ndiff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h\nindex e78bc98ab229b..ea0111392b9bc 100644\n--- a/include/linux/memcontrol.h\n+++ b/include/linux/memcontrol.h\n@@ -113,7 +113,7 @@ struct mem_cgroup_per_node {\n \t/* Fields which get updated often at the end. */\n \tstruct lruvec\t\tlruvec;\n \tCACHELINE_PADDING(_pad2_);\n-\tunsigned long\t\tlru_zone_size[MAX_NR_ZONES][NR_LRU_LISTS];\n+\tatomic_long_t\t\tlru_zone_size[MAX_NR_ZONES][NR_LRU_LISTS];\n \tstruct mem_cgroup_reclaim_iter\titer;\n \n \t/*\n@@ -897,10 +897,15 @@ static inline\n unsigned long mem_cgroup_get_zone_lru_size(struct lruvec *lruvec,\n \t\tenum lru_list lru, int zone_idx)\n {\n+\tlong val;\n \tstruct mem_cgroup_per_node *mz;\n \n \tmz = container_of(lruvec, struct mem_cgroup_per_node, lruvec);\n-\treturn READ_ONCE(mz-\u003elru_zone_size[zone_idx][lru]);\n+\tval = atomic_long_read(\u0026mz-\u003elru_zone_size[zone_idx][lru]);\n+\tif (val \u003c 0)\n+\t\treturn 0;\n+\n+\treturn val;\n }\n \n void __mem_cgroup_handle_over_high(gfp_t gfp_mask);\n@@ -1501,6 +1506,44 @@ static inline void lruvec_lock_irq(struct lruvec *lruvec)\n \tspin_lock_irq(\u0026lruvec-\u003elru_lock);\n }\n \n+/**\n+ * folio_lruvec_live_get - get a live lruvec for a folio under RCU\n+ * @folio: the folio\n+ *\n+ * Computes @folio's lruvec and walks up to the nearest live ancestor\n+ * if the folio's memcg is dying. Must be paired with\n+ * folio_lruvec_live_put().\n+ *\n+ * Return: the live lruvec, with rcu_read_lock held.\n+ */\n+static inline struct lruvec *folio_lruvec_live_get(struct folio *folio)\n+{\n+#ifdef CONFIG_MEMCG\n+\tstruct lruvec *lruvec;\n+\tstruct pglist_data *pgdat;\n+\tstruct mem_cgroup *memcg;\n+\n+\trcu_read_lock();\n+\tlruvec = folio_lruvec(folio);\n+\tpgdat = lruvec_pgdat(lruvec);\n+\tmemcg = lruvec_memcg(lruvec);\n+\twhile (unlikely(memcg \u0026\u0026 css_is_dying(\u0026memcg-\u003ecss))) {\n+\t\tmemcg = parent_mem_cgroup(memcg);\n+\t\tlruvec = mem_cgroup_lruvec(memcg, pgdat);\n+\t}\n+\treturn lruvec;\n+#else\n+\treturn folio_lruvec(folio);\n+#endif\n+}\n+\n+static inline void folio_lruvec_live_put(struct lruvec *lruvec)\n+{\n+#ifdef CONFIG_MEMCG\n+\trcu_read_unlock();\n+#endif\n+}\n+\n static inline struct lruvec *lruvec_live_lock_irq(struct lruvec *lruvec)\n {\n #ifdef CONFIG_MEMCG\ndiff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h\nindex 621c8653d8f7e..a13b7d3c033a8 100644\n--- a/include/linux/mm_inline.h\n+++ b/include/linux/mm_inline.h\n@@ -36,11 +36,10 @@ static __always_inline void __update_lru_size(struct lruvec *lruvec,\n {\n \tstruct pglist_data *pgdat = lruvec_pgdat(lruvec);\n \n-\tlockdep_assert_held(\u0026lruvec-\u003elru_lock);\n \tWARN_ON_ONCE(nr_pages != (int)nr_pages);\n \n \tmod_lruvec_state(lruvec, NR_LRU_BASE + lru, nr_pages);\n-\t__mod_zone_page_state(\u0026pgdat-\u003enode_zones[zid],\n+\tmod_zone_page_state(\u0026pgdat-\u003enode_zones[zid],\n \t\t\t\tNR_ZONE_LRU_BASE + lru, nr_pages);\n }\n \n@@ -95,6 +94,161 @@ static __always_inline enum lru_list folio_lru_list(const struct folio *folio)\n \treturn lru;\n }\n \n+/**\n+ * lru_refs_from_flags - Return LRU referenced / access count from folio flags.\n+ * @flags: folio flags\n+ */\n+static inline int lru_refs_from_flags(unsigned long flags)\n+{\n+\tint refs;\n+\n+\t/*\n+\t * Return the total number of accesses. Also see the comment on\n+\t * LRU_REFS_FLAGS.\n+\t */\n+\trefs = (flags \u0026 BIT(PG_referenced)) ? BIT(0) : 0;\n+\trefs += (flags \u0026 BIT(PG_workingset)) ? BIT(1) : 0;\n+\trefs += ((flags \u0026 LRU_REFS_MASK) \u003e\u003e LRU_REFS_PGOFF) \u003c\u003c 2;\n+\treturn refs;\n+}\n+\n+/**\n+ * lru_refs_set_flags - Set the LRU referenced / access count to specified folio flags.\n+ * @flags: pointer to the folio flags\n+ * @refs: referenced / access count number, between 0 and LRU_REFS_MAX, inclusive.\n+ */\n+static inline void lru_refs_set_flags(unsigned long *flags, unsigned int refs)\n+{\n+\tVM_WARN_ON_ONCE(refs \u003e LRU_REFS_MAX);\n+\tBUILD_BUG_ON((LRU_REFS_MAX \u003e\u003e 2) \u003e (BIT(LRU_REFS_WIDTH) - 1));\n+\t*flags \u0026= ~LRU_REFS_FLAGS;\n+\tif (refs \u0026 BIT(0))\n+\t\t*flags |= BIT(PG_referenced);\n+\tif (refs \u0026 BIT(1))\n+\t\t*flags |= BIT(PG_workingset);\n+\t*flags |= (((unsigned long)refs) \u003e\u003e 2) \u003c\u003c LRU_REFS_PGOFF;\n+}\n+\n+static inline int folio_lru_refs(const struct folio *folio)\n+{\n+\treturn lru_refs_from_flags(READ_ONCE(*const_folio_flags(folio, 0)));\n+}\n+\n+static inline void folio_set_lru_refs(struct folio *folio, unsigned int refs)\n+{\n+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n+\n+\tdo {\n+\t\tnew_flags = old_flags;\n+\t\tlru_refs_set_flags(\u0026new_flags, refs);\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n+}\n+\n+int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec);\n+\n+/**\n+ * folio_is_referenced - Tell if a folio was accessed before.\n+ * @folio: the folio.\n+ *\n+ * This helper currently only works as intended for MGLRU, as it checks\n+ * all LRU_REFS_FLAGS. It might be fine for non-MGLRU to replace\n+ * folio_test_referenced in some cases but the user should be careful.\n+ *\n+ * Returns: true if the folio's LRU referenced / accessed count \u003e 0.\n+ */\n+static inline bool folio_is_referenced(const struct folio *folio)\n+{\n+\treturn folio_lru_refs(folio) \u003e= LRU_REFS_REFERENCED;\n+}\n+\n+/**\n+ * folio_mark_referenced - Mark a folio as referenced.\n+ * @folio: the folio.\n+ *\n+ * Ensures the folio's LRU referenced count is at least\n+ * LRU_REFS_REFERENCED. Won't do anything if the count is already larger\n+ * than that. This helper currently only works as intended for MGLRU.\n+ * Not a drop-in replacement, but should be fine for non-MGLRU to replace\n+ * folio_set_referenced with this after audit.\n+ */\n+static inline void folio_mark_referenced(struct folio *folio)\n+{\n+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n+\n+\tdo {\n+\t\tnew_flags = old_flags;\n+\t\tif (lru_refs_from_flags(new_flags) \u003e= LRU_REFS_REFERENCED)\n+\t\t\treturn;\n+\t\tlru_refs_set_flags(\u0026new_flags, LRU_REFS_REFERENCED);\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n+}\n+\n+/**\n+ * folio_mark_referenced_by_bit - Mark a folio as referenced by bit.\n+ * @folio: the folio.\n+ *\n+ * non-MGLRU may want to make use of the lowest LRU referenced count bit\n+ * explicitly as a referenced mark.\n+ */\n+static inline void folio_mark_referenced_by_bit(struct folio *folio)\n+{\n+\tset_bit(PG_referenced, folio_flags(folio, 0));\n+}\n+\n+/**\n+ * folio_clear_referenced_by_bit - Clear the referenced bit of a folio.\n+ * @folio: the folio.\n+ */\n+static inline void folio_clear_referenced_by_bit(struct folio *folio)\n+{\n+\tclear_bit(PG_referenced, folio_flags(folio, 0));\n+}\n+\n+/**\n+ * folio_test_clear_referenced_by_bit - Test and clear the referenced bit\n+ * @folio: the folio.\n+ */\n+static inline bool folio_test_clear_referenced_by_bit(struct folio *folio)\n+{\n+\treturn test_and_clear_bit(PG_referenced, folio_flags(folio, 0));\n+}\n+\n+/**\n+ * folio_is_referenced_by_bit - Test if the referenced bit of a folio is set.\n+ * @folio: the folio.\n+ */\n+static inline bool folio_is_referenced_by_bit(const struct folio *folio)\n+{\n+\treturn test_bit(PG_referenced, const_folio_flags(folio, 0));\n+}\n+\n+/**\n+ * folio_is_workingset - Tell if a folio is part of the workingset.\n+ * @folio: the folio.\n+ *\n+ * Can be used to replace folio_test_workingset safely. For MGLRU the LRU\n+ * referenced count tells if a folio is a workingset as intended. For non-MGLRU,\n+ * the check below only holds true if the PG_workingset bit is set.\n+ */\n+static inline bool folio_is_workingset(const struct folio *folio)\n+{\n+\treturn folio_lru_refs(folio) \u003e= LRU_REFS_WORKINGSET;\n+}\n+\n+/**\n+ * folio_mark_workingset_by_bit - Set the workingset bit of a folio.\n+ * @folio: the folio.\n+ */\n+static inline void folio_mark_workingset_by_bit(struct folio *folio)\n+{\n+\tset_bit(PG_workingset, folio_flags(folio, 0));\n+}\n+\n+static inline void folio_migrate_refs(struct folio *new, const struct folio *old)\n+{\n+\tfolio_set_lru_refs(new, folio_lru_refs(old));\n+}\n+\n #ifdef CONFIG_LRU_GEN\n \n static inline bool lru_gen_switching(void)\n@@ -134,39 +288,54 @@ static inline int lru_hist_from_seq(unsigned long seq)\n \treturn seq % NR_HIST_GENS;\n }\n \n-static inline int lru_tier_from_refs(int refs, bool workingset)\n+static inline int lru_tier_from_refs(unsigned int refs)\n {\n-\tVM_WARN_ON_ONCE(refs \u003e BIT(LRU_REFS_WIDTH));\n-\n-\t/* see the comment on MAX_NR_TIERS */\n-\treturn workingset ? MAX_NR_TIERS - 1 : order_base_2(refs);\n+\tBUILD_BUG_ON(fls(LRU_REFS_MAX - 1) \u003e MAX_NR_TIERS - 1);\n+\tVM_WARN_ON_ONCE(refs \u003e LRU_REFS_MAX);\n+\tif (refs \u003c LRU_REFS_WORKINGSET)\n+\t\treturn 0;\n+\treturn fls(refs - 1);\n }\n \n-static inline int folio_lru_refs(const struct folio *folio)\n+/**\n+ * lru_gen_from_flags - Return the LRU generation number from folio flags.\n+ * @flags: folio flags\n+ *\n+ * Returns: A number between 0 and LRU_GEN_MAX, inclusive. Returns -1 if the\n+ * flags indicate the folio is off the list (e.g., isolated).\n+ */\n+static inline int lru_gen_from_flags(unsigned long flags)\n {\n-\tunsigned long flags = READ_ONCE(folio-\u003eflags.f);\n+\tint gen = ((flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF);\n \n-\tif (!(flags \u0026 BIT(PG_referenced)))\n-\t\treturn 0;\n-\t/*\n-\t * Return the total number of accesses including PG_referenced. Also see\n-\t * the comment on LRU_REFS_FLAGS.\n-\t */\n-\treturn ((flags \u0026 LRU_REFS_MASK) \u003e\u003e LRU_REFS_PGOFF) + 1;\n+\tgen -= 1;\n+\tVM_WARN_ON_ONCE(gen != -1 \u0026\u0026 gen \u003e LRU_GEN_MAX);\n+\treturn gen;\n }\n \n-static inline int folio_lru_gen(const struct folio *folio)\n+/**\n+ * lru_gen_set_flags - Set the LRU generation number to specified folio flags.\n+ * @flags: pointer to the folio flags\n+ * @gen: generation number, between 0 and LRU_GEN_MAX, inclusive.\n+ */\n+static inline void lru_gen_set_flags(unsigned long *flags, int gen)\n {\n-\tunsigned long flags = READ_ONCE(folio-\u003eflags.f);\n+\tBUILD_BUG_ON((LRU_GEN_MAX + 1) != MAX_NR_GENS);\n+\tVM_WARN_ON_ONCE(gen \u003e LRU_GEN_MAX || gen \u003c 0);\n+\t*flags \u0026= ~LRU_GEN_MASK;\n+\t*flags |= (gen + 1UL) \u003c\u003c LRU_GEN_PGOFF;\n+}\n \n-\treturn ((flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF) - 1;\n+static inline int folio_lru_gen(const struct folio *folio)\n+{\n+\treturn lru_gen_from_flags(READ_ONCE(*const_folio_flags(folio, 0)));\n }\n \n static inline bool lru_gen_is_active(const struct lruvec *lruvec, int gen)\n {\n-\tunsigned long max_seq = lruvec-\u003elrugen.max_seq;\n+\tunsigned long max_seq = READ_ONCE(lruvec-\u003elrugen.max_seq);\n \n-\tVM_WARN_ON_ONCE(gen \u003e= MAX_NR_GENS);\n+\tVM_WARN_ON_ONCE(gen \u003e LRU_GEN_MAX);\n \n \t/* see the comment on MIN_NR_GENS */\n \treturn gen == lru_gen_from_seq(max_seq) || gen == lru_gen_from_seq(max_seq - 1);\n@@ -186,11 +355,9 @@ static inline void lru_gen_update_size(struct lruvec *lruvec, struct folio *foli\n \tVM_WARN_ON_ONCE(old_gen == -1 \u0026\u0026 new_gen == -1);\n \n \tif (old_gen \u003e= 0)\n-\t\tWRITE_ONCE(lrugen-\u003enr_pages[old_gen][type][zone],\n-\t\t\t lrugen-\u003enr_pages[old_gen][type][zone] - delta);\n+\t\tatomic_long_sub(delta, \u0026lrugen-\u003enr_pages[old_gen][type][zone]);\n \tif (new_gen \u003e= 0)\n-\t\tWRITE_ONCE(lrugen-\u003enr_pages[new_gen][type][zone],\n-\t\t\t lrugen-\u003enr_pages[new_gen][type][zone] + delta);\n+\t\tatomic_long_add(delta, \u0026lrugen-\u003enr_pages[new_gen][type][zone]);\n \n \t/* addition */\n \tif (old_gen \u003c 0) {\n@@ -223,23 +390,24 @@ static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec,\n \t\t\t\t\t bool reclaiming)\n {\n \tint gen;\n+\tint refs = folio_lru_refs(folio);\n \tint type = folio_is_file_lru(folio);\n \tconst struct lru_gen_folio *lrugen = \u0026lruvec-\u003elrugen;\n \n \t/*\n-\t * +-----------------------------------+-----------------------------------+\n-\t * | Accessed through page tables and | Accessed through file descriptors |\n-\t * | promoted by folio_update_gen() | and protected by folio_inc_gen() |\n-\t * +-----------------------------------+-----------------------------------+\n-\t * | PG_active (set while isolated) | |\n-\t * +-----------------+-----------------+-----------------+-----------------+\n-\t * | PG_workingset | PG_referenced | PG_workingset | LRU_REFS_FLAGS |\n-\t * +-----------------------------------+-----------------------------------+\n-\t * |\u003c---------- MIN_NR_GENS ----------\u003e| |\n-\t * |\u003c---------------------------- MAX_NR_GENS ----------------------------\u003e|\n+\t * +------------------------------------------+------------------------------------------+\n+\t * | Accessed through page tables and | Accessed through file descriptors |\n+\t * | promoted by folio_inc_lru_refs_walk() | protected by folio_inc_lru_refs/inc_gen |\n+\t * +------------------------------------------+------------------------------------------+\n+\t * | PG_active (set at isolation or refault) | |\n+\t * +--------------------+---------------------+--------------------+---------------------+\n+\t * | LRU_REFS_MAX | LRU_REFS_WORKINGSET | LRU_REFS_MAX | LRU_REFS_WORKINGSET |\n+\t * +------------------------------------------+------------------------------------------+\n+\t * |\u003c-------------- MIN_NR_GENS -------------\u003e| |\n+\t * |\u003c----------------------------------- MAX_NR_GENS -----------------------------------\u003e|\n \t */\n \tif (folio_test_active(folio))\n-\t\tgen = MIN_NR_GENS - folio_test_workingset(folio);\n+\t\tgen = MIN_NR_GENS - (refs \u003e= LRU_REFS_WORKINGSET);\n \telse if (reclaiming)\n \t\tgen = MAX_NR_GENS;\n \telse if ((!folio_is_file_lru(folio) \u0026\u0026 !folio_test_swapcache(folio)) ||\n@@ -247,7 +415,7 @@ static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec,\n \t\t (folio_test_dirty(folio) || folio_test_writeback(folio))))\n \t\tgen = MIN_NR_GENS;\n \telse\n-\t\tgen = MAX_NR_GENS - (folio_test_workingset(folio) || folio_test_referenced(folio));\n+\t\tgen = MAX_NR_GENS - (refs \u003e= LRU_REFS_WORKINGSET);\n \n \treturn max(READ_ONCE(lrugen-\u003emax_seq) - gen + 1, READ_ONCE(lrugen-\u003emin_seq[type]));\n }\n@@ -270,7 +438,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio,\n \tgen = lru_gen_from_seq(seq);\n \tflags = (gen + 1UL) \u003c\u003c LRU_GEN_PGOFF;\n \t/* see the comment on MIN_NR_GENS about PG_active */\n-\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_GEN_MASK | BIT(PG_active), flags);\n+\tset_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK | BIT(PG_active), flags);\n \n \tlru_gen_update_size(lruvec, folio, -1, gen);\n \t/* for folio_rotate_reclaimable() */\n@@ -295,7 +463,7 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,\n \n \t/* for folio_migrate_flags() */\n \tflags = !reclaiming \u0026\u0026 lru_gen_is_active(lruvec, gen) ? BIT(PG_active) : 0;\n-\tflags = set_mask_bits(\u0026folio-\u003eflags.f, LRU_GEN_MASK, flags);\n+\tflags = set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK, flags);\n \tgen = ((flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF) - 1;\n \n \tlru_gen_update_size(lruvec, folio, gen, -1);\n@@ -304,12 +472,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,\n \treturn true;\n }\n \n-static inline void folio_migrate_refs(struct folio *new, const struct folio *old)\n-{\n-\tunsigned long refs = READ_ONCE(old-\u003eflags.f) \u0026 LRU_REFS_MASK;\n-\n-\tset_mask_bits(\u0026new-\u003eflags.f, LRU_REFS_MASK, refs);\n-}\n #else /* !CONFIG_LRU_GEN */\n \n static inline bool lru_gen_enabled(void)\n@@ -337,10 +499,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,\n \treturn false;\n }\n \n-static inline void folio_migrate_refs(struct folio *new, const struct folio *old)\n-{\n-\n-}\n #endif /* CONFIG_LRU_GEN */\n \n static __always_inline\ndiff --git a/include/linux/mmzone.h b/include/linux/mmzone.h\nindex a26c8b8552222..e4f7efc02e501 100644\n--- a/include/linux/mmzone.h\n+++ b/include/linux/mmzone.h\n@@ -472,54 +472,111 @@ enum lruvec_flags {\n #define MAX_NR_GENS\t\t4U\n \n /*\n- * Each generation is divided into multiple tiers. A folio accessed N times\n- * through file descriptors is in tier order_base_2(N). A folio in the first\n- * tier (N=0,1) is marked by PG_referenced unless it was faulted in through page\n- * tables or read ahead. A folio in the last tier (MAX_NR_TIERS-1) is marked by\n- * PG_workingset. A folio in any other tier (1\u003cN\u003c5) between the first and last\n- * is marked by additional bits of LRU_REFS_WIDTH in folio-\u003eflags.\n+ * Each generation is divided into multiple tiers. A folio's referenced\n+ * count maps to a tier as shown below:\n *\n- * In contrast to moving across generations which requires the LRU lock, moving\n- * across tiers only involves atomic operations on folio-\u003eflags and therefore\n- * has a negligible cost in the buffered access path. In the eviction path,\n- * comparisons of refaulted/(evicted+protected) from the first tier and the rest\n- * infer whether folios accessed multiple times through file descriptors are\n- * statistically hot and thus worth protecting.\n+ * MGLRU (frequency guidance)\n+ * Refs Tier |- Refs: how many times (at least) a folio has been referenced.\n+ * 0 0 |- Mostly cold pages, readahead, etc. [1]\n+ * 1 0 |= LRU_REFS_REFERENCED: Used at least once. [2]\n+ * -WORKINGSET-+|- Pages beyond are workingset and never fall below this floor. [3]\n+ * 2 1\u003c-+|= LRU_REFS_WORKINGSET: Classical workingset, accessed twice, protected. [4]\n+ * 3 2 |- LRU_REFS_PROTECTED: Protected workingset, promoted pages capped at here. [5]\n+ * 4* 2 |\n+ * 5* 3 |- The tier here is MAX_NR_TIERS - 1\n+ * 6* 3 |\n+ * 7* 3 |= LRU_REFS_MAX: Promotion candidate. [6]\n+ * -PROMOTION-\u003e-/\n *\n- * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice the\n- * number of categories of the active/inactive LRU when keeping track of\n- * accesses through file descriptors. This uses MAX_NR_TIERS-2 spare bits in\n- * folio-\u003eflags, masked by LRU_REFS_MASK.\n+ * Ideally each tier holds folios of similar access patterns: lower tiers\n+ * are less important and evicted faster. A page's reference count and\n+ * tier are capped when it changes generation, preventing it from\n+ * dominating the new generation based on old-generation access history.\n+ * Generation ordering already ensures a newer-gen page is hotter than an\n+ * older-gen one regardless of tier.\n+ *\n+ * Refs tracks accesses from two sources: page table (lazily collected by\n+ * the page table aging walk or rmap eviction lookup) and file descriptors\n+ * (by folio_mark_accessed). Page table accesses are weighted heavier\n+ * because the accessed bit is sticky (undercounts repeated accesses),\n+ * passively collected, and page faults are generally more important as\n+ * userspace does not expect a memory access to block on reclaim. Both\n+ * access types increment refs by one; the result is capped at\n+ * LRU_REFS_PROTECTED on promotion or deferral, or LRU_REFS_MAX otherwise.\n+ *\n+ * 1. Tier is fls(N-1) for N \u003e 1, 0 otherwise. Folios with zero\n+ * accesses (refs == 0) are generally cold, e.g. readahead folios.\n+ *\n+ * Page table access advances a folio by one generation even at the\n+ * lowest refs or tier. Freshly allocated folios start with refs == 0;\n+ * faulted and mapped folios have their page table access bit set, so\n+ * the first page table access check always sets LRU_REFS_REFERENCED and\n+ * moves them one generation forward, driving aging and workingset shift.\n+ *\n+ * 2. Folios accessed once stay on tier 0: one-time usage does not\n+ * qualify for protection. A second access advances the folio,\n+ * aligning with classical LRU's use-twice threshold. A second page\n+ * table access promotes to the latest gen; file access only defers\n+ * eviction from the oldest gen.\n+ *\n+ * 3. Folios accessed at least twice are considered workingset. This\n+ * mostly aligns with classical LRU: at least one I/O is saved by\n+ * keeping them in memory. Folios at or above this level never fall\n+ * below tier 1 (the workingset floor), so tier 0 stays a clean tier\n+ * for cold cache while tier 1 serves as the fallback line for\n+ * actually reused or historically hot folios.\n+ *\n+ * Folios refaulted through a page fault at refs 1 will enter the second\n+ * newest gen, so faulting will be protected better.\n+ *\n+ * 4. Starting from tier 1, PID protection sacrifices lower tiers to\n+ * protect higher tiers by comparing refault rates for long-term\n+ * accuracy, and caps higher refs to this value. Since PID protection\n+ * bypasses page table lookup and clearing, when a further eviction\n+ * attempt occurs after PID loosens, the folio's page table access is\n+ * rechecked and the folio is sent back to LRU_REFS_PROTECTED. This\n+ * also gives folios a fair opportunity to be promoted by file access\n+ * again.\n+ *\n+ * Folios refaulted through a page fault at tier 1 or above are activated\n+ * and enter the newest gen. Non fault page will enter second oldest gen,\n+ * driving aging and workingset shifting.\n+ *\n+ * 5. Pages beyond the ordinary workingset tier form new tiers for the\n+ * PID controller to protect differently. Folios at or above this\n+ * level are capped at LRU_REFS_PROTECTED on promotion or deferral,\n+ * and at LRU_REFS_WORKINGSET under PID protection in the oldest\n+ * generation, where they represent a historical workingset.\n+ *\n+ * 6. Folios that reach LRU_REFS_MAX are advanced to the next generation\n+ * on further access, with refs capped to LRU_REFS_PROTECTED. This\n+ * gives them a fair start for advancement to an even newer generation\n+ * while keeping hot folios distinguishable.\n+ *\n+ * Tiering uses PG_referenced and PG_workingset as the lower two bits,\n+ * and the bits masked by LRU_REFS_MASK as the higher bits.\n+ *\n+ * A folio's referenced count never goes backwards except upon gen\n+ * increase as described above. Refault of a reclaimed folio restores\n+ * its referenced count, capped at LRU_REFS_PROTECTED, which aligns with\n+ * promotion. Page table refaults of previous workingset folios send\n+ * them to the latest gen, driving aging faster.\n+ *\n+ * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice\n+ * the number of categories of the active/inactive LRU.\n */\n #define MAX_NR_TIERS\t\t4U\n+#define LRU_REFS_REFERENCED\t0x1\n+#define LRU_REFS_WORKINGSET\t0x2\n+#define LRU_REFS_PROTECTED\t0x3\n \n #ifndef __GENERATING_BOUNDS_H\n \n #define LRU_GEN_MASK\t\t((BIT(LRU_GEN_WIDTH) - 1) \u003c\u003c LRU_GEN_PGOFF)\n+#define LRU_GEN_MAX\t\t(BIT(LRU_GEN_WIDTH - 1) - 1)\n #define LRU_REFS_MASK\t\t((BIT(LRU_REFS_WIDTH) - 1) \u003c\u003c LRU_REFS_PGOFF)\n-\n-/*\n- * For folios accessed multiple times through file descriptors,\n- * lru_gen_inc_refs() sets additional bits of LRU_REFS_WIDTH in folio-\u003eflags\n- * after PG_referenced, then PG_workingset after LRU_REFS_WIDTH. After all its\n- * bits are set, i.e., LRU_REFS_FLAGS|BIT(PG_workingset), a folio is lazily\n- * promoted into the second oldest generation in the eviction path. And when\n- * folio_inc_gen() does that, it clears LRU_REFS_FLAGS so that\n- * lru_gen_inc_refs() can start over. Note that for this case, LRU_REFS_MASK is\n- * only valid when PG_referenced is set.\n- *\n- * For folios accessed multiple times through page tables, folio_update_gen()\n- * from a page table walk or lru_gen_set_refs() from a rmap walk sets\n- * PG_referenced after the accessed bit is cleared for the first time.\n- * Thereafter, those two paths set PG_workingset and promote folios to the\n- * youngest generation. Like folio_inc_gen(), folio_update_gen() also clears\n- * PG_referenced. Note that for this case, LRU_REFS_MASK is not used.\n- *\n- * For both cases above, after PG_workingset is set on a folio, it remains until\n- * this folio is either reclaimed, or \"deactivated\" by lru_gen_clear_refs(). It\n- * can be set again if lru_gen_test_recent() returns true upon a refault.\n- */\n-#define LRU_REFS_FLAGS\t\t(LRU_REFS_MASK | BIT(PG_referenced))\n+#define LRU_REFS_FLAGS\t\t(LRU_REFS_MASK | BIT(PG_referenced) | BIT(PG_workingset))\n+#define LRU_REFS_MAX\t\t(BIT(LRU_REFS_WIDTH + 2) - 1)\n \n struct lruvec;\n struct page_vma_mapped_walk;\n@@ -570,7 +627,7 @@ struct lru_gen_folio {\n \t/* the multi-gen LRU lists, lazily sorted on eviction */\n \tstruct list_head folios[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];\n \t/* the multi-gen LRU sizes, eventually consistent */\n-\tlong nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];\n+\tatomic_long_t nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];\n \t/* the exponential moving average of refaulted */\n \tunsigned long avg_refaulted[ANON_AND_FILE][MAX_NR_TIERS];\n \t/* the exponential moving average of evicted+protected */\ndiff --git a/kernel/bounds.c b/kernel/bounds.c\nindex 02b619eb61065..06a034713b5dd 100644\n--- a/kernel/bounds.c\n+++ b/kernel/bounds.c\n@@ -25,7 +25,7 @@ int main(void)\n \tDEFINE(SPINLOCK_SIZE, sizeof(spinlock_t));\n #ifdef CONFIG_LRU_GEN\n \tDEFINE(LRU_GEN_WIDTH, order_base_2(MAX_NR_GENS + 1));\n-\tDEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 2);\n+\tDEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 3);\n #else\n \tDEFINE(LRU_GEN_WIDTH, 0);\n \tDEFINE(__LRU_REFS_WIDTH, 0);\ndiff --git a/mm/filemap.c b/mm/filemap.c\nindex 6afec636881fb..a88a6140ed094 100644\n--- a/mm/filemap.c\n+++ b/mm/filemap.c\n@@ -1259,7 +1259,7 @@ static inline int folio_wait_bit_common(struct folio *folio, int bit_nr,\n \tbool in_thrashing;\n \n \tif (bit_nr == PG_locked \u0026\u0026\n-\t !folio_test_uptodate(folio) \u0026\u0026 folio_test_workingset(folio)) {\n+\t !folio_test_uptodate(folio) \u0026\u0026 folio_is_workingset(folio)) {\n \t\tdelayacct_thrashing_start(\u0026in_thrashing);\n \t\tpsi_memstall_enter(\u0026pflags);\n \t\tthrashing = true;\n@@ -1414,7 +1414,7 @@ void softleaf_entry_wait_on_locked(softleaf_t entry, spinlock_t *ptl)\n \tstruct folio *folio = softleaf_to_folio(entry);\n \n \tq = folio_waitqueue(folio);\n-\tif (!folio_test_uptodate(folio) \u0026\u0026 folio_test_workingset(folio)) {\n+\tif (!folio_test_uptodate(folio) \u0026\u0026 folio_is_workingset(folio)) {\n \t\tdelayacct_thrashing_start(\u0026in_thrashing);\n \t\tpsi_memstall_enter(\u0026pflags);\n \t\tthrashing = true;\n@@ -2510,7 +2510,7 @@ static void filemap_get_read_batch(struct address_space *mapping,\n static int filemap_read_folio(struct file *file, filler_t filler,\n \t\tstruct folio *folio)\n {\n-\tbool workingset = folio_test_workingset(folio);\n+\tbool workingset = folio_is_workingset(folio);\n \tunsigned long pflags;\n \tint error;\n \n@@ -3981,7 +3981,7 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,\n \t\t */\n \t\tif ((map_ret \u0026 VM_FAULT_NOPAGE) \u0026\u0026\n \t\t !(vmf-\u003eflags \u0026 FAULT_FLAG_TRIED) \u0026\u0026\n-\t\t !folio_test_workingset(folio) \u0026\u0026\n+\t\t !folio_is_workingset(folio) \u0026\u0026\n \t\t !(vma-\u003evm_flags \u0026 (VM_SEQ_READ | VM_EXEC))) {\n \t\t\tunsigned short mmap_miss;\n \ndiff --git a/mm/folio.c b/mm/folio.c\nindex a9e328c3f21bb..a326602a59fe3 100644\n--- a/mm/folio.c\n+++ b/mm/folio.c\n@@ -272,7 +272,6 @@ static void lru_activate(struct lruvec *lruvec, struct folio *folio)\n \tif (folio_test_active(folio) || folio_test_unevictable(folio))\n \t\treturn;\n \n-\n \tlruvec_del_folio(lruvec, folio);\n \tfolio_set_active(folio);\n \tlruvec_add_folio(lruvec, folio);\n@@ -351,30 +350,6 @@ static void __lru_cache_activate_folio(struct folio *folio)\n \n #ifdef CONFIG_LRU_GEN\n \n-static void lru_gen_inc_refs(struct folio *folio)\n-{\n-\tunsigned long new_flags, old_flags = READ_ONCE(folio-\u003eflags.f);\n-\n-\tif (folio_test_unevictable(folio))\n-\t\treturn;\n-\n-\t/* see the comment on LRU_REFS_FLAGS */\n-\tif (!folio_test_referenced(folio)) {\n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_MASK, BIT(PG_referenced));\n-\t\treturn;\n-\t}\n-\n-\tdo {\n-\t\tif ((old_flags \u0026 LRU_REFS_MASK) == LRU_REFS_MASK) {\n-\t\t\tif (!folio_test_workingset(folio))\n-\t\t\t\tfolio_set_workingset(folio);\n-\t\t\treturn;\n-\t\t}\n-\n-\t\tnew_flags = old_flags + BIT(LRU_REFS_PGOFF);\n-\t} while (!try_cmpxchg(\u0026folio-\u003eflags.f, \u0026old_flags, new_flags));\n-}\n-\n static bool lru_gen_clear_refs(struct folio *folio)\n {\n \tint gen = folio_lru_gen(folio);\n@@ -384,7 +359,7 @@ static bool lru_gen_clear_refs(struct folio *folio)\n \tif (gen \u003c 0)\n \t\treturn true;\n \n-\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_FLAGS | BIT(PG_workingset), 0);\n+\tfolio_set_lru_refs(folio, 0);\n \n \trcu_read_lock();\n \tseq = READ_ONCE(folio_lruvec(folio)-\u003elrugen.min_seq[type]);\n@@ -395,10 +370,6 @@ static bool lru_gen_clear_refs(struct folio *folio)\n \n #else /* !CONFIG_LRU_GEN */\n \n-static void lru_gen_inc_refs(struct folio *folio)\n-{\n-}\n-\n static bool lru_gen_clear_refs(struct folio *folio)\n {\n \treturn false;\n@@ -424,7 +395,8 @@ void folio_mark_accessed(struct folio *folio)\n \tif (folio_test_dropbehind(folio))\n \t\treturn;\n \tif (lru_gen_enabled()) {\n-\t\tlru_gen_inc_refs(folio);\n+\t\tif (!folio_test_unevictable(folio))\n+\t\t\tfolio_inc_lru_refs(folio, false, false);\n \t\treturn;\n \t}\n \n@@ -470,21 +442,6 @@ void folio_add_lru(struct folio *folio)\n \t\t\tfolio_test_unevictable(folio), folio);\n \tVM_BUG_ON_FOLIO(folio_test_lru(folio), folio);\n \n-\t/*\n-\t * For refaulted workingset folios, set PG_active so they\n-\t * can be added to active generations.\n-\t * For prefaulted file folios, folio_mark_accessed() sets\n-\t * PG_referenced so lru_gen_folio_seq() places them into\n-\t * the second oldest generation.\n-\t */\n-\tif (lru_gen_enabled() \u0026\u0026 !folio_test_unevictable(folio) \u0026\u0026\n-\t lru_gen_in_fault() \u0026\u0026 !(current-\u003eflags \u0026 PF_MEMALLOC)) {\n-\t\tif (folio_test_workingset(folio))\n-\t\t\tfolio_set_active(folio);\n-\t\telse if (!folio_test_referenced(folio))\n-\t\t\tfolio_mark_accessed(folio);\n-\t}\n-\n \tfolio_batch_add_and_move(folio, lru_add);\n }\n EXPORT_SYMBOL(folio_add_lru);\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex 21c92ee48e469..043c9ac963b4a 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -3058,8 +3058,8 @@ static void __split_huge_pud_locked(struct vm_area_struct *vma, pud_t *pud,\n \n \tif (!folio_test_dirty(folio) \u0026\u0026 pud_dirty(old_pud))\n \t\tfolio_mark_dirty(folio);\n-\tif (!folio_test_referenced(folio) \u0026\u0026 pud_young(old_pud))\n-\t\tfolio_set_referenced(folio);\n+\tif (pud_young(old_pud) \u0026\u0026 vma_has_recency(vma))\n+\t\tfolio_mark_accessed(folio);\n \tfolio_remove_rmap_pud(folio, page, vma);\n \tadd_mm_counter(vma-\u003evm_mm, mm_counter_file(folio),\n \t\t-HPAGE_PUD_NR);\n@@ -3181,8 +3181,8 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n \t\t\tfolio = page_folio(page);\n \t\t\tif (!folio_test_dirty(folio) \u0026\u0026 pmd_dirty(old_pmd))\n \t\t\t\tfolio_mark_dirty(folio);\n-\t\t\tif (!folio_test_referenced(folio) \u0026\u0026 pmd_young(old_pmd))\n-\t\t\t\tfolio_set_referenced(folio);\n+\t\t\tif (pmd_young(old_pmd) \u0026\u0026 vma_has_recency(vma))\n+\t\t\t\tfolio_mark_accessed(folio);\n \t\t\tfolio_remove_rmap_pmd(folio, page, vma);\n \t\t\tadd_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);\n \t\t\tfolio_put(folio);\ndiff --git a/mm/khugepaged.c b/mm/khugepaged.c\nindex b237f6e7662a5..86c9b07dece6a 100644\n--- a/mm/khugepaged.c\n+++ b/mm/khugepaged.c\n@@ -809,7 +809,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\t */\n \t\tif (cc-\u003eis_khugepaged \u0026\u0026\n \t\t (pte_young(pteval) || folio_test_young(folio) ||\n-\t\t folio_test_referenced(folio) ||\n+\t\t folio_is_referenced(folio) ||\n \t\t mmu_notifier_test_young(vma-\u003evm_mm, addr)))\n \t\t\treferenced++;\n \t}\n@@ -1767,7 +1767,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\t */\n \t\tif (cc-\u003eis_khugepaged \u0026\u0026\n \t\t (pte_young(pteval) || folio_test_young(folio) ||\n-\t\t folio_test_referenced(folio) ||\n+\t\t folio_is_referenced(folio) ||\n \t\t mmu_notifier_test_young(vma-\u003evm_mm, addr)))\n \t\t\treferenced++;\n \t}\n@@ -2752,7 +2752,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,\n \t\t/*\n \t\t * We probably should check if the folio is referenced\n \t\t * here, but nobody would transfer pte_young() to\n-\t\t * folio_test_referenced() for us. And rmap walk here\n+\t\t * folio_is_referenced() for us. And rmap walk here\n \t\t * is just too costly...\n \t\t */\n \ndiff --git a/mm/madvise.c b/mm/madvise.c\nindex 07a21ca31bad4..f132dd7418f58 100644\n--- a/mm/madvise.c\n+++ b/mm/madvise.c\n@@ -350,6 +350,27 @@ static inline int madvise_folio_pte_batch(unsigned long addr, unsigned long end,\n \t\t\t\t FPB_MERGE_YOUNG_DIRTY);\n }\n \n+/*\n+ * We are deactivating a folio for accelerating reclaiming.\n+ * VM couldn't reclaim the folio unless we clear PG_young.\n+ * As a side effect, it makes confuse idle-page tracking\n+ * because they will miss recent referenced history.\n+ */\n+static void madvise_cold_or_pageout_prep_folio(struct folio *folio)\n+{\n+\tfolio_test_clear_young(folio);\n+\n+\t/*\n+\t * MGLRU clears all reference flags in folio_deactivate,\n+\t * no need to touch it here.\n+\t */\n+\tif (!lru_gen_enabled()) {\n+\t\tfolio_clear_referenced_by_bit(folio);\n+\t\tif (folio_test_active(folio))\n+\t\t\tfolio_mark_workingset_by_bit(folio);\n+\t}\n+}\n+\n static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\n \t\t\t\tunsigned long addr, unsigned long end,\n \t\t\t\tstruct mm_walk *walk)\n@@ -424,10 +445,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\n \t\t\ttlb_remove_pmd_tlb_entry(tlb, pmd, addr);\n \t\t}\n \n-\t\tfolio_clear_referenced(folio);\n-\t\tfolio_test_clear_young(folio);\n-\t\tif (folio_test_active(folio))\n-\t\t\tfolio_set_workingset(folio);\n+\t\tmadvise_cold_or_pageout_prep_folio(folio);\n \t\tif (pageout) {\n \t\t\tif (folio_isolate_lru(folio)) {\n \t\t\t\tif (folio_test_unevictable(folio))\n@@ -533,16 +551,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\n \t\t\ttlb_remove_tlb_entries(tlb, pte, nr, addr);\n \t\t}\n \n-\t\t/*\n-\t\t * We are deactivating a folio for accelerating reclaiming.\n-\t\t * VM couldn't reclaim the folio unless we clear PG_young.\n-\t\t * As a side effect, it makes confuse idle-page tracking\n-\t\t * because they will miss recent referenced history.\n-\t\t */\n-\t\tfolio_clear_referenced(folio);\n-\t\tfolio_test_clear_young(folio);\n-\t\tif (folio_test_active(folio))\n-\t\t\tfolio_set_workingset(folio);\n+\t\tmadvise_cold_or_pageout_prep_folio(folio);\n \t\tif (pageout) {\n \t\t\tif (folio_isolate_lru(folio)) {\n \t\t\t\tif (folio_test_unevictable(folio))\ndiff --git a/mm/memcontrol.c b/mm/memcontrol.c\nindex 1bde9d5af88af..3fbd7a6f66500 100644\n--- a/mm/memcontrol.c\n+++ b/mm/memcontrol.c\n@@ -1522,35 +1522,19 @@ struct lruvec *folio_lruvec_lock_irqsave(struct folio *folio,\n * @zid: zone id of the accounted pages\n * @nr_pages: positive when adding or negative when removing\n *\n- * This function must be called under lru_lock, just before a page is added\n- * to or just after a page is removed from an lru list.\n+ * This function must be called when a page is added to or removed from\n+ * an lru list. Caller need to protect the lruvec from being freed.\n */\n void mem_cgroup_update_lru_size(struct lruvec *lruvec, enum lru_list lru,\n \t\t\t\tint zid, long nr_pages)\n {\n \tstruct mem_cgroup_per_node *mz;\n-\tunsigned long *lru_size;\n-\tlong size;\n \n \tif (mem_cgroup_disabled())\n \t\treturn;\n \n \tmz = container_of(lruvec, struct mem_cgroup_per_node, lruvec);\n-\tlru_size = \u0026mz-\u003elru_zone_size[zid][lru];\n-\n-\tif (nr_pages \u003c 0)\n-\t\t*lru_size += nr_pages;\n-\n-\tsize = *lru_size;\n-\tif (WARN_ONCE(size \u003c 0,\n-\t\t\"%s(%p, %d, %ld): lru_size %ld\\n\",\n-\t\t__func__, lruvec, lru, nr_pages, size)) {\n-\t\tVM_BUG_ON(1);\n-\t\t*lru_size = 0;\n-\t}\n-\n-\tif (nr_pages \u003e 0)\n-\t\t*lru_size += nr_pages;\n+\tatomic_long_add(nr_pages, \u0026mz-\u003elru_zone_size[zid][lru]);\n }\n \n /**\ndiff --git a/mm/migrate.c b/mm/migrate.c\nindex b937cbd764808..806f1e913a38f 100644\n--- a/mm/migrate.c\n+++ b/mm/migrate.c\n@@ -779,8 +779,6 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)\n {\n \tint cpupid;\n \n-\tif (folio_test_referenced(folio))\n-\t\tfolio_set_referenced(newfolio);\n \tif (folio_test_uptodate(folio))\n \t\tfolio_mark_uptodate(newfolio);\n \tif (folio_test_clear_active(folio)) {\n@@ -788,8 +786,6 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)\n \t\tfolio_set_active(newfolio);\n \t} else if (folio_test_clear_unevictable(folio))\n \t\tfolio_set_unevictable(newfolio);\n-\tif (folio_test_workingset(folio))\n-\t\tfolio_set_workingset(newfolio);\n \tif (folio_test_checked(folio))\n \t\tfolio_set_checked(newfolio);\n \t/*\ndiff --git a/mm/page_io.c b/mm/page_io.c\nindex e4fa7ffffe8bb..e7efc5bff6683 100644\n--- a/mm/page_io.c\n+++ b/mm/page_io.c\n@@ -25,6 +25,7 @@\n #include \u003clinux/sched/task.h\u003e\n #include \u003clinux/delayacct.h\u003e\n #include \u003clinux/zswap.h\u003e\n+#include \u003clinux/mm_inline.h\u003e\n #include \"swap.h\"\n #include \"swap_table.h\"\n \n@@ -452,7 +453,7 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)\n {\n \tstruct swap_info_struct *sis = __swap_entry_to_info(folio-\u003eswap);\n \tbool synchronous = sis-\u003eflags \u0026 SWP_SYNCHRONOUS_IO;\n-\tbool workingset = folio_test_workingset(folio);\n+\tbool workingset = folio_is_workingset(folio);\n \tunsigned long pflags;\n \tbool in_thrashing;\n \ndiff --git a/mm/readahead.c b/mm/readahead.c\nindex 558c92957518b..3ab796af64907 100644\n--- a/mm/readahead.c\n+++ b/mm/readahead.c\n@@ -302,7 +302,7 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,\n \t\t}\n \t\tif (i == mark)\n \t\t\tfolio_set_readahead(folio);\n-\t\tractl-\u003e_workingset |= folio_test_workingset(folio);\n+\t\tractl-\u003e_workingset |= folio_is_workingset(folio);\n \t\tractl-\u003e_nr_pages += min_nrpages;\n \t\ti += min_nrpages;\n \t}\n@@ -474,7 +474,7 @@ static inline int ra_alloc_folio(struct readahead_control *ractl, pgoff_t index,\n \t}\n \n \tractl-\u003e_nr_pages += 1UL \u003c\u003c order;\n-\tractl-\u003e_workingset |= folio_test_workingset(folio);\n+\tractl-\u003e_workingset |= folio_is_workingset(folio);\n \treturn 0;\n }\n \n@@ -817,7 +817,7 @@ void readahead_expand(struct readahead_control *ractl,\n \t\t\tfolio_put(folio);\n \t\t\treturn;\n \t\t}\n-\t\tif (unlikely(folio_test_workingset(folio)) \u0026\u0026\n+\t\tif (unlikely(folio_is_workingset(folio)) \u0026\u0026\n \t\t\t\t!ractl-\u003e_workingset) {\n \t\t\tractl-\u003e_workingset = true;\n \t\t\tpsi_memstall_enter(\u0026ractl-\u003e_pflags);\n@@ -846,7 +846,7 @@ void readahead_expand(struct readahead_control *ractl,\n \t\t\tfolio_put(folio);\n \t\t\treturn;\n \t\t}\n-\t\tif (unlikely(folio_test_workingset(folio)) \u0026\u0026\n+\t\tif (unlikely(folio_is_workingset(folio)) \u0026\u0026\n \t\t\t\t!ractl-\u003e_workingset) {\n \t\t\tractl-\u003e_workingset = true;\n \t\t\tpsi_memstall_enter(\u0026ractl-\u003e_pflags);\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 17d2b793cbfc4..913e69eae5348 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -830,35 +830,177 @@ enum folio_references {\n };\n \n #ifdef CONFIG_LRU_GEN\n+/******************************************************************************\n+ * Referenced count feedback\n+ ******************************************************************************/\n+\n /*\n- * Only used on a mapped folio in the eviction (rmap walk) path, where promotion\n- * needs to be done by taking the folio off the LRU list and then adding it back\n- * with PG_active set. In contrast, the aging (page table walk) path uses\n- * folio_update_gen().\n+ * The folio_inc_lru_refs{_*} helpers below collect the referenced info\n+ * (hotness) from other parts, including the page table walker, the rmap walk\n+ * upon eviction, the rmap lookaround, and file descriptors\n+ * (folio_mark_accessed).\n+ *\n+ * Page table accesses escalate a folio in two steps. The first access\n+ * advances it one generation; a second access sends it to the newest\n+ * generation. Executable file folios skip the first step and are promoted\n+ * immediately, as reclaiming them causes IO thrashing.\n+ *\n+ * File descriptor accesses do not promote. They only defer eviction from\n+ * the oldest generation, and only once the folio is a workingset folio\n+ * (LRU_REFS_WORKINGSET), leaving the rest to PID protection. Page table\n+ * accesses are treated more generously because the accessed bit is sticky\n+ * (it under-counts repeated accesses) and because a page fault is more\n+ * costly than file descriptor I/O.\n+ *\n+ * PID protection operates on tier \u003e 0 folios. The one proactive promotion\n+ * outside of it and the page table path is the overflow case where the\n+ * referenced count exceeds LRU_REFS_MAX, which means the folio is hotter\n+ * than everything else in its generation.\n+ *\n+ * Whenever a folio changes generation here its referenced count is capped at\n+ * LRU_REFS_PROTECTED, so it starts at or below the protected tier regardless\n+ * of its old-generation access history. PID protection (folio_inc_gen) caps\n+ * at LRU_REFS_WORKINGSET independently.\n */\n-static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags)\n+\n+/*\n+ * Update the folio's lru refs indicator without taking the folio lock,\n+ * isolation, or lruvec lock. Used by both page table access (@is_fault=true)\n+ * and by file access (@is_fault=false).\n+ */\n+int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec)\n {\n-\t/* see the comment on LRU_REFS_FLAGS */\n-\tif (!folio_test_referenced(folio) \u0026\u0026 !folio_test_workingset(folio)) {\n-\t\t/* Activate file-backed executable folios after first usage. */\n-\t\tif (is_exec_file_folio(folio, vma_flags)) {\n-\t\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_FLAGS, BIT(PG_workingset));\n-\t\t\treturn true;\n+\tint max_gen, min_gen;\n+\tint type, refs, gen, new_gen;\n+\tunsigned long new_flags, old_flags, max_seq;\n+\tstruct lru_gen_folio *lrugen;\n+\tstruct lruvec *lruvec;\n+\n+\ttype = folio_is_file_lru(folio);\n+\tlruvec = folio_lruvec_live_get(folio);\n+\tlrugen = \u0026lruvec-\u003elrugen;\n+\n+\told_flags = READ_ONCE(*folio_flags(folio, 0));\n+\tdo {\n+\t\tnew_flags = old_flags;\n+\t\tgen = lru_gen_from_flags(old_flags);\n+\t\trefs = lru_refs_from_flags(old_flags) + 1;\n+\t\tnew_gen = gen;\n+\t\tif (!(old_flags \u0026 BIT(PG_lru)) || gen \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tmax_seq = READ_ONCE(lrugen-\u003emax_seq);\n+\t\tmax_gen = lru_gen_from_seq(max_seq);\n+\t\tmin_gen = lru_gen_from_seq(READ_ONCE(lrugen-\u003emin_seq[type]));\n+\t\tif (gen == max_gen)\n+\t\t\tgoto out;\n+\n+\t\tif (is_fault || is_exec) {\n+\t\t\t/* Promote second page table access or executable */\n+\t\t\tif (refs \u003e LRU_REFS_REFERENCED || is_exec)\n+\t\t\t\tnew_gen = max_gen;\n+\t\t\telse\n+\t\t\t\tnew_gen = (gen + 1UL) % MAX_NR_GENS;\n+\t\t\trefs = min(refs, LRU_REFS_PROTECTED);\n+\t\t} else if (refs \u003e LRU_REFS_MAX) {\n+\t\t\t/* LRU refs counting overflow, bump the gen */\n+\t\t\tnew_gen = (gen + 1UL) % MAX_NR_GENS;\n+\t\t\trefs = LRU_REFS_PROTECTED;\n+\t\t} else if (gen == min_gen \u0026\u0026 refs \u003e= LRU_REFS_WORKINGSET) {\n+\t\t\t/* Defer eviction of just accessed workingset */\n+\t\t\tnew_gen = (gen + 1UL) % MAX_NR_GENS;\n+\t\t\trefs = min(refs, LRU_REFS_PROTECTED);\n \t\t}\n+out:\n+\t\trefs = min(refs, LRU_REFS_MAX);\n+\t\tlru_refs_set_flags(\u0026new_flags, refs);\n+\t\tif (new_gen \u003e= 0)\n+\t\t\tlru_gen_set_flags(\u0026new_flags, new_gen);\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n \n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_MASK, BIT(PG_referenced));\n-\t\treturn false;\n+\tif (new_gen != gen) {\n+\t\t/*\n+\t\t * Gen can only go forward, so concurrent aging is\n+\t\t * usually fine, except when multiple aging increase\n+\t\t * max_seq multiple times, new_gen may have go beyond\n+\t\t * the new max_seq's current gen border and causes\n+\t\t * hotness inversion. In that very unlikely case,\n+\t\t * just activate the folio.\n+\t\t */\n+\t\tlru_gen_update_size(lruvec, folio, gen, new_gen);\n+\t\tif (unlikely(READ_ONCE(lrugen-\u003emax_seq) - max_seq \u003e MIN_NR_GENS))\n+\t\t\tfolio_activate(folio);\n \t}\n \n-\t/* Promote on second access */\n-\tif (folio_lru_refs(folio) \u003e 1)\n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_FLAGS, BIT(PG_workingset));\n-\telse\n-\t\tfolio_mark_accessed(folio);\n-\treturn true;\n+\tfolio_lruvec_live_put(lruvec);\n+\treturn refs;\n+}\n+\n+/*\n+ * Update the folio's lru refs indicator during a page table walk.\n+ * max_seq is stable since this runs inside the aging process.\n+ *\n+ * Returns the old generation and stores the new generation in @new_gen when\n+ * the folio is promoted (to max_gen) or advanced by one generation.\n+ * Returns -1 if no gen change occurred.\n+ */\n+static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lruvec,\n+\t\t\t\t const vma_flags_t *vma_flags, int *new_gen)\n+{\n+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n+\tunsigned long max_seq = READ_ONCE(lruvec-\u003elrugen.max_seq);\n+\tint refs, gen, max_gen, ret;\n+\n+\tmax_gen = lru_gen_from_seq(max_seq);\n+\n+\tdo {\n+\t\tgen = lru_gen_from_flags(old_flags);\n+\t\trefs = lru_refs_from_flags(old_flags) + 1;\n+\t\tnew_flags = old_flags;\n+\n+\t\tif (gen \u003e= 0 \u0026\u0026 gen != max_gen) {\n+\t\t\tret = gen;\n+\t\t\t/* Promote second page table access or executable */\n+\t\t\tif (refs \u003e LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags))\n+\t\t\t\t*new_gen = max_gen;\n+\t\t\telse\n+\t\t\t\t*new_gen = (gen + 1) % MAX_NR_GENS;\n+\t\t\tlru_gen_set_flags(\u0026new_flags, *new_gen);\n+\t\t\tlru_refs_set_flags(\u0026new_flags, min(refs, LRU_REFS_PROTECTED));\n+\t\t} else {\n+\t\t\tret = -1;\n+\t\t\tlru_refs_set_flags(\u0026new_flags, min(refs, LRU_REFS_MAX));\n+\t\t}\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Update the folio's lru refs indicator while the folio is isolated.\n+ * Only used on mapped folios upon the final eviction, when the folio is\n+ * off the LRU list (isolated).\n+ *\n+ * Increments the refs count (capped at LRU_REFS_PROTECTED). Returns true\n+ * if the caller should activate the folio (second access or\n+ * executable), false to keep it in the eviction list.\n+ */\n+static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)\n+{\n+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n+\tint refs;\n+\n+\tdo {\n+\t\tnew_flags = old_flags;\n+\t\trefs = lru_refs_from_flags(old_flags) + 1;\n+\t\tlru_refs_set_flags(\u0026new_flags, min(refs, LRU_REFS_PROTECTED));\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n+\n+\t/* Promote second page table access or executable */\n+\treturn refs \u003e LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags);\n }\n #else\n-static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags)\n+static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)\n {\n \treturn false;\n }\n@@ -893,7 +1035,8 @@ static enum folio_references folio_check_references(struct folio *folio,\n \t\tif (!referenced_ptes)\n \t\t\treturn FOLIOREF_RECLAIM;\n \n-\t\treturn lru_gen_set_refs(folio, \u0026vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP;\n+\t\treturn folio_inc_lru_refs_isolated(folio, \u0026vma_flags) ?\n+\t\t FOLIOREF_ACTIVATE : FOLIOREF_KEEP;\n \t}\n \n \treferenced_folio = folio_test_clear_referenced(folio);\n@@ -1636,10 +1779,6 @@ unsigned int reclaim_clean_pages_from_list(struct zone *zone,\n \treturn nr_reclaimed;\n }\n \n-/*\n- * Update LRU sizes after isolating pages. The LRU size updates must\n- * be complete before mem_cgroup_update_lru_size due to a sanity check.\n- */\n static __always_inline void update_lru_sizes(struct lruvec *lruvec,\n \t\t\tenum lru_list lru, unsigned long *nr_zone_taken)\n {\n@@ -1651,7 +1790,6 @@ static __always_inline void update_lru_sizes(struct lruvec *lruvec,\n \n \t\tupdate_lru_size(lruvec, lru, zid, -nr_zone_taken[zid]);\n \t}\n-\n }\n \n /*\n@@ -2130,7 +2268,7 @@ static void shrink_active_list(unsigned long nr_to_scan,\n \t\t}\n \n \t\tfolio_clear_active(folio);\t/* we are de-activating */\n-\t\tfolio_set_workingset(folio);\n+\t\tfolio_mark_workingset_by_bit(folio);\n \t\tlist_add(\u0026folio-\u003elru, \u0026l_inactive);\n \t}\n \n@@ -3194,8 +3332,8 @@ struct ctrl_pos {\n \tint gain;\n };\n \n-static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,\n-\t\t\t struct ctrl_pos *pos)\n+static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier_min,\n+\t\t\t int tier_max, int gain, struct ctrl_pos *pos)\n {\n \tint i;\n \tstruct lru_gen_folio *lrugen = \u0026lruvec-\u003elrugen;\n@@ -3204,7 +3342,7 @@ static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,\n \tpos-\u003egain = gain;\n \tpos-\u003erefaulted = pos-\u003etotal = 0;\n \n-\tfor (i = tier % MAX_NR_TIERS; i \u003c= min(tier, MAX_NR_TIERS - 1); i++) {\n+\tfor (i = tier_min; i \u003c tier_max; i++) {\n \t\tpos-\u003erefaulted += lrugen-\u003eavg_refaulted[type][i] +\n \t\t\t\t atomic_long_read(\u0026lrugen-\u003erefaulted[hist][type][i]);\n \t\tpos-\u003etotal += lrugen-\u003eavg_total[type][i] +\n@@ -3264,57 +3402,32 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)\n * the aging\n ******************************************************************************/\n \n-/* promote pages accessed through page tables */\n-static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags)\n-{\n-\tunsigned long new_flags, old_flags = READ_ONCE(folio-\u003eflags.f);\n-\n-\tVM_WARN_ON_ONCE(gen \u003e= MAX_NR_GENS);\n-\n-\t/*\n-\t * See the comment on LRU_REFS_FLAGS, and activate file-backed\n-\t * executable folios after first usage to avoid typical IO\n-\t * thrashing from reclaiming.\n-\t */\n-\tif (!folio_test_referenced(folio) \u0026\u0026 !folio_test_workingset(folio) \u0026\u0026\n-\t !is_exec_file_folio(folio, vma_flags)) {\n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_MASK, BIT(PG_referenced));\n-\t\treturn -1;\n-\t}\n-\n-\tdo {\n-\t\t/* lru_gen_del_folio() has isolated this page? */\n-\t\tif (!(old_flags \u0026 LRU_GEN_MASK))\n-\t\t\treturn -1;\n-\n-\t\tnew_flags = old_flags \u0026 ~(LRU_GEN_MASK | LRU_REFS_FLAGS);\n-\t\tnew_flags |= ((gen + 1UL) \u003c\u003c LRU_GEN_PGOFF) | BIT(PG_workingset);\n-\t} while (!try_cmpxchg(\u0026folio-\u003eflags.f, \u0026old_flags, new_flags));\n-\n-\treturn ((old_flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF) - 1;\n-}\n-\n-/* protect pages accessed multiple times through file descriptors */\n+/*\n+ * Force bump a folio's generation. Used for PID protection or defer the\n+ * eviction of temporarily unevictable folio.\n+ */\n static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)\n {\n+\tint refs;\n \tint type = folio_is_file_lru(folio);\n \tstruct lru_gen_folio *lrugen = \u0026lruvec-\u003elrugen;\n-\tint new_gen, old_gen = lru_gen_from_seq(lrugen-\u003emin_seq[type]);\n-\tunsigned long new_flags, old_flags = READ_ONCE(folio-\u003eflags.f);\n-\n-\tVM_WARN_ON_ONCE_FOLIO(!(old_flags \u0026 LRU_GEN_MASK), folio);\n+\tint old_gen, new_gen, min_gen = lru_gen_from_seq(lrugen-\u003emin_seq[type]);\n+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n \n \tdo {\n-\t\tnew_gen = ((old_flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF) - 1;\n-\t\t/* folio_update_gen() has promoted this page? */\n-\t\tif (new_gen \u003e= 0 \u0026\u0026 new_gen != old_gen)\n-\t\t\treturn new_gen;\n+\t\tnew_flags = old_flags;\n+\t\trefs = lru_refs_from_flags(old_flags);\n+\t\told_gen = lru_gen_from_flags(old_flags);\n+\t\tVM_WARN_ON_ONCE_FOLIO(old_gen \u003c 0, folio);\n \n-\t\tnew_gen = (old_gen + 1) % MAX_NR_GENS;\n+\t\t/* folio has been promoted? */\n+\t\tif (old_gen \u003e= 0 \u0026\u0026 old_gen != min_gen)\n+\t\t\treturn old_gen;\n \n-\t\tnew_flags = old_flags \u0026 ~(LRU_GEN_MASK | LRU_REFS_FLAGS);\n-\t\tnew_flags |= (new_gen + 1UL) \u003c\u003c LRU_GEN_PGOFF;\n-\t} while (!try_cmpxchg(\u0026folio-\u003eflags.f, \u0026old_flags, new_flags));\n+\t\tnew_gen = (old_gen + 1) % MAX_NR_GENS;\n+\t\tlru_gen_set_flags(\u0026new_flags, new_gen);\n+\t\tlru_refs_set_flags(\u0026new_flags, min(refs, LRU_REFS_WORKINGSET));\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n \n \tlru_gen_update_size(lruvec, folio, old_gen, new_gen);\n \n@@ -3353,8 +3466,7 @@ static void reset_batch_size(struct lru_gen_mm_walk *walk)\n \t\t\tcontinue;\n \n \t\twalk-\u003enr_pages[gen][type][zone] = 0;\n-\t\tWRITE_ONCE(lrugen-\u003enr_pages[gen][type][zone],\n-\t\t\t lrugen-\u003enr_pages[gen][type][zone] + delta);\n+\t\tatomic_long_add(delta, \u0026lrugen-\u003enr_pages[gen][type][zone]);\n \n \t\tif (lru_gen_is_active(lruvec, gen))\n \t\t\tlru += LRU_ACTIVE;\n@@ -3511,9 +3623,9 @@ static bool suitable_to_scan(int total, int young)\n }\n \n static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma,\n-\t\tstruct folio *folio, int new_gen, bool dirty)\n+\t\t\t struct lruvec *lruvec, struct folio *folio, bool dirty)\n {\n-\tint old_gen;\n+\tint new_gen, old_gen;\n \n \tif (!folio)\n \t\treturn;\n@@ -3524,13 +3636,11 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struc\n \t\tfolio_mark_dirty(folio);\n \n \tif (walk) {\n-\t\told_gen = folio_update_gen(folio, new_gen, \u0026vma-\u003eflags);\n-\t\tif (old_gen \u003e= 0 \u0026\u0026 old_gen != new_gen)\n+\t\told_gen = folio_inc_lru_refs_walk(folio, lruvec, \u0026vma-\u003eflags, \u0026new_gen);\n+\t\tif (old_gen \u003e= 0)\n \t\t\tupdate_batch_size(walk, folio, old_gen, new_gen);\n-\t} else if (lru_gen_set_refs(folio, \u0026vma-\u003eflags)) {\n-\t\told_gen = folio_lru_gen(folio);\n-\t\tif (old_gen \u003e= 0 \u0026\u0026 old_gen != new_gen)\n-\t\t\tfolio_activate(folio);\n+\t} else {\n+\t\tfolio_inc_lru_refs(folio, true, is_exec_file_folio(folio, \u0026vma-\u003eflags));\n \t}\n }\n \n@@ -3548,8 +3658,6 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,\n \tstruct lru_gen_mm_walk *walk = args-\u003eprivate;\n \tstruct mem_cgroup *memcg = lruvec_memcg(walk-\u003elruvec);\n \tstruct pglist_data *pgdat = lruvec_pgdat(walk-\u003elruvec);\n-\tDEFINE_MAX_SEQ(walk-\u003elruvec);\n-\tint gen = lru_gen_from_seq(max_seq);\n \tunsigned int nr;\n \tpmd_t pmdval;\n \n@@ -3600,7 +3708,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,\n \t\t\tcontinue;\n \n \t\tif (last != folio) {\n-\t\t\twalk_update_folio(walk, args-\u003evma, last, gen, dirty);\n+\t\t\twalk_update_folio(walk, args-\u003evma, walk-\u003elruvec, last, dirty);\n \n \t\t\tlast = folio;\n \t\t\tdirty = false;\n@@ -3613,7 +3721,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,\n \t\twalk-\u003emm_stats[MM_LEAF_YOUNG] += nr;\n \t}\n \n-\twalk_update_folio(walk, args-\u003evma, last, gen, dirty);\n+\twalk_update_folio(walk, args-\u003evma, walk-\u003elruvec, last, dirty);\n \tlast = NULL;\n \n \tif (i \u003c PTRS_PER_PTE \u0026\u0026 get_next_vma(PMD_MASK, PAGE_SIZE, args, \u0026start, \u0026end))\n@@ -3636,8 +3744,6 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area\n \tstruct lru_gen_mm_walk *walk = args-\u003eprivate;\n \tstruct mem_cgroup *memcg = lruvec_memcg(walk-\u003elruvec);\n \tstruct pglist_data *pgdat = lruvec_pgdat(walk-\u003elruvec);\n-\tDEFINE_MAX_SEQ(walk-\u003elruvec);\n-\tint gen = lru_gen_from_seq(max_seq);\n \n \tVM_WARN_ON_ONCE(pud_leaf(*pud));\n \n@@ -3691,7 +3797,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area\n \t\t\tgoto next;\n \n \t\tif (last != folio) {\n-\t\t\twalk_update_folio(walk, vma, last, gen, dirty);\n+\t\t\twalk_update_folio(walk, vma, walk-\u003elruvec, last, dirty);\n \n \t\t\tlast = folio;\n \t\t\tdirty = false;\n@@ -3705,7 +3811,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area\n \t\ti = i \u003e MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1;\n \t} while (i \u003c= MIN_LRU_BATCH);\n \n-\twalk_update_folio(walk, vma, last, gen, dirty);\n+\twalk_update_folio(walk, vma, walk-\u003elruvec, last, dirty);\n \n \tlazy_mmu_mode_disable();\n \tspin_unlock(ptl);\n@@ -3919,7 +4025,8 @@ static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness)\n \t\twhile (!list_empty(head)) {\n \t\t\tstruct folio *folio = lru_to_folio(head);\n \t\t\tint refs = folio_lru_refs(folio);\n-\t\t\tbool workingset = folio_test_workingset(folio);\n+\t\t\tint delta = folio_nr_pages(folio);\n+\t\t\tint tier = lru_tier_from_refs(refs);\n \n \t\t\tVM_WARN_ON_ONCE_FOLIO(folio_test_unevictable(folio), folio);\n \t\t\tVM_WARN_ON_ONCE_FOLIO(folio_test_active(folio), folio);\n@@ -3929,14 +4036,8 @@ static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness)\n \t\t\tnew_gen = folio_inc_gen(lruvec, folio);\n \t\t\tlist_move_tail(\u0026folio-\u003elru, \u0026lrugen-\u003efolios[new_gen][type][zone]);\n \n-\t\t\t/* don't count the workingset being lazily promoted */\n-\t\t\tif (refs + workingset != BIT(LRU_REFS_WIDTH) + 1) {\n-\t\t\t\tint tier = lru_tier_from_refs(refs, workingset);\n-\t\t\t\tint delta = folio_nr_pages(folio);\n-\n-\t\t\t\tWRITE_ONCE(lrugen-\u003eprotected[hist][type][tier],\n-\t\t\t\t\t lrugen-\u003eprotected[hist][type][tier] + delta);\n-\t\t\t}\n+\t\t\tWRITE_ONCE(lrugen-\u003eprotected[hist][type][tier],\n+\t\t\t\t lrugen-\u003eprotected[hist][type][tier] + delta);\n \n \t\t\tif (!--remaining)\n \t\t\t\treturn false;\n@@ -4043,8 +4144,8 @@ static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness\n \tfor (type = 0; type \u003c ANON_AND_FILE; type++) {\n \t\tfor (zone = 0; zone \u003c MAX_NR_ZONES; zone++) {\n \t\t\tenum lru_list lru = type * LRU_INACTIVE_FILE;\n-\t\t\tlong delta = lrugen-\u003enr_pages[prev][type][zone] -\n-\t\t\t\t lrugen-\u003enr_pages[next][type][zone];\n+\t\t\tlong delta = atomic_long_read(\u0026lrugen-\u003enr_pages[prev][type][zone]) -\n+\t\t\t\t atomic_long_read(\u0026lrugen-\u003enr_pages[next][type][zone]);\n \n \t\t\tif (!delta)\n \t\t\t\tcontinue;\n@@ -4162,7 +4263,8 @@ static unsigned long lruvec_evictable_size(struct lruvec *lruvec, int swappiness\n \t\tfor (seq = min_seq[type]; seq \u003c= max_seq; seq++) {\n \t\t\tgen = lru_gen_from_seq(seq);\n \t\t\tfor (zone = 0; zone \u003c MAX_NR_ZONES; zone++)\n-\t\t\t\ttotal += max(READ_ONCE(lrugen-\u003enr_pages[gen][type][zone]), 0L);\n+\t\t\t\ttotal += max(atomic_long_read(\u0026lrugen-\u003enr_pages[gen][type][zone]),\n+\t\t\t\t\t 0L);\n \t\t}\n \t}\n \n@@ -4269,8 +4371,6 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\n \tstruct pglist_data *pgdat = folio_pgdat(folio);\n \tstruct lruvec *lruvec;\n \tstruct lru_gen_mm_state *mm_state;\n-\tunsigned long max_seq;\n-\tint gen;\n \n \tlockdep_assert_held(pvmw-\u003eptl);\n \tVM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);\n@@ -4307,8 +4407,6 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\n \n \tmemcg = get_mem_cgroup_from_folio(folio);\n \tlruvec = mem_cgroup_lruvec(memcg, pgdat);\n-\tmax_seq = READ_ONCE((lruvec)-\u003elrugen.max_seq);\n-\tgen = lru_gen_from_seq(max_seq);\n \tmm_state = get_mm_state(lruvec);\n \n \tlazy_mmu_mode_enable();\n@@ -4340,7 +4438,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\n \t\t\tcontinue;\n \n \t\tif (last != folio) {\n-\t\t\twalk_update_folio(walk, vma, last, gen, dirty);\n+\t\t\twalk_update_folio(walk, vma, lruvec, last, dirty);\n \n \t\t\tlast = folio;\n \t\t\tdirty = false;\n@@ -4352,13 +4450,14 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\n \t\tyoung += nr;\n \t}\n \n-\twalk_update_folio(walk, vma, last, gen, dirty);\n+\twalk_update_folio(walk, vma, lruvec, last, dirty);\n \n \tlazy_mmu_mode_disable();\n \n \t/* feedback from rmap walkers to page table walkers */\n \tif (mm_state \u0026\u0026 suitable_to_scan(i, young))\n-\t\tupdate_bloom_filter(mm_state, max_seq, pvmw-\u003epmd);\n+\t\tupdate_bloom_filter(mm_state, READ_ONCE(lruvec-\u003elrugen.max_seq),\n+\t\t\t\t pvmw-\u003epmd);\n \n \tmem_cgroup_put(memcg);\n \n@@ -4597,7 +4696,7 @@ static void __lru_gen_reparent_memcg(struct lruvec *child_lruvec, struct lruvec\n \n \tfor (i = 0; i \u003c get_nr_gens(child_lruvec, type); i++) {\n \t\tint gen = lru_gen_from_seq(child_lrugen-\u003emax_seq - i);\n-\t\tlong nr_pages = child_lrugen-\u003enr_pages[gen][type][zone];\n+\t\tlong nr_pages = atomic_long_read(\u0026child_lrugen-\u003enr_pages[gen][type][zone]);\n \t\tint child_lru_active = lru_gen_is_active(child_lruvec, gen) ? LRU_ACTIVE : 0;\n \t\tint parent_lru_active = lru_gen_is_active(parent_lruvec, gen) ? LRU_ACTIVE : 0;\n \n@@ -4605,9 +4704,8 @@ static void __lru_gen_reparent_memcg(struct lruvec *child_lruvec, struct lruvec\n \t\tlist_splice_tail_init(\u0026child_lrugen-\u003efolios[gen][type][zone],\n \t\t\t\t \u0026parent_lrugen-\u003efolios[gen][type][zone]);\n \n-\t\tWRITE_ONCE(child_lrugen-\u003enr_pages[gen][type][zone], 0);\n-\t\tWRITE_ONCE(parent_lrugen-\u003enr_pages[gen][type][zone],\n-\t\t\t parent_lrugen-\u003enr_pages[gen][type][zone] + nr_pages);\n+\t\tatomic_long_set(\u0026child_lrugen-\u003enr_pages[gen][type][zone], 0);\n+\t\tatomic_long_add(nr_pages, \u0026parent_lrugen-\u003enr_pages[gen][type][zone]);\n \n \t\tif (lru_gen_is_active(child_lruvec, gen) != lru_gen_is_active(parent_lruvec, gen)) {\n \t\t\t__update_lru_size(child_lruvec, lru + child_lru_active, zone, -nr_pages);\n@@ -4654,8 +4752,7 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c\n \tint zone = folio_zonenum(folio);\n \tint delta = folio_nr_pages(folio);\n \tint refs = folio_lru_refs(folio);\n-\tbool workingset = folio_test_workingset(folio);\n-\tint tier = lru_tier_from_refs(refs, workingset);\n+\tint tier = lru_tier_from_refs(refs);\n \tstruct lru_gen_folio *lrugen = \u0026lruvec-\u003elrugen;\n \n \tVM_WARN_ON_ONCE_FOLIO(gen \u003e= MAX_NR_GENS, folio);\n@@ -4677,17 +4774,15 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c\n \t}\n \n \t/* protected */\n-\tif (tier \u003e tier_idx || refs + workingset == BIT(LRU_REFS_WIDTH) + 1) {\n+\tif (tier \u003e tier_idx) {\n+\t\tint hist = lru_hist_from_seq(lrugen-\u003emin_seq[type]);\n+\n \t\tgen = folio_inc_gen(lruvec, folio);\n \t\tlist_move(\u0026folio-\u003elru, \u0026lrugen-\u003efolios[gen][type][zone]);\n \n-\t\t/* don't count the workingset being lazily promoted */\n-\t\tif (refs + workingset != BIT(LRU_REFS_WIDTH) + 1) {\n-\t\t\tint hist = lru_hist_from_seq(lrugen-\u003emin_seq[type]);\n+\t\tWRITE_ONCE(lrugen-\u003eprotected[hist][type][tier],\n+\t\t\t lrugen-\u003eprotected[hist][type][tier] + delta);\n \n-\t\t\tWRITE_ONCE(lrugen-\u003eprotected[hist][type][tier],\n-\t\t\t\t lrugen-\u003eprotected[hist][type][tier] + delta);\n-\t\t}\n \t\treturn true;\n \t}\n \n@@ -4715,10 +4810,6 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca\n \t\treturn false;\n \t}\n \n-\t/* see the comment on LRU_REFS_FLAGS */\n-\tif (!folio_test_referenced(folio))\n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_MASK, 0);\n-\n \tsuccess = lru_gen_del_folio(lruvec, folio, true);\n \tVM_WARN_ON_ONCE_FOLIO(!success, folio);\n \n@@ -4806,13 +4897,13 @@ static int get_tier_idx(struct lruvec *lruvec, int type)\n \tstruct ctrl_pos sp, pv = {};\n \n \t/*\n-\t * To leave a margin for fluctuations, use a larger gain factor (2:3).\n+\t * To leave a margin for fluctuations, use a larger gain factor (1:2).\n \t * This value is chosen because any other tier would have at least twice\n \t * as many refaults as the first tier.\n \t */\n-\tread_ctrl_pos(lruvec, type, 0, 2, \u0026sp);\n \tfor (tier = 1; tier \u003c MAX_NR_TIERS; tier++) {\n-\t\tread_ctrl_pos(lruvec, type, tier, 3, \u0026pv);\n+\t\tread_ctrl_pos(lruvec, type, 0, tier, 1, \u0026sp);\n+\t\tread_ctrl_pos(lruvec, type, tier, tier + 1, 2, \u0026pv);\n \t\tif (!positive_ctrl_err(\u0026sp, \u0026pv))\n \t\t\tbreak;\n \t}\n@@ -4833,8 +4924,8 @@ static int get_type_to_scan(struct lruvec *lruvec, int swappiness)\n \t * Compare the sum of all tiers of anon with that of file to determine\n \t * which type to scan.\n \t */\n-\tread_ctrl_pos(lruvec, LRU_GEN_ANON, MAX_NR_TIERS, swappiness, \u0026sp);\n-\tread_ctrl_pos(lruvec, LRU_GEN_FILE, MAX_NR_TIERS, MAX_SWAPPINESS - swappiness, \u0026pv);\n+\tread_ctrl_pos(lruvec, LRU_GEN_ANON, 0, MAX_NR_TIERS, swappiness, \u0026sp);\n+\tread_ctrl_pos(lruvec, LRU_GEN_FILE, 0, MAX_NR_TIERS, MAX_SWAPPINESS - swappiness, \u0026pv);\n \n \treturn positive_ctrl_err(\u0026sp, \u0026pv);\n }\n@@ -4936,7 +5027,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n \n \t\t/* don't add rejected folios to the oldest generation */\n \t\tif (lru_gen_folio_seq(lruvec, folio, false) == min_seq[type])\n-\t\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_FLAGS, BIT(PG_active));\n+\t\t\tfolio_set_active(folio);\n \t}\n \n \tmove_folios_to_lru(\u0026list);\n@@ -5647,7 +5738,8 @@ static int lru_gen_seq_show(struct seq_file *m, void *v)\n \t\t\tchar mark = full \u0026\u0026 seq \u003c min_seq[type] ? 'x' : ' ';\n \n \t\t\tfor (zone = 0; zone \u003c MAX_NR_ZONES; zone++)\n-\t\t\t\tsize += max(READ_ONCE(lrugen-\u003enr_pages[gen][type][zone]), 0L);\n+\t\t\t\tsize += max(atomic_long_read(\u0026lrugen-\u003enr_pages[gen][type][zone]),\n+\t\t\t\t\t 0L);\n \n \t\t\tseq_printf(m, \" %10lu%c\", size, mark);\n \t\t}\ndiff --git a/mm/workingset.c b/mm/workingset.c\nindex 7ac2b88c80ae5..568a3e44cd5e8 100644\n--- a/mm/workingset.c\n+++ b/mm/workingset.c\n@@ -189,6 +189,13 @@\n #define EVICTION_MASK\t(~0UL \u003e\u003e EVICTION_SHIFT)\n #define EVICTION_MASK_ANON\t(~0UL \u003e\u003e EVICTION_SHIFT_ANON)\n \n+/*\n+ * LRU refs uses LRU_REFS_WIDTH + 2 bits, the 2 bits are PG_workingset and\n+ * PG_referenced. But here we record PG_workingset separately (to reuse\n+ * pack_shadow).\n+ */\n+#define LRU_REFS_BITS ((LRU_REFS_WIDTH + 2) - 1)\n+\n /*\n * Eviction timestamps need to be able to cover the full range of\n * actionable refaults. However, bits are tight in the xarray\n@@ -242,13 +249,12 @@ static void *lru_gen_eviction(struct folio *folio)\n \tint type = folio_is_file_lru(folio);\n \tint delta = folio_nr_pages(folio);\n \tint refs = folio_lru_refs(folio);\n-\tbool workingset = folio_test_workingset(folio);\n-\tint tier = lru_tier_from_refs(refs, workingset);\n+\tint tier = lru_tier_from_refs(refs);\n \tstruct mem_cgroup *memcg;\n \tstruct pglist_data *pgdat = folio_pgdat(folio);\n \tunsigned short memcg_id;\n \n-\tBUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_WIDTH \u003e\n+\tBUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_BITS \u003e\n \t\t BITS_PER_LONG - max(EVICTION_SHIFT, EVICTION_SHIFT_ANON));\n \n \trcu_read_lock();\n@@ -256,14 +262,14 @@ static void *lru_gen_eviction(struct folio *folio)\n \tlruvec = mem_cgroup_lruvec(memcg, pgdat);\n \tlrugen = \u0026lruvec-\u003elrugen;\n \tmin_seq = READ_ONCE(lrugen-\u003emin_seq[type]);\n-\ttoken = (min_seq \u003c\u003c LRU_REFS_WIDTH) | max(refs - 1, 0);\n+\ttoken = (min_seq \u003c\u003c LRU_REFS_BITS) | refs \u003e\u003e 1;\n \n \thist = lru_hist_from_seq(min_seq);\n \tatomic_long_add(delta, \u0026lrugen-\u003eevicted[hist][type][tier]);\n \tmemcg_id = mem_cgroup_private_id(memcg);\n \trcu_read_unlock();\n \n-\treturn pack_shadow(memcg_id, pgdat, token, workingset, type);\n+\treturn pack_shadow(memcg_id, pgdat, token, refs \u0026 1, type);\n }\n \n /*\n@@ -284,11 +290,24 @@ static bool lru_gen_test_recent(void *shadow, struct lruvec **lruvec,\n \t*lruvec = mem_cgroup_lruvec(memcg, pgdat);\n \n \tmax_seq = READ_ONCE((*lruvec)-\u003elrugen.max_seq);\n-\tmax_seq \u0026= (file ? EVICTION_MASK : EVICTION_MASK_ANON) \u003e\u003e LRU_REFS_WIDTH;\n+\tmax_seq \u0026= (file ? EVICTION_MASK : EVICTION_MASK_ANON) \u003e\u003e LRU_REFS_BITS;\n \n-\treturn abs_diff(max_seq, *token \u003e\u003e LRU_REFS_WIDTH) \u003c MAX_NR_GENS;\n+\treturn abs_diff(max_seq, *token \u003e\u003e LRU_REFS_BITS) \u003c MAX_NR_GENS;\n }\n \n+/*\n+ * Restore the refs of a refaulted folio from its shadow entry.\n+ *\n+ * Any folio that was accessed at least once before eviction (refs \u003e=\n+ * LRU_REFS_REFERENCED) is activated on a fault-driven refault, giving it a\n+ * strong gen placement. Non-fault refaults (e.g. readahead) are not\n+ * activated regardless of refs.\n+ *\n+ * The restored refs is capped at LRU_REFS_PROTECTED to prevent stale\n+ * high-tier history from carrying over across eviction cycles. The\n+ * WORKINGSET_RESTORE stat is bumped only for refs \u003e= LRU_REFS_WORKINGSET\n+ * to track genuine workingset restoration.\n+ */\n static void lru_gen_refault(struct folio *folio, void *shadow)\n {\n \tbool recent;\n@@ -314,22 +333,29 @@ static void lru_gen_refault(struct folio *folio, void *shadow)\n \tlrugen = \u0026lruvec-\u003elrugen;\n \n \thist = lru_hist_from_seq(READ_ONCE(lrugen-\u003emin_seq[type]));\n-\trefs = (token \u0026 (BIT(LRU_REFS_WIDTH) - 1)) + 1;\n-\ttier = lru_tier_from_refs(refs, workingset);\n+\trefs = ((token \u0026 (BIT(LRU_REFS_BITS) - 1)) \u003c\u003c 1) + workingset;\n+\ttier = lru_tier_from_refs(refs);\n \n \tatomic_long_add(delta, \u0026lrugen-\u003erefaulted[hist][type][tier]);\n \n-\tif (workingset) {\n-\t\t/*\n-\t\t * see folio_add_lru(), where folio_set_active() is\n-\t\t * called for workingset folios\n-\t\t */\n-\t\tif (lru_gen_in_fault())\n+\t/*\n+\t * Activate a fault-driven refault: the folio was accessed at\n+\t * least once before eviction and would have been promoted had\n+\t * it stayed in memory.\n+\t */\n+\tif (refs \u003e= LRU_REFS_REFERENCED) {\n+\t\tif (lru_gen_in_fault()) {\n+\t\t\tfolio_set_active(folio);\n \t\t\tmod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + type, delta);\n-\t\tfolio_set_workingset(folio);\n+\t\t}\n+\t\t/* Cap restored refs to prevent stale high-tier carry-over */\n+\t\tfolio_set_lru_refs(folio, min(refs, LRU_REFS_PROTECTED));\n+\t}\n+\n+\t/* WORKINGSET_RESTORE tracks genuine workingset-level refaults */\n+\tif (refs \u003e= LRU_REFS_WORKINGSET)\n \t\tmod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + type, delta);\n-\t} else\n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_MASK, (refs - 1UL) \u003c\u003c LRU_REFS_PGOFF);\n+\n unlock:\n \trcu_read_unlock();\n }\n@@ -412,7 +438,7 @@ void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg)\n \teviction \u003e\u003e= bucket_order[file];\n \tworkingset_age_nonresident(lruvec, folio_nr_pages(folio));\n \treturn pack_shadow(memcgid, pgdat, eviction,\n-\t\t\t folio_test_workingset(folio), file);\n+\t\t\t folio_is_workingset(folio), file);\n }\n \n /**\n@@ -583,7 +609,7 @@ void workingset_refault(struct folio *folio, void *shadow)\n \n \t/* Folio was active prior to eviction */\n \tif (workingset) {\n-\t\tfolio_set_workingset(folio);\n+\t\tfolio_mark_workingset_by_bit(folio);\n \t\tmod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr);\n \t}\n out:\n"
} |
| 2/1 | 2026/08/03 21:26 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG",
"TRANSPARENT_HUGEPAGE",
"SWAP"
],
"FocusSymbols": [
"folio_inc_lru_refs",
"lru_gen_look_around",
"workingset_refault",
"workingset_eviction",
"swap_read_folio",
"__split_huge_pmd",
"madvise_collapse",
"do_madvise"
],
"Reasoning": "This patch introduces a new LRU reference tracking mechanism for both MGLRU and non-MGLRU, replacing PG_referenced and PG_workingset with a multi-bit reference count. It modifies core memory management paths including page fault handling, page cache read-ahead, swap, THP splitting, and madvise behaviors. The changes are highly functional and affect the core page reclaim and aging logic, making it worth fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit e6f118540b92cb2dd2b6d3c07a4d82301148a6cf
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 3 21:26:32 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index ffb6b52863a78..e756403e8bd5d 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -21,6 +21,7 @@
#include <linux/sched/mm.h>
#include <linux/log2.h>
#include <linux/shrinker.h>
+#include <linux/mm_inline.h>
#include "misc.h"
#include "ctree.h"
#include "fs.h"
@@ -448,7 +449,7 @@ static noinline int add_ra_bio_folios(struct inode *inode, u64 compressed_end,
continue;
}
- if (!*memstall && folio_test_workingset(folio)) {
+ if (!*memstall && folio_is_workingset(folio)) {
psi_memstall_enter(pflags);
*memstall = 1;
}
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 817e3e0f91943..c5c96c5232918 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -944,6 +944,22 @@ static void smaps_page_accumulate(struct mem_size_stats *mss,
}
}
+static bool smap_check_folio_referenced(struct folio *folio)
+{
+ if (lru_gen_enabled())
+ return folio_is_referenced(folio);
+ else
+ return folio_is_referenced_by_bit(folio);
+}
+
+static void smap_clear_folio_referenced(struct folio *folio)
+{
+ if (lru_gen_enabled())
+ folio_set_lru_refs(folio, 0);
+ else
+ folio_clear_referenced_by_bit(folio);
+}
+
static void smaps_account(struct mem_size_stats *mss, struct page *page,
bool compound, bool young, bool dirty, bool locked,
bool present)
@@ -970,7 +986,7 @@ static void smaps_account(struct mem_size_stats *mss, struct page *page,
mss->resident += size;
/* Accumulate the size in pages that have been accessed. */
- if (young || folio_test_young(folio) || folio_test_referenced(folio))
+ if (young || folio_test_young(folio) || smap_check_folio_referenced(folio))
mss->referenced += size;
/*
@@ -1791,7 +1807,7 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,
/* Clear accessed and referenced bits. */
pmdp_test_and_clear_young(vma, addr, pmd);
folio_test_clear_young(folio);
- folio_clear_referenced(folio);
+ smap_clear_folio_referenced(folio);
out:
spin_unlock(ptl);
return 0;
@@ -1820,7 +1836,7 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,
/* Clear accessed and referenced bits. */
ptep_test_and_clear_young(vma, addr, pte);
folio_test_clear_young(folio);
- folio_clear_referenced(folio);
+ smap_clear_folio_referenced(folio);
}
pte_unmap_unlock(pte - 1, ptl);
cond_resched();
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index e78bc98ab229b..ea0111392b9bc 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -113,7 +113,7 @@ struct mem_cgroup_per_node {
/* Fields which get updated often at the end. */
struct lruvec lruvec;
CACHELINE_PADDING(_pad2_);
- unsigned long lru_zone_size[MAX_NR_ZONES][NR_LRU_LISTS];
+ atomic_long_t lru_zone_size[MAX_NR_ZONES][NR_LRU_LISTS];
struct mem_cgroup_reclaim_iter iter;
/*
@@ -897,10 +897,15 @@ static inline
unsigned long mem_cgroup_get_zone_lru_size(struct lruvec *lruvec,
enum lru_list lru, int zone_idx)
{
+ long val;
struct mem_cgroup_per_node *mz;
mz = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
- return READ_ONCE(mz->lru_zone_size[zone_idx][lru]);
+ val = atomic_long_read(&mz->lru_zone_size[zone_idx][lru]);
+ if (val < 0)
+ return 0;
+
+ return val;
}
void __mem_cgroup_handle_over_high(gfp_t gfp_mask);
@@ -1501,6 +1506,44 @@ static inline void lruvec_lock_irq(struct lruvec *lruvec)
spin_lock_irq(&lruvec->lru_lock);
}
+/**
+ * folio_lruvec_live_get - get a live lruvec for a folio under RCU
+ * @folio: the folio
+ *
+ * Computes @folio's lruvec and walks up to the nearest live ancestor
+ * if the folio's memcg is dying. Must be paired with
+ * folio_lruvec_live_put().
+ *
+ * Return: the live lruvec, with rcu_read_lock held.
+ */
+static inline struct lruvec *folio_lruvec_live_get(struct folio *folio)
+{
+#ifdef CONFIG_MEMCG
+ struct lruvec *lruvec;
+ struct pglist_data *pgdat;
+ struct mem_cgroup *memcg;
+
+ rcu_read_lock();
+ lruvec = folio_lruvec(folio);
+ pgdat = lruvec_pgdat(lruvec);
+ memcg = lruvec_memcg(lruvec);
+ while (unlikely(memcg && css_is_dying(&memcg->css))) {
+ memcg = parent_mem_cgroup(memcg);
+ lruvec = mem_cgroup_lruvec(memcg, pgdat);
+ }
+ return lruvec;
+#else
+ return folio_lruvec(folio);
+#endif
+}
+
+static inline void folio_lruvec_live_put(struct lruvec *lruvec)
+{
+#ifdef CONFIG_MEMCG
+ rcu_read_unlock();
+#endif
+}
+
static inline struct lruvec *lruvec_live_lock_irq(struct lruvec *lruvec)
{
#ifdef CONFIG_MEMCG
diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
index 621c8653d8f7e..a13b7d3c033a8 100644
--- a/include/linux/mm_inline.h
+++ b/include/linux/mm_inline.h
@@ -36,11 +36,10 @@ static __always_inline void __update_lru_size(struct lruvec *lruvec,
{
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
- lockdep_assert_held(&lruvec->lru_lock);
WARN_ON_ONCE(nr_pages != (int)nr_pages);
mod_lruvec_state(lruvec, NR_LRU_BASE + lru, nr_pages);
- __mod_zone_page_state(&pgdat->node_zones[zid],
+ mod_zone_page_state(&pgdat->node_zones[zid],
NR_ZONE_LRU_BASE + lru, nr_pages);
}
@@ -95,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)
@@ -134,39 +288,54 @@ static inline int lru_hist_from_seq(unsigned long seq)
return seq % NR_HIST_GENS;
}
-static inline int lru_tier_from_refs(int refs, bool workingset)
+static inline int lru_tier_from_refs(unsigned int refs)
{
- VM_WARN_ON_ONCE(refs > BIT(LRU_REFS_WIDTH));
-
- /* see the comment on MAX_NR_TIERS */
- return workingset ? MAX_NR_TIERS - 1 : order_base_2(refs);
+ BUILD_BUG_ON(fls(LRU_REFS_MAX - 1) > MAX_NR_TIERS - 1);
+ VM_WARN_ON_ONCE(refs > LRU_REFS_MAX);
+ if (refs < LRU_REFS_WORKINGSET)
+ return 0;
+ return fls(refs - 1);
}
-static inline int folio_lru_refs(const struct folio *folio)
+/**
+ * lru_gen_from_flags - Return the LRU generation number from folio flags.
+ * @flags: folio flags
+ *
+ * Returns: A number between 0 and LRU_GEN_MAX, inclusive. Returns -1 if the
+ * flags indicate the folio is off the list (e.g., isolated).
+ */
+static inline int lru_gen_from_flags(unsigned long flags)
{
- unsigned long flags = READ_ONCE(folio->flags.f);
+ int gen = ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF);
- if (!(flags & BIT(PG_referenced)))
- return 0;
- /*
- * Return the total number of accesses including PG_referenced. Also see
- * the comment on LRU_REFS_FLAGS.
- */
- return ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) + 1;
+ gen -= 1;
+ VM_WARN_ON_ONCE(gen != -1 && gen > LRU_GEN_MAX);
+ return gen;
}
-static inline int folio_lru_gen(const struct folio *folio)
+/**
+ * lru_gen_set_flags - Set the LRU generation number to specified folio flags.
+ * @flags: pointer to the folio flags
+ * @gen: generation number, between 0 and LRU_GEN_MAX, inclusive.
+ */
+static inline void lru_gen_set_flags(unsigned long *flags, int gen)
{
- unsigned long flags = READ_ONCE(folio->flags.f);
+ BUILD_BUG_ON((LRU_GEN_MAX + 1) != MAX_NR_GENS);
+ VM_WARN_ON_ONCE(gen > LRU_GEN_MAX || gen < 0);
+ *flags &= ~LRU_GEN_MASK;
+ *flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+}
- return ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+static inline int folio_lru_gen(const struct folio *folio)
+{
+ return lru_gen_from_flags(READ_ONCE(*const_folio_flags(folio, 0)));
}
static inline bool lru_gen_is_active(const struct lruvec *lruvec, int gen)
{
- unsigned long max_seq = lruvec->lrugen.max_seq;
+ unsigned long max_seq = READ_ONCE(lruvec->lrugen.max_seq);
- VM_WARN_ON_ONCE(gen >= MAX_NR_GENS);
+ VM_WARN_ON_ONCE(gen > LRU_GEN_MAX);
/* see the comment on MIN_NR_GENS */
return gen == lru_gen_from_seq(max_seq) || gen == lru_gen_from_seq(max_seq - 1);
@@ -186,11 +355,9 @@ static inline void lru_gen_update_size(struct lruvec *lruvec, struct folio *foli
VM_WARN_ON_ONCE(old_gen == -1 && new_gen == -1);
if (old_gen >= 0)
- WRITE_ONCE(lrugen->nr_pages[old_gen][type][zone],
- lrugen->nr_pages[old_gen][type][zone] - delta);
+ atomic_long_sub(delta, &lrugen->nr_pages[old_gen][type][zone]);
if (new_gen >= 0)
- WRITE_ONCE(lrugen->nr_pages[new_gen][type][zone],
- lrugen->nr_pages[new_gen][type][zone] + delta);
+ atomic_long_add(delta, &lrugen->nr_pages[new_gen][type][zone]);
/* addition */
if (old_gen < 0) {
@@ -223,23 +390,24 @@ static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec,
bool reclaiming)
{
int gen;
+ int refs = folio_lru_refs(folio);
int type = folio_is_file_lru(folio);
const struct lru_gen_folio *lrugen = &lruvec->lrugen;
/*
- * +-----------------------------------+-----------------------------------+
- * | Accessed through page tables and | Accessed through file descriptors |
- * | promoted by folio_update_gen() | and protected by folio_inc_gen() |
- * +-----------------------------------+-----------------------------------+
- * | PG_active (set while isolated) | |
- * +-----------------+-----------------+-----------------+-----------------+
- * | PG_workingset | PG_referenced | PG_workingset | LRU_REFS_FLAGS |
- * +-----------------------------------+-----------------------------------+
- * |<---------- MIN_NR_GENS ---------->| |
- * |<---------------------------- MAX_NR_GENS ---------------------------->|
+ * +------------------------------------------+------------------------------------------+
+ * | Accessed through page tables and | Accessed through file descriptors |
+ * | promoted by folio_inc_lru_refs_walk() | protected by folio_inc_lru_refs/inc_gen |
+ * +------------------------------------------+------------------------------------------+
+ * | PG_active (set at isolation or refault) | |
+ * +--------------------+---------------------+--------------------+---------------------+
+ * | LRU_REFS_MAX | LRU_REFS_WORKINGSET | LRU_REFS_MAX | LRU_REFS_WORKINGSET |
+ * +------------------------------------------+------------------------------------------+
+ * |<-------------- MIN_NR_GENS ------------->| |
+ * |<----------------------------------- MAX_NR_GENS ----------------------------------->|
*/
if (folio_test_active(folio))
- gen = MIN_NR_GENS - folio_test_workingset(folio);
+ gen = MIN_NR_GENS - (refs >= LRU_REFS_WORKINGSET);
else if (reclaiming)
gen = MAX_NR_GENS;
else if ((!folio_is_file_lru(folio) && !folio_test_swapcache(folio)) ||
@@ -247,7 +415,7 @@ static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec,
(folio_test_dirty(folio) || folio_test_writeback(folio))))
gen = MIN_NR_GENS;
else
- gen = MAX_NR_GENS - (folio_test_workingset(folio) || folio_test_referenced(folio));
+ gen = MAX_NR_GENS - (refs >= LRU_REFS_WORKINGSET);
return max(READ_ONCE(lrugen->max_seq) - gen + 1, READ_ONCE(lrugen->min_seq[type]));
}
@@ -270,7 +438,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio,
gen = lru_gen_from_seq(seq);
flags = (gen + 1UL) << LRU_GEN_PGOFF;
/* see the comment on MIN_NR_GENS about PG_active */
- set_mask_bits(&folio->flags.f, LRU_GEN_MASK | BIT(PG_active), flags);
+ set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK | BIT(PG_active), flags);
lru_gen_update_size(lruvec, folio, -1, gen);
/* for folio_rotate_reclaimable() */
@@ -295,7 +463,7 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
/* for folio_migrate_flags() */
flags = !reclaiming && lru_gen_is_active(lruvec, gen) ? BIT(PG_active) : 0;
- flags = set_mask_bits(&folio->flags.f, LRU_GEN_MASK, flags);
+ flags = set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK, flags);
gen = ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
lru_gen_update_size(lruvec, folio, gen, -1);
@@ -304,12 +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)
-{
- unsigned long refs = READ_ONCE(old->flags.f) & LRU_REFS_MASK;
-
- set_mask_bits(&new->flags.f, LRU_REFS_MASK, refs);
-}
#else /* !CONFIG_LRU_GEN */
static inline bool lru_gen_enabled(void)
@@ -337,10 +499,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
return false;
}
-static inline void folio_migrate_refs(struct folio *new, const struct folio *old)
-{
-
-}
#endif /* CONFIG_LRU_GEN */
static __always_inline
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index a26c8b8552222..e4f7efc02e501 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -472,54 +472,111 @@ enum lruvec_flags {
#define MAX_NR_GENS 4U
/*
- * Each generation is divided into multiple tiers. A folio accessed N times
- * through file descriptors is in tier order_base_2(N). A folio in the first
- * tier (N=0,1) is marked by PG_referenced unless it was faulted in through page
- * tables or read ahead. A folio in the last tier (MAX_NR_TIERS-1) is marked by
- * PG_workingset. A folio in any other tier (1<N<5) between the first and last
- * is marked by additional bits of LRU_REFS_WIDTH in folio->flags.
+ * Each generation is divided into multiple tiers. A folio's referenced
+ * count maps to a tier as shown below:
*
- * In contrast to moving across generations which requires the LRU lock, moving
- * across tiers only involves atomic operations on folio->flags and therefore
- * has a negligible cost in the buffered access path. In the eviction path,
- * comparisons of refaulted/(evicted+protected) from the first tier and the rest
- * infer whether folios accessed multiple times through file descriptors are
- * statistically hot and thus worth protecting.
+ * MGLRU (frequency guidance)
+ * Refs Tier |- Refs: how many times (at least) a folio has been referenced.
+ * 0 0 |- Mostly cold pages, readahead, etc. [1]
+ * 1 0 |= LRU_REFS_REFERENCED: Used at least once. [2]
+ * -WORKINGSET-+|- Pages beyond are workingset and never fall below this floor. [3]
+ * 2 1<-+|= LRU_REFS_WORKINGSET: Classical workingset, accessed twice, protected. [4]
+ * 3 2 |- LRU_REFS_PROTECTED: Protected workingset, promoted pages capped at here. [5]
+ * 4* 2 |
+ * 5* 3 |- The tier here is MAX_NR_TIERS - 1
+ * 6* 3 |
+ * 7* 3 |= LRU_REFS_MAX: Promotion candidate. [6]
+ * -PROMOTION->-/
*
- * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice the
- * number of categories of the active/inactive LRU when keeping track of
- * accesses through file descriptors. This uses MAX_NR_TIERS-2 spare bits in
- * folio->flags, masked by LRU_REFS_MASK.
+ * Ideally each tier holds folios of similar access patterns: lower tiers
+ * are less important and evicted faster. A page's reference count and
+ * tier are capped when it changes generation, preventing it from
+ * dominating the new generation based on old-generation access history.
+ * Generation ordering already ensures a newer-gen page is hotter than an
+ * older-gen one regardless of tier.
+ *
+ * Refs tracks accesses from two sources: page table (lazily collected by
+ * the page table aging walk or rmap eviction lookup) and file descriptors
+ * (by folio_mark_accessed). Page table accesses are weighted heavier
+ * because the accessed bit is sticky (undercounts repeated accesses),
+ * passively collected, and page faults are generally more important as
+ * userspace does not expect a memory access to block on reclaim. Both
+ * access types increment refs by one; the result is capped at
+ * LRU_REFS_PROTECTED on promotion or deferral, or LRU_REFS_MAX otherwise.
+ *
+ * 1. Tier is fls(N-1) for N > 1, 0 otherwise. Folios with zero
+ * accesses (refs == 0) are generally cold, e.g. readahead folios.
+ *
+ * Page table access advances a folio by one generation even at the
+ * lowest refs or tier. Freshly allocated folios start with refs == 0;
+ * faulted and mapped folios have their page table access bit set, so
+ * the first page table access check always sets LRU_REFS_REFERENCED and
+ * moves them one generation forward, driving aging and workingset shift.
+ *
+ * 2. Folios accessed once stay on tier 0: one-time usage does not
+ * qualify for protection. A second access advances the folio,
+ * aligning with classical LRU's use-twice threshold. A second page
+ * table access promotes to the latest gen; file access only defers
+ * eviction from the oldest gen.
+ *
+ * 3. Folios accessed at least twice are considered workingset. This
+ * mostly aligns with classical LRU: at least one I/O is saved by
+ * keeping them in memory. Folios at or above this level never fall
+ * below tier 1 (the workingset floor), so tier 0 stays a clean tier
+ * for cold cache while tier 1 serves as the fallback line for
+ * actually reused or historically hot folios.
+ *
+ * Folios refaulted through a page fault at refs 1 will enter the second
+ * newest gen, so faulting will be protected better.
+ *
+ * 4. Starting from tier 1, PID protection sacrifices lower tiers to
+ * protect higher tiers by comparing refault rates for long-term
+ * accuracy, and caps higher refs to this value. Since PID protection
+ * bypasses page table lookup and clearing, when a further eviction
+ * attempt occurs after PID loosens, the folio's page table access is
+ * rechecked and the folio is sent back to LRU_REFS_PROTECTED. This
+ * also gives folios a fair opportunity to be promoted by file access
+ * again.
+ *
+ * Folios refaulted through a page fault at tier 1 or above are activated
+ * and enter the newest gen. Non fault page will enter second oldest gen,
+ * driving aging and workingset shifting.
+ *
+ * 5. Pages beyond the ordinary workingset tier form new tiers for the
+ * PID controller to protect differently. Folios at or above this
+ * level are capped at LRU_REFS_PROTECTED on promotion or deferral,
+ * and at LRU_REFS_WORKINGSET under PID protection in the oldest
+ * generation, where they represent a historical workingset.
+ *
+ * 6. Folios that reach LRU_REFS_MAX are advanced to the next generation
+ * on further access, with refs capped to LRU_REFS_PROTECTED. This
+ * gives them a fair start for advancement to an even newer generation
+ * while keeping hot folios distinguishable.
+ *
+ * Tiering uses PG_referenced and PG_workingset as the lower two bits,
+ * and the bits masked by LRU_REFS_MASK as the higher bits.
+ *
+ * A folio's referenced count never goes backwards except upon gen
+ * increase as described above. Refault of a reclaimed folio restores
+ * its referenced count, capped at LRU_REFS_PROTECTED, which aligns with
+ * promotion. Page table refaults of previous workingset folios send
+ * them to the latest gen, driving aging faster.
+ *
+ * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice
+ * the number of categories of the active/inactive LRU.
*/
#define MAX_NR_TIERS 4U
+#define LRU_REFS_REFERENCED 0x1
+#define LRU_REFS_WORKINGSET 0x2
+#define LRU_REFS_PROTECTED 0x3
#ifndef __GENERATING_BOUNDS_H
#define LRU_GEN_MASK ((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF)
+#define LRU_GEN_MAX (BIT(LRU_GEN_WIDTH - 1) - 1)
#define LRU_REFS_MASK ((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF)
-
-/*
- * For folios accessed multiple times through file descriptors,
- * lru_gen_inc_refs() sets additional bits of LRU_REFS_WIDTH in folio->flags
- * after PG_referenced, then PG_workingset after LRU_REFS_WIDTH. After all its
- * bits are set, i.e., LRU_REFS_FLAGS|BIT(PG_workingset), a folio is lazily
- * promoted into the second oldest generation in the eviction path. And when
- * folio_inc_gen() does that, it clears LRU_REFS_FLAGS so that
- * lru_gen_inc_refs() can start over. Note that for this case, LRU_REFS_MASK is
- * only valid when PG_referenced is set.
- *
- * For folios accessed multiple times through page tables, folio_update_gen()
- * from a page table walk or lru_gen_set_refs() from a rmap walk sets
- * PG_referenced after the accessed bit is cleared for the first time.
- * Thereafter, those two paths set PG_workingset and promote folios to the
- * youngest generation. Like folio_inc_gen(), folio_update_gen() also clears
- * PG_referenced. Note that for this case, LRU_REFS_MASK is not used.
- *
- * For both cases above, after PG_workingset is set on a folio, it remains until
- * this folio is either reclaimed, or "deactivated" by lru_gen_clear_refs(). It
- * can be set again if lru_gen_test_recent() returns true upon a refault.
- */
-#define LRU_REFS_FLAGS (LRU_REFS_MASK | BIT(PG_referenced))
+#define LRU_REFS_FLAGS (LRU_REFS_MASK | BIT(PG_referenced) | BIT(PG_workingset))
+#define LRU_REFS_MAX (BIT(LRU_REFS_WIDTH + 2) - 1)
struct lruvec;
struct page_vma_mapped_walk;
@@ -570,7 +627,7 @@ struct lru_gen_folio {
/* the multi-gen LRU lists, lazily sorted on eviction */
struct list_head folios[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
/* the multi-gen LRU sizes, eventually consistent */
- long nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
+ atomic_long_t nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];
/* the exponential moving average of refaulted */
unsigned long avg_refaulted[ANON_AND_FILE][MAX_NR_TIERS];
/* the exponential moving average of evicted+protected */
diff --git a/kernel/bounds.c b/kernel/bounds.c
index 02b619eb61065..06a034713b5dd 100644
--- a/kernel/bounds.c
+++ b/kernel/bounds.c
@@ -25,7 +25,7 @@ int main(void)
DEFINE(SPINLOCK_SIZE, sizeof(spinlock_t));
#ifdef CONFIG_LRU_GEN
DEFINE(LRU_GEN_WIDTH, order_base_2(MAX_NR_GENS + 1));
- DEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 2);
+ DEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 3);
#else
DEFINE(LRU_GEN_WIDTH, 0);
DEFINE(__LRU_REFS_WIDTH, 0);
diff --git a/mm/filemap.c b/mm/filemap.c
index 6afec636881fb..a88a6140ed094 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1259,7 +1259,7 @@ static inline int folio_wait_bit_common(struct folio *folio, int bit_nr,
bool in_thrashing;
if (bit_nr == PG_locked &&
- !folio_test_uptodate(folio) && folio_test_workingset(folio)) {
+ !folio_test_uptodate(folio) && folio_is_workingset(folio)) {
delayacct_thrashing_start(&in_thrashing);
psi_memstall_enter(&pflags);
thrashing = true;
@@ -1414,7 +1414,7 @@ void softleaf_entry_wait_on_locked(softleaf_t entry, spinlock_t *ptl)
struct folio *folio = softleaf_to_folio(entry);
q = folio_waitqueue(folio);
- if (!folio_test_uptodate(folio) && folio_test_workingset(folio)) {
+ if (!folio_test_uptodate(folio) && folio_is_workingset(folio)) {
delayacct_thrashing_start(&in_thrashing);
psi_memstall_enter(&pflags);
thrashing = true;
@@ -2510,7 +2510,7 @@ static void filemap_get_read_batch(struct address_space *mapping,
static int filemap_read_folio(struct file *file, filler_t filler,
struct folio *folio)
{
- bool workingset = folio_test_workingset(folio);
+ bool workingset = folio_is_workingset(folio);
unsigned long pflags;
int error;
@@ -3981,7 +3981,7 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,
*/
if ((map_ret & VM_FAULT_NOPAGE) &&
!(vmf->flags & FAULT_FLAG_TRIED) &&
- !folio_test_workingset(folio) &&
+ !folio_is_workingset(folio) &&
!(vma->vm_flags & (VM_SEQ_READ | VM_EXEC))) {
unsigned short mmap_miss;
diff --git a/mm/folio.c b/mm/folio.c
index a9e328c3f21bb..a326602a59fe3 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -272,7 +272,6 @@ static void lru_activate(struct lruvec *lruvec, struct folio *folio)
if (folio_test_active(folio) || folio_test_unevictable(folio))
return;
-
lruvec_del_folio(lruvec, folio);
folio_set_active(folio);
lruvec_add_folio(lruvec, folio);
@@ -351,30 +350,6 @@ static void __lru_cache_activate_folio(struct folio *folio)
#ifdef CONFIG_LRU_GEN
-static void lru_gen_inc_refs(struct folio *folio)
-{
- unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f);
-
- if (folio_test_unevictable(folio))
- return;
-
- /* see the comment on LRU_REFS_FLAGS */
- if (!folio_test_referenced(folio)) {
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced));
- return;
- }
-
- do {
- if ((old_flags & LRU_REFS_MASK) == LRU_REFS_MASK) {
- if (!folio_test_workingset(folio))
- folio_set_workingset(folio);
- return;
- }
-
- new_flags = old_flags + BIT(LRU_REFS_PGOFF);
- } while (!try_cmpxchg(&folio->flags.f, &old_flags, new_flags));
-}
-
static bool lru_gen_clear_refs(struct folio *folio)
{
int gen = folio_lru_gen(folio);
@@ -384,7 +359,7 @@ static bool lru_gen_clear_refs(struct folio *folio)
if (gen < 0)
return true;
- set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS | BIT(PG_workingset), 0);
+ folio_set_lru_refs(folio, 0);
rcu_read_lock();
seq = READ_ONCE(folio_lruvec(folio)->lrugen.min_seq[type]);
@@ -395,10 +370,6 @@ static bool lru_gen_clear_refs(struct folio *folio)
#else /* !CONFIG_LRU_GEN */
-static void lru_gen_inc_refs(struct folio *folio)
-{
-}
-
static bool lru_gen_clear_refs(struct folio *folio)
{
return false;
@@ -424,7 +395,8 @@ void folio_mark_accessed(struct folio *folio)
if (folio_test_dropbehind(folio))
return;
if (lru_gen_enabled()) {
- lru_gen_inc_refs(folio);
+ if (!folio_test_unevictable(folio))
+ folio_inc_lru_refs(folio, false, false);
return;
}
@@ -470,21 +442,6 @@ void folio_add_lru(struct folio *folio)
folio_test_unevictable(folio), folio);
VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
- /*
- * For refaulted workingset folios, set PG_active so they
- * can be added to active generations.
- * For prefaulted file folios, folio_mark_accessed() sets
- * PG_referenced so lru_gen_folio_seq() places them into
- * the second oldest generation.
- */
- if (lru_gen_enabled() && !folio_test_unevictable(folio) &&
- lru_gen_in_fault() && !(current->flags & PF_MEMALLOC)) {
- if (folio_test_workingset(folio))
- folio_set_active(folio);
- else if (!folio_test_referenced(folio))
- folio_mark_accessed(folio);
- }
-
folio_batch_add_and_move(folio, lru_add);
}
EXPORT_SYMBOL(folio_add_lru);
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 21c92ee48e469..043c9ac963b4a 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3058,8 +3058,8 @@ static void __split_huge_pud_locked(struct vm_area_struct *vma, pud_t *pud,
if (!folio_test_dirty(folio) && pud_dirty(old_pud))
folio_mark_dirty(folio);
- if (!folio_test_referenced(folio) && pud_young(old_pud))
- folio_set_referenced(folio);
+ if (pud_young(old_pud) && vma_has_recency(vma))
+ folio_mark_accessed(folio);
folio_remove_rmap_pud(folio, page, vma);
add_mm_counter(vma->vm_mm, mm_counter_file(folio),
-HPAGE_PUD_NR);
@@ -3181,8 +3181,8 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
folio = page_folio(page);
if (!folio_test_dirty(folio) && pmd_dirty(old_pmd))
folio_mark_dirty(folio);
- if (!folio_test_referenced(folio) && pmd_young(old_pmd))
- folio_set_referenced(folio);
+ if (pmd_young(old_pmd) && vma_has_recency(vma))
+ folio_mark_accessed(folio);
folio_remove_rmap_pmd(folio, page, vma);
add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
folio_put(folio);
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index b237f6e7662a5..86c9b07dece6a 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -809,7 +809,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
*/
if (cc->is_khugepaged &&
(pte_young(pteval) || folio_test_young(folio) ||
- folio_test_referenced(folio) ||
+ folio_is_referenced(folio) ||
mmu_notifier_test_young(vma->vm_mm, addr)))
referenced++;
}
@@ -1767,7 +1767,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
*/
if (cc->is_khugepaged &&
(pte_young(pteval) || folio_test_young(folio) ||
- folio_test_referenced(folio) ||
+ folio_is_referenced(folio) ||
mmu_notifier_test_young(vma->vm_mm, addr)))
referenced++;
}
@@ -2752,7 +2752,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
/*
* We probably should check if the folio is referenced
* here, but nobody would transfer pte_young() to
- * folio_test_referenced() for us. And rmap walk here
+ * folio_is_referenced() for us. And rmap walk here
* is just too costly...
*/
diff --git a/mm/madvise.c b/mm/madvise.c
index 07a21ca31bad4..f132dd7418f58 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -350,6 +350,27 @@ static inline int madvise_folio_pte_batch(unsigned long addr, unsigned long end,
FPB_MERGE_YOUNG_DIRTY);
}
+/*
+ * We are deactivating a folio for accelerating reclaiming.
+ * VM couldn't reclaim the folio unless we clear PG_young.
+ * As a side effect, it makes confuse idle-page tracking
+ * because they will miss recent referenced history.
+ */
+static void madvise_cold_or_pageout_prep_folio(struct folio *folio)
+{
+ folio_test_clear_young(folio);
+
+ /*
+ * MGLRU clears all reference flags in folio_deactivate,
+ * no need to touch it here.
+ */
+ if (!lru_gen_enabled()) {
+ folio_clear_referenced_by_bit(folio);
+ if (folio_test_active(folio))
+ folio_mark_workingset_by_bit(folio);
+ }
+}
+
static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,
unsigned long addr, unsigned long end,
struct mm_walk *walk)
@@ -424,10 +445,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,
tlb_remove_pmd_tlb_entry(tlb, pmd, addr);
}
- folio_clear_referenced(folio);
- folio_test_clear_young(folio);
- if (folio_test_active(folio))
- folio_set_workingset(folio);
+ madvise_cold_or_pageout_prep_folio(folio);
if (pageout) {
if (folio_isolate_lru(folio)) {
if (folio_test_unevictable(folio))
@@ -533,16 +551,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,
tlb_remove_tlb_entries(tlb, pte, nr, addr);
}
- /*
- * We are deactivating a folio for accelerating reclaiming.
- * VM couldn't reclaim the folio unless we clear PG_young.
- * As a side effect, it makes confuse idle-page tracking
- * because they will miss recent referenced history.
- */
- folio_clear_referenced(folio);
- folio_test_clear_young(folio);
- if (folio_test_active(folio))
- folio_set_workingset(folio);
+ madvise_cold_or_pageout_prep_folio(folio);
if (pageout) {
if (folio_isolate_lru(folio)) {
if (folio_test_unevictable(folio))
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 1bde9d5af88af..3fbd7a6f66500 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1522,35 +1522,19 @@ struct lruvec *folio_lruvec_lock_irqsave(struct folio *folio,
* @zid: zone id of the accounted pages
* @nr_pages: positive when adding or negative when removing
*
- * This function must be called under lru_lock, just before a page is added
- * to or just after a page is removed from an lru list.
+ * This function must be called when a page is added to or removed from
+ * an lru list. Caller need to protect the lruvec from being freed.
*/
void mem_cgroup_update_lru_size(struct lruvec *lruvec, enum lru_list lru,
int zid, long nr_pages)
{
struct mem_cgroup_per_node *mz;
- unsigned long *lru_size;
- long size;
if (mem_cgroup_disabled())
return;
mz = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
- lru_size = &mz->lru_zone_size[zid][lru];
-
- if (nr_pages < 0)
- *lru_size += nr_pages;
-
- size = *lru_size;
- if (WARN_ONCE(size < 0,
- "%s(%p, %d, %ld): lru_size %ld\n",
- __func__, lruvec, lru, nr_pages, size)) {
- VM_BUG_ON(1);
- *lru_size = 0;
- }
-
- if (nr_pages > 0)
- *lru_size += nr_pages;
+ atomic_long_add(nr_pages, &mz->lru_zone_size[zid][lru]);
}
/**
diff --git a/mm/migrate.c b/mm/migrate.c
index b937cbd764808..806f1e913a38f 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -779,8 +779,6 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)
{
int cpupid;
- if (folio_test_referenced(folio))
- folio_set_referenced(newfolio);
if (folio_test_uptodate(folio))
folio_mark_uptodate(newfolio);
if (folio_test_clear_active(folio)) {
@@ -788,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);
/*
diff --git a/mm/page_io.c b/mm/page_io.c
index e4fa7ffffe8bb..e7efc5bff6683 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -25,6 +25,7 @@
#include <linux/sched/task.h>
#include <linux/delayacct.h>
#include <linux/zswap.h>
+#include <linux/mm_inline.h>
#include "swap.h"
#include "swap_table.h"
@@ -452,7 +453,7 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
{
struct swap_info_struct *sis = __swap_entry_to_info(folio->swap);
bool synchronous = sis->flags & SWP_SYNCHRONOUS_IO;
- bool workingset = folio_test_workingset(folio);
+ bool workingset = folio_is_workingset(folio);
unsigned long pflags;
bool in_thrashing;
diff --git a/mm/readahead.c b/mm/readahead.c
index 558c92957518b..3ab796af64907 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -302,7 +302,7 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
}
if (i == mark)
folio_set_readahead(folio);
- ractl->_workingset |= folio_test_workingset(folio);
+ ractl->_workingset |= folio_is_workingset(folio);
ractl->_nr_pages += min_nrpages;
i += min_nrpages;
}
@@ -474,7 +474,7 @@ static inline int ra_alloc_folio(struct readahead_control *ractl, pgoff_t index,
}
ractl->_nr_pages += 1UL << order;
- ractl->_workingset |= folio_test_workingset(folio);
+ ractl->_workingset |= folio_is_workingset(folio);
return 0;
}
@@ -817,7 +817,7 @@ void readahead_expand(struct readahead_control *ractl,
folio_put(folio);
return;
}
- if (unlikely(folio_test_workingset(folio)) &&
+ if (unlikely(folio_is_workingset(folio)) &&
!ractl->_workingset) {
ractl->_workingset = true;
psi_memstall_enter(&ractl->_pflags);
@@ -846,7 +846,7 @@ void readahead_expand(struct readahead_control *ractl,
folio_put(folio);
return;
}
- if (unlikely(folio_test_workingset(folio)) &&
+ if (unlikely(folio_is_workingset(folio)) &&
!ractl->_workingset) {
ractl->_workingset = true;
psi_memstall_enter(&ractl->_pflags);
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 17d2b793cbfc4..913e69eae5348 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -830,35 +830,177 @@ enum folio_references {
};
#ifdef CONFIG_LRU_GEN
+/******************************************************************************
+ * Referenced count feedback
+ ******************************************************************************/
+
/*
- * Only used on a mapped folio in the eviction (rmap walk) path, where promotion
- * needs to be done by taking the folio off the LRU list and then adding it back
- * with PG_active set. In contrast, the aging (page table walk) path uses
- * folio_update_gen().
+ * The folio_inc_lru_refs{_*} helpers below collect the referenced info
+ * (hotness) from other parts, including the page table walker, the rmap walk
+ * upon eviction, the rmap lookaround, and file descriptors
+ * (folio_mark_accessed).
+ *
+ * Page table accesses escalate a folio in two steps. The first access
+ * advances it one generation; a second access sends it to the newest
+ * generation. Executable file folios skip the first step and are promoted
+ * immediately, as reclaiming them causes IO thrashing.
+ *
+ * File descriptor accesses do not promote. They only defer eviction from
+ * the oldest generation, and only once the folio is a workingset folio
+ * (LRU_REFS_WORKINGSET), leaving the rest to PID protection. Page table
+ * accesses are treated more generously because the accessed bit is sticky
+ * (it under-counts repeated accesses) and because a page fault is more
+ * costly than file descriptor I/O.
+ *
+ * PID protection operates on tier > 0 folios. The one proactive promotion
+ * outside of it and the page table path is the overflow case where the
+ * referenced count exceeds LRU_REFS_MAX, which means the folio is hotter
+ * than everything else in its generation.
+ *
+ * Whenever a folio changes generation here its referenced count is capped at
+ * LRU_REFS_PROTECTED, so it starts at or below the protected tier regardless
+ * of its old-generation access history. PID protection (folio_inc_gen) caps
+ * at LRU_REFS_WORKINGSET independently.
*/
-static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags)
+
+/*
+ * Update the folio's lru refs indicator without taking the folio lock,
+ * isolation, or lruvec lock. Used by both page table access (@is_fault=true)
+ * and by file access (@is_fault=false).
+ */
+int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec)
{
- /* see the comment on LRU_REFS_FLAGS */
- if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) {
- /* Activate file-backed executable folios after first usage. */
- if (is_exec_file_folio(folio, vma_flags)) {
- set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset));
- return true;
+ int max_gen, min_gen;
+ int type, refs, gen, new_gen;
+ unsigned long new_flags, old_flags, max_seq;
+ struct lru_gen_folio *lrugen;
+ struct lruvec *lruvec;
+
+ type = folio_is_file_lru(folio);
+ lruvec = folio_lruvec_live_get(folio);
+ lrugen = &lruvec->lrugen;
+
+ old_flags = READ_ONCE(*folio_flags(folio, 0));
+ do {
+ new_flags = old_flags;
+ gen = lru_gen_from_flags(old_flags);
+ refs = lru_refs_from_flags(old_flags) + 1;
+ new_gen = gen;
+ if (!(old_flags & BIT(PG_lru)) || gen < 0)
+ goto out;
+
+ max_seq = READ_ONCE(lrugen->max_seq);
+ max_gen = lru_gen_from_seq(max_seq);
+ min_gen = lru_gen_from_seq(READ_ONCE(lrugen->min_seq[type]));
+ if (gen == max_gen)
+ goto out;
+
+ if (is_fault || is_exec) {
+ /* Promote second page table access or executable */
+ if (refs > LRU_REFS_REFERENCED || is_exec)
+ new_gen = max_gen;
+ else
+ new_gen = (gen + 1UL) % MAX_NR_GENS;
+ refs = min(refs, LRU_REFS_PROTECTED);
+ } else if (refs > LRU_REFS_MAX) {
+ /* LRU refs counting overflow, bump the gen */
+ new_gen = (gen + 1UL) % MAX_NR_GENS;
+ refs = LRU_REFS_PROTECTED;
+ } else if (gen == min_gen && refs >= LRU_REFS_WORKINGSET) {
+ /* Defer eviction of just accessed workingset */
+ new_gen = (gen + 1UL) % MAX_NR_GENS;
+ refs = min(refs, LRU_REFS_PROTECTED);
}
+out:
+ refs = min(refs, LRU_REFS_MAX);
+ lru_refs_set_flags(&new_flags, refs);
+ if (new_gen >= 0)
+ lru_gen_set_flags(&new_flags, new_gen);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced));
- return false;
+ if (new_gen != gen) {
+ /*
+ * Gen can only go forward, so concurrent aging is
+ * usually fine, except when multiple aging increase
+ * max_seq multiple times, new_gen may have go beyond
+ * the new max_seq's current gen border and causes
+ * hotness inversion. In that very unlikely case,
+ * just activate the folio.
+ */
+ lru_gen_update_size(lruvec, folio, gen, new_gen);
+ if (unlikely(READ_ONCE(lrugen->max_seq) - max_seq > MIN_NR_GENS))
+ folio_activate(folio);
}
- /* Promote on second access */
- if (folio_lru_refs(folio) > 1)
- set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset));
- else
- folio_mark_accessed(folio);
- return true;
+ folio_lruvec_live_put(lruvec);
+ return refs;
+}
+
+/*
+ * Update the folio's lru refs indicator during a page table walk.
+ * max_seq is stable since this runs inside the aging process.
+ *
+ * Returns the old generation and stores the new generation in @new_gen when
+ * the folio is promoted (to max_gen) or advanced by one generation.
+ * Returns -1 if no gen change occurred.
+ */
+static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lruvec,
+ const vma_flags_t *vma_flags, int *new_gen)
+{
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
+ unsigned long max_seq = READ_ONCE(lruvec->lrugen.max_seq);
+ int refs, gen, max_gen, ret;
+
+ max_gen = lru_gen_from_seq(max_seq);
+
+ do {
+ gen = lru_gen_from_flags(old_flags);
+ refs = lru_refs_from_flags(old_flags) + 1;
+ new_flags = old_flags;
+
+ if (gen >= 0 && gen != max_gen) {
+ ret = gen;
+ /* Promote second page table access or executable */
+ if (refs > LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags))
+ *new_gen = max_gen;
+ else
+ *new_gen = (gen + 1) % MAX_NR_GENS;
+ lru_gen_set_flags(&new_flags, *new_gen);
+ lru_refs_set_flags(&new_flags, min(refs, LRU_REFS_PROTECTED));
+ } else {
+ ret = -1;
+ lru_refs_set_flags(&new_flags, min(refs, LRU_REFS_MAX));
+ }
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
+
+ return ret;
+}
+
+/*
+ * Update the folio's lru refs indicator while the folio is isolated.
+ * Only used on mapped folios upon the final eviction, when the folio is
+ * off the LRU list (isolated).
+ *
+ * Increments the refs count (capped at LRU_REFS_PROTECTED). Returns true
+ * if the caller should activate the folio (second access or
+ * executable), false to keep it in the eviction list.
+ */
+static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)
+{
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
+ int refs;
+
+ do {
+ new_flags = old_flags;
+ refs = lru_refs_from_flags(old_flags) + 1;
+ lru_refs_set_flags(&new_flags, min(refs, LRU_REFS_PROTECTED));
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
+
+ /* Promote second page table access or executable */
+ return refs > LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags);
}
#else
-static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags)
+static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)
{
return false;
}
@@ -893,7 +1035,8 @@ static enum folio_references folio_check_references(struct folio *folio,
if (!referenced_ptes)
return FOLIOREF_RECLAIM;
- return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP;
+ return folio_inc_lru_refs_isolated(folio, &vma_flags) ?
+ FOLIOREF_ACTIVATE : FOLIOREF_KEEP;
}
referenced_folio = folio_test_clear_referenced(folio);
@@ -1636,10 +1779,6 @@ unsigned int reclaim_clean_pages_from_list(struct zone *zone,
return nr_reclaimed;
}
-/*
- * Update LRU sizes after isolating pages. The LRU size updates must
- * be complete before mem_cgroup_update_lru_size due to a sanity check.
- */
static __always_inline void update_lru_sizes(struct lruvec *lruvec,
enum lru_list lru, unsigned long *nr_zone_taken)
{
@@ -1651,7 +1790,6 @@ static __always_inline void update_lru_sizes(struct lruvec *lruvec,
update_lru_size(lruvec, lru, zid, -nr_zone_taken[zid]);
}
-
}
/*
@@ -2130,7 +2268,7 @@ static void shrink_active_list(unsigned long nr_to_scan,
}
folio_clear_active(folio); /* we are de-activating */
- folio_set_workingset(folio);
+ folio_mark_workingset_by_bit(folio);
list_add(&folio->lru, &l_inactive);
}
@@ -3194,8 +3332,8 @@ struct ctrl_pos {
int gain;
};
-static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
- struct ctrl_pos *pos)
+static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier_min,
+ int tier_max, int gain, struct ctrl_pos *pos)
{
int i;
struct lru_gen_folio *lrugen = &lruvec->lrugen;
@@ -3204,7 +3342,7 @@ static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
pos->gain = gain;
pos->refaulted = pos->total = 0;
- for (i = tier % MAX_NR_TIERS; i <= min(tier, MAX_NR_TIERS - 1); i++) {
+ for (i = tier_min; i < tier_max; i++) {
pos->refaulted += lrugen->avg_refaulted[type][i] +
atomic_long_read(&lrugen->refaulted[hist][type][i]);
pos->total += lrugen->avg_total[type][i] +
@@ -3264,57 +3402,32 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)
* the aging
******************************************************************************/
-/* promote pages accessed through page tables */
-static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags)
-{
- unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f);
-
- VM_WARN_ON_ONCE(gen >= MAX_NR_GENS);
-
- /*
- * See the comment on LRU_REFS_FLAGS, and activate file-backed
- * executable folios after first usage to avoid typical IO
- * thrashing from reclaiming.
- */
- if (!folio_test_referenced(folio) && !folio_test_workingset(folio) &&
- !is_exec_file_folio(folio, vma_flags)) {
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced));
- return -1;
- }
-
- do {
- /* lru_gen_del_folio() has isolated this page? */
- if (!(old_flags & LRU_GEN_MASK))
- return -1;
-
- new_flags = old_flags & ~(LRU_GEN_MASK | LRU_REFS_FLAGS);
- new_flags |= ((gen + 1UL) << LRU_GEN_PGOFF) | BIT(PG_workingset);
- } while (!try_cmpxchg(&folio->flags.f, &old_flags, new_flags));
-
- return ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
-}
-
-/* protect pages accessed multiple times through file descriptors */
+/*
+ * Force bump a folio's generation. Used for PID protection or defer the
+ * eviction of temporarily unevictable folio.
+ */
static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
{
+ int refs;
int type = folio_is_file_lru(folio);
struct lru_gen_folio *lrugen = &lruvec->lrugen;
- int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
- unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f);
-
- VM_WARN_ON_ONCE_FOLIO(!(old_flags & LRU_GEN_MASK), folio);
+ int old_gen, new_gen, min_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
do {
- new_gen = ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
- /* folio_update_gen() has promoted this page? */
- if (new_gen >= 0 && new_gen != old_gen)
- return new_gen;
+ new_flags = old_flags;
+ refs = lru_refs_from_flags(old_flags);
+ old_gen = lru_gen_from_flags(old_flags);
+ VM_WARN_ON_ONCE_FOLIO(old_gen < 0, folio);
- new_gen = (old_gen + 1) % MAX_NR_GENS;
+ /* folio has been promoted? */
+ if (old_gen >= 0 && old_gen != min_gen)
+ return old_gen;
- new_flags = old_flags & ~(LRU_GEN_MASK | LRU_REFS_FLAGS);
- new_flags |= (new_gen + 1UL) << LRU_GEN_PGOFF;
- } while (!try_cmpxchg(&folio->flags.f, &old_flags, new_flags));
+ new_gen = (old_gen + 1) % MAX_NR_GENS;
+ lru_gen_set_flags(&new_flags, new_gen);
+ lru_refs_set_flags(&new_flags, min(refs, LRU_REFS_WORKINGSET));
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
lru_gen_update_size(lruvec, folio, old_gen, new_gen);
@@ -3353,8 +3466,7 @@ static void reset_batch_size(struct lru_gen_mm_walk *walk)
continue;
walk->nr_pages[gen][type][zone] = 0;
- WRITE_ONCE(lrugen->nr_pages[gen][type][zone],
- lrugen->nr_pages[gen][type][zone] + delta);
+ atomic_long_add(delta, &lrugen->nr_pages[gen][type][zone]);
if (lru_gen_is_active(lruvec, gen))
lru += LRU_ACTIVE;
@@ -3511,9 +3623,9 @@ static bool suitable_to_scan(int total, int young)
}
static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma,
- struct folio *folio, int new_gen, bool dirty)
+ struct lruvec *lruvec, struct folio *folio, bool dirty)
{
- int old_gen;
+ int new_gen, old_gen;
if (!folio)
return;
@@ -3524,13 +3636,11 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struc
folio_mark_dirty(folio);
if (walk) {
- old_gen = folio_update_gen(folio, new_gen, &vma->flags);
- if (old_gen >= 0 && old_gen != new_gen)
+ old_gen = folio_inc_lru_refs_walk(folio, lruvec, &vma->flags, &new_gen);
+ if (old_gen >= 0)
update_batch_size(walk, folio, old_gen, new_gen);
- } else if (lru_gen_set_refs(folio, &vma->flags)) {
- old_gen = folio_lru_gen(folio);
- if (old_gen >= 0 && old_gen != new_gen)
- folio_activate(folio);
+ } else {
+ folio_inc_lru_refs(folio, true, is_exec_file_folio(folio, &vma->flags));
}
}
@@ -3548,8 +3658,6 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,
struct lru_gen_mm_walk *walk = args->private;
struct mem_cgroup *memcg = lruvec_memcg(walk->lruvec);
struct pglist_data *pgdat = lruvec_pgdat(walk->lruvec);
- DEFINE_MAX_SEQ(walk->lruvec);
- int gen = lru_gen_from_seq(max_seq);
unsigned int nr;
pmd_t pmdval;
@@ -3600,7 +3708,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,
continue;
if (last != folio) {
- walk_update_folio(walk, args->vma, last, gen, dirty);
+ walk_update_folio(walk, args->vma, walk->lruvec, last, dirty);
last = folio;
dirty = false;
@@ -3613,7 +3721,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end,
walk->mm_stats[MM_LEAF_YOUNG] += nr;
}
- walk_update_folio(walk, args->vma, last, gen, dirty);
+ walk_update_folio(walk, args->vma, walk->lruvec, last, dirty);
last = NULL;
if (i < PTRS_PER_PTE && get_next_vma(PMD_MASK, PAGE_SIZE, args, &start, &end))
@@ -3636,8 +3744,6 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area
struct lru_gen_mm_walk *walk = args->private;
struct mem_cgroup *memcg = lruvec_memcg(walk->lruvec);
struct pglist_data *pgdat = lruvec_pgdat(walk->lruvec);
- DEFINE_MAX_SEQ(walk->lruvec);
- int gen = lru_gen_from_seq(max_seq);
VM_WARN_ON_ONCE(pud_leaf(*pud));
@@ -3691,7 +3797,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area
goto next;
if (last != folio) {
- walk_update_folio(walk, vma, last, gen, dirty);
+ walk_update_folio(walk, vma, walk->lruvec, last, dirty);
last = folio;
dirty = false;
@@ -3705,7 +3811,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area
i = i > MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1;
} while (i <= MIN_LRU_BATCH);
- walk_update_folio(walk, vma, last, gen, dirty);
+ walk_update_folio(walk, vma, walk->lruvec, last, dirty);
lazy_mmu_mode_disable();
spin_unlock(ptl);
@@ -3919,7 +4025,8 @@ static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness)
while (!list_empty(head)) {
struct folio *folio = lru_to_folio(head);
int refs = folio_lru_refs(folio);
- bool workingset = folio_test_workingset(folio);
+ int delta = folio_nr_pages(folio);
+ int tier = lru_tier_from_refs(refs);
VM_WARN_ON_ONCE_FOLIO(folio_test_unevictable(folio), folio);
VM_WARN_ON_ONCE_FOLIO(folio_test_active(folio), folio);
@@ -3929,14 +4036,8 @@ static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness)
new_gen = folio_inc_gen(lruvec, folio);
list_move_tail(&folio->lru, &lrugen->folios[new_gen][type][zone]);
- /* don't count the workingset being lazily promoted */
- if (refs + workingset != BIT(LRU_REFS_WIDTH) + 1) {
- int tier = lru_tier_from_refs(refs, workingset);
- int delta = folio_nr_pages(folio);
-
- WRITE_ONCE(lrugen->protected[hist][type][tier],
- lrugen->protected[hist][type][tier] + delta);
- }
+ WRITE_ONCE(lrugen->protected[hist][type][tier],
+ lrugen->protected[hist][type][tier] + delta);
if (!--remaining)
return false;
@@ -4043,8 +4144,8 @@ static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness
for (type = 0; type < ANON_AND_FILE; type++) {
for (zone = 0; zone < MAX_NR_ZONES; zone++) {
enum lru_list lru = type * LRU_INACTIVE_FILE;
- long delta = lrugen->nr_pages[prev][type][zone] -
- lrugen->nr_pages[next][type][zone];
+ long delta = atomic_long_read(&lrugen->nr_pages[prev][type][zone]) -
+ atomic_long_read(&lrugen->nr_pages[next][type][zone]);
if (!delta)
continue;
@@ -4162,7 +4263,8 @@ static unsigned long lruvec_evictable_size(struct lruvec *lruvec, int swappiness
for (seq = min_seq[type]; seq <= max_seq; seq++) {
gen = lru_gen_from_seq(seq);
for (zone = 0; zone < MAX_NR_ZONES; zone++)
- total += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+ total += max(atomic_long_read(&lrugen->nr_pages[gen][type][zone]),
+ 0L);
}
}
@@ -4269,8 +4371,6 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)
struct pglist_data *pgdat = folio_pgdat(folio);
struct lruvec *lruvec;
struct lru_gen_mm_state *mm_state;
- unsigned long max_seq;
- int gen;
lockdep_assert_held(pvmw->ptl);
VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
@@ -4307,8 +4407,6 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)
memcg = get_mem_cgroup_from_folio(folio);
lruvec = mem_cgroup_lruvec(memcg, pgdat);
- max_seq = READ_ONCE((lruvec)->lrugen.max_seq);
- gen = lru_gen_from_seq(max_seq);
mm_state = get_mm_state(lruvec);
lazy_mmu_mode_enable();
@@ -4340,7 +4438,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)
continue;
if (last != folio) {
- walk_update_folio(walk, vma, last, gen, dirty);
+ walk_update_folio(walk, vma, lruvec, last, dirty);
last = folio;
dirty = false;
@@ -4352,13 +4450,14 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)
young += nr;
}
- walk_update_folio(walk, vma, last, gen, dirty);
+ walk_update_folio(walk, vma, lruvec, last, dirty);
lazy_mmu_mode_disable();
/* feedback from rmap walkers to page table walkers */
if (mm_state && suitable_to_scan(i, young))
- update_bloom_filter(mm_state, max_seq, pvmw->pmd);
+ update_bloom_filter(mm_state, READ_ONCE(lruvec->lrugen.max_seq),
+ pvmw->pmd);
mem_cgroup_put(memcg);
@@ -4597,7 +4696,7 @@ static void __lru_gen_reparent_memcg(struct lruvec *child_lruvec, struct lruvec
for (i = 0; i < get_nr_gens(child_lruvec, type); i++) {
int gen = lru_gen_from_seq(child_lrugen->max_seq - i);
- long nr_pages = child_lrugen->nr_pages[gen][type][zone];
+ long nr_pages = atomic_long_read(&child_lrugen->nr_pages[gen][type][zone]);
int child_lru_active = lru_gen_is_active(child_lruvec, gen) ? LRU_ACTIVE : 0;
int parent_lru_active = lru_gen_is_active(parent_lruvec, gen) ? LRU_ACTIVE : 0;
@@ -4605,9 +4704,8 @@ static void __lru_gen_reparent_memcg(struct lruvec *child_lruvec, struct lruvec
list_splice_tail_init(&child_lrugen->folios[gen][type][zone],
&parent_lrugen->folios[gen][type][zone]);
- WRITE_ONCE(child_lrugen->nr_pages[gen][type][zone], 0);
- WRITE_ONCE(parent_lrugen->nr_pages[gen][type][zone],
- parent_lrugen->nr_pages[gen][type][zone] + nr_pages);
+ atomic_long_set(&child_lrugen->nr_pages[gen][type][zone], 0);
+ atomic_long_add(nr_pages, &parent_lrugen->nr_pages[gen][type][zone]);
if (lru_gen_is_active(child_lruvec, gen) != lru_gen_is_active(parent_lruvec, gen)) {
__update_lru_size(child_lruvec, lru + child_lru_active, zone, -nr_pages);
@@ -4654,8 +4752,7 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c
int zone = folio_zonenum(folio);
int delta = folio_nr_pages(folio);
int refs = folio_lru_refs(folio);
- bool workingset = folio_test_workingset(folio);
- int tier = lru_tier_from_refs(refs, workingset);
+ int tier = lru_tier_from_refs(refs);
struct lru_gen_folio *lrugen = &lruvec->lrugen;
VM_WARN_ON_ONCE_FOLIO(gen >= MAX_NR_GENS, folio);
@@ -4677,17 +4774,15 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c
}
/* protected */
- if (tier > tier_idx || refs + workingset == BIT(LRU_REFS_WIDTH) + 1) {
+ if (tier > tier_idx) {
+ int hist = lru_hist_from_seq(lrugen->min_seq[type]);
+
gen = folio_inc_gen(lruvec, folio);
list_move(&folio->lru, &lrugen->folios[gen][type][zone]);
- /* don't count the workingset being lazily promoted */
- if (refs + workingset != BIT(LRU_REFS_WIDTH) + 1) {
- int hist = lru_hist_from_seq(lrugen->min_seq[type]);
+ WRITE_ONCE(lrugen->protected[hist][type][tier],
+ lrugen->protected[hist][type][tier] + delta);
- WRITE_ONCE(lrugen->protected[hist][type][tier],
- lrugen->protected[hist][type][tier] + delta);
- }
return true;
}
@@ -4715,10 +4810,6 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca
return false;
}
- /* see the comment on LRU_REFS_FLAGS */
- if (!folio_test_referenced(folio))
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, 0);
-
success = lru_gen_del_folio(lruvec, folio, true);
VM_WARN_ON_ONCE_FOLIO(!success, folio);
@@ -4806,13 +4897,13 @@ static int get_tier_idx(struct lruvec *lruvec, int type)
struct ctrl_pos sp, pv = {};
/*
- * To leave a margin for fluctuations, use a larger gain factor (2:3).
+ * To leave a margin for fluctuations, use a larger gain factor (1:2).
* This value is chosen because any other tier would have at least twice
* as many refaults as the first tier.
*/
- read_ctrl_pos(lruvec, type, 0, 2, &sp);
for (tier = 1; tier < MAX_NR_TIERS; tier++) {
- read_ctrl_pos(lruvec, type, tier, 3, &pv);
+ read_ctrl_pos(lruvec, type, 0, tier, 1, &sp);
+ read_ctrl_pos(lruvec, type, tier, tier + 1, 2, &pv);
if (!positive_ctrl_err(&sp, &pv))
break;
}
@@ -4833,8 +4924,8 @@ static int get_type_to_scan(struct lruvec *lruvec, int swappiness)
* Compare the sum of all tiers of anon with that of file to determine
* which type to scan.
*/
- read_ctrl_pos(lruvec, LRU_GEN_ANON, MAX_NR_TIERS, swappiness, &sp);
- read_ctrl_pos(lruvec, LRU_GEN_FILE, MAX_NR_TIERS, MAX_SWAPPINESS - swappiness, &pv);
+ read_ctrl_pos(lruvec, LRU_GEN_ANON, 0, MAX_NR_TIERS, swappiness, &sp);
+ read_ctrl_pos(lruvec, LRU_GEN_FILE, 0, MAX_NR_TIERS, MAX_SWAPPINESS - swappiness, &pv);
return positive_ctrl_err(&sp, &pv);
}
@@ -4936,7 +5027,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
/* don't add rejected folios to the oldest generation */
if (lru_gen_folio_seq(lruvec, folio, false) == min_seq[type])
- set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_active));
+ folio_set_active(folio);
}
move_folios_to_lru(&list);
@@ -5647,7 +5738,8 @@ static int lru_gen_seq_show(struct seq_file *m, void *v)
char mark = full && seq < min_seq[type] ? 'x' : ' ';
for (zone = 0; zone < MAX_NR_ZONES; zone++)
- size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+ size += max(atomic_long_read(&lrugen->nr_pages[gen][type][zone]),
+ 0L);
seq_printf(m, " %10lu%c", size, mark);
}
diff --git a/mm/workingset.c b/mm/workingset.c
index 7ac2b88c80ae5..568a3e44cd5e8 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -189,6 +189,13 @@
#define EVICTION_MASK (~0UL >> EVICTION_SHIFT)
#define EVICTION_MASK_ANON (~0UL >> EVICTION_SHIFT_ANON)
+/*
+ * LRU refs uses LRU_REFS_WIDTH + 2 bits, the 2 bits are PG_workingset and
+ * PG_referenced. But here we record PG_workingset separately (to reuse
+ * pack_shadow).
+ */
+#define LRU_REFS_BITS ((LRU_REFS_WIDTH + 2) - 1)
+
/*
* Eviction timestamps need to be able to cover the full range of
* actionable refaults. However, bits are tight in the xarray
@@ -242,13 +249,12 @@ static void *lru_gen_eviction(struct folio *folio)
int type = folio_is_file_lru(folio);
int delta = folio_nr_pages(folio);
int refs = folio_lru_refs(folio);
- bool workingset = folio_test_workingset(folio);
- int tier = lru_tier_from_refs(refs, workingset);
+ int tier = lru_tier_from_refs(refs);
struct mem_cgroup *memcg;
struct pglist_data *pgdat = folio_pgdat(folio);
unsigned short memcg_id;
- BUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_WIDTH >
+ BUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_BITS >
BITS_PER_LONG - max(EVICTION_SHIFT, EVICTION_SHIFT_ANON));
rcu_read_lock();
@@ -256,14 +262,14 @@ static void *lru_gen_eviction(struct folio *folio)
lruvec = mem_cgroup_lruvec(memcg, pgdat);
lrugen = &lruvec->lrugen;
min_seq = READ_ONCE(lrugen->min_seq[type]);
- token = (min_seq << LRU_REFS_WIDTH) | max(refs - 1, 0);
+ token = (min_seq << LRU_REFS_BITS) | refs >> 1;
hist = lru_hist_from_seq(min_seq);
atomic_long_add(delta, &lrugen->evicted[hist][type][tier]);
memcg_id = mem_cgroup_private_id(memcg);
rcu_read_unlock();
- return pack_shadow(memcg_id, pgdat, token, workingset, type);
+ return pack_shadow(memcg_id, pgdat, token, refs & 1, type);
}
/*
@@ -284,11 +290,24 @@ static bool lru_gen_test_recent(void *shadow, struct lruvec **lruvec,
*lruvec = mem_cgroup_lruvec(memcg, pgdat);
max_seq = READ_ONCE((*lruvec)->lrugen.max_seq);
- max_seq &= (file ? EVICTION_MASK : EVICTION_MASK_ANON) >> LRU_REFS_WIDTH;
+ max_seq &= (file ? EVICTION_MASK : EVICTION_MASK_ANON) >> LRU_REFS_BITS;
- return abs_diff(max_seq, *token >> LRU_REFS_WIDTH) < MAX_NR_GENS;
+ return abs_diff(max_seq, *token >> LRU_REFS_BITS) < MAX_NR_GENS;
}
+/*
+ * Restore the refs of a refaulted folio from its shadow entry.
+ *
+ * Any folio that was accessed at least once before eviction (refs >=
+ * LRU_REFS_REFERENCED) is activated on a fault-driven refault, giving it a
+ * strong gen placement. Non-fault refaults (e.g. readahead) are not
+ * activated regardless of refs.
+ *
+ * The restored refs is capped at LRU_REFS_PROTECTED to prevent stale
+ * high-tier history from carrying over across eviction cycles. The
+ * WORKINGSET_RESTORE stat is bumped only for refs >= LRU_REFS_WORKINGSET
+ * to track genuine workingset restoration.
+ */
static void lru_gen_refault(struct folio *folio, void *shadow)
{
bool recent;
@@ -314,22 +333,29 @@ static void lru_gen_refault(struct folio *folio, void *shadow)
lrugen = &lruvec->lrugen;
hist = lru_hist_from_seq(READ_ONCE(lrugen->min_seq[type]));
- refs = (token & (BIT(LRU_REFS_WIDTH) - 1)) + 1;
- tier = lru_tier_from_refs(refs, workingset);
+ refs = ((token & (BIT(LRU_REFS_BITS) - 1)) << 1) + workingset;
+ tier = lru_tier_from_refs(refs);
atomic_long_add(delta, &lrugen->refaulted[hist][type][tier]);
- if (workingset) {
- /*
- * see folio_add_lru(), where folio_set_active() is
- * called for workingset folios
- */
- if (lru_gen_in_fault())
+ /*
+ * Activate a fault-driven refault: the folio was accessed at
+ * least once before eviction and would have been promoted had
+ * it stayed in memory.
+ */
+ if (refs >= LRU_REFS_REFERENCED) {
+ if (lru_gen_in_fault()) {
+ folio_set_active(folio);
mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + type, delta);
- folio_set_workingset(folio);
+ }
+ /* Cap restored refs to prevent stale high-tier carry-over */
+ folio_set_lru_refs(folio, min(refs, LRU_REFS_PROTECTED));
+ }
+
+ /* WORKINGSET_RESTORE tracks genuine workingset-level refaults */
+ if (refs >= LRU_REFS_WORKINGSET)
mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + type, delta);
- } else
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, (refs - 1UL) << LRU_REFS_PGOFF);
+
unlock:
rcu_read_unlock();
}
@@ -412,7 +438,7 @@ void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg)
eviction >>= bucket_order[file];
workingset_age_nonresident(lruvec, folio_nr_pages(folio));
return pack_shadow(memcgid, pgdat, eviction,
- folio_test_workingset(folio), file);
+ folio_is_workingset(folio), file);
}
/**
@@ -583,7 +609,7 @@ void workingset_refault(struct folio *folio, void *shadow)
/* Folio was active prior to eviction */
if (workingset) {
- folio_set_workingset(folio);
+ folio_mark_workingset_by_bit(folio);
mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr);
}
out:
|
| 3/2 | 2026/08/03 21:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28880 output: 34 thoughts: 424 Thoughts: **Analyzing a Core Memory Management Patch** Okay, so I'm looking at this patch series, and it seems like we're fundamentally altering how LRU references are handled. Crucially, this touches both Multi-Gen LRU (MGLRU) and non-MGLRU contexts. The scope is pretty broad, affecting files like `fs/btrfs/compression.c`, several `mm` subsystem files, and critical headers. Given the breadth of the changes and the core nature of memory management, this is definitely something we need to subject to rigorous fuzzing. The patch introduces these new functions: `folio_is_workingset`, `folio_is_referenced`, `folio_inc_lru_refs`, `folio_set_lru_refs`, and `folio_lru_refs`. I'm seeing significant modifications within `vmscan.c`, particularly around the `folio_inc_lru_refs` family of functions: `folio_inc_lru_refs`, `folio_inc_lru_refs_walk`, and `folio_inc_lru_refs_isolated`. There are also changes to `madvise.c`, specifically `madvise_cold_or_pageout_prep_folio`, which is also worth further investigation. My immediate focus is on identifying prime candidates for fuzzing. I need to find the key functions affected. Let me start by checking the visibility of `folio_inc_lru_refs`. Is it exported or non-static? Aha, it's declared in `include/linux/mm_inline.h` and defined in `mm/vmscan.c`. It's non-static, so it's a good target. I'll need to dig into `mm/vmscan.c` to understand exactly how `folio_inc_lru_refs` is used and where potential vulnerabilities might lie. |
| 4/2 | 2026/08/03 21:26 | tool | grepper |
0mArgs:{
"Expression": "folio_inc_lru_refs",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=826=enum folio_references {\n--\nmm/vmscan.c-837-/*\nmm/vmscan.c:838: * The folio_inc_lru_refs{_*} helpers below collect the referenced info\nmm/vmscan.c-839- * (hotness) from other parts, including the page table walker, the rmap walk\n--\nmm/vmscan.c-870- */\nmm/vmscan.c:871:int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec)\nmm/vmscan.c-872-{\n--\nmm/vmscan.c-946- */\nmm/vmscan.c:947:static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lruvec,\nmm/vmscan.c-948-\t\t\t\t const vma_flags_t *vma_flags, int *new_gen)\n--\nmm/vmscan.c-987- */\nmm/vmscan.c:988:static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)\nmm/vmscan.c-989-{\n--\nmm/vmscan.c-1002-#else\nmm/vmscan.c:1003:static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)\nmm/vmscan.c-1004-{\n--\nmm/vmscan.c=1009=static enum folio_references folio_check_references(struct folio *folio,\n--\nmm/vmscan.c-1037-\nmm/vmscan.c:1038:\t\treturn folio_inc_lru_refs_isolated(folio, \u0026vma_flags) ?\nmm/vmscan.c-1039-\t\t FOLIOREF_ACTIVATE : FOLIOREF_KEEP;\n--\nmm/vmscan.c=3625=static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma,\n--\nmm/vmscan.c-3638-\tif (walk) {\nmm/vmscan.c:3639:\t\told_gen = folio_inc_lru_refs_walk(folio, lruvec, \u0026vma-\u003eflags, \u0026new_gen);\nmm/vmscan.c-3640-\t\tif (old_gen \u003e= 0)\n--\nmm/vmscan.c-3642-\t} else {\nmm/vmscan.c:3643:\t\tfolio_inc_lru_refs(folio, true, is_exec_file_folio(folio, \u0026vma-\u003eflags));\nmm/vmscan.c-3644-\t}\n"
} |
| 5/2 | 2026/08/03 21:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30463 output: 41 thoughts: 63 Thoughts: **Considering Potential Optimization Points** Okay, let's see, `folio_inc_lru_refs` seems like a promising starting point for optimization. But before I zero in completely, I want to explore other possibilities. Let's see... what about `madvise_cold_or_pageout_prep_folio`? Hmm, that's static, which could affect its impact, but it's in `mm/madvise.c`, so it's worth a look. I'll need to check the code itself to determine its relevance in greater detail. Right, let's head over to `mm/madvise.c` and take a closer look at that function. |
| 6/2 | 2026/08/03 21:26 | tool | grepper |
0mArgs:{
"Expression": "madvise_cold_or_pageout_prep_folio",
"PathPrefix": "mm/madvise.c"
}Results: {
"Output": "mm/madvise.c=343=static inline int madvise_folio_pte_batch(unsigned long addr, unsigned long end,\n--\nmm/madvise.c-358- */\nmm/madvise.c:359:static void madvise_cold_or_pageout_prep_folio(struct folio *folio)\nmm/madvise.c-360-{\n--\nmm/madvise.c=374=static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\n--\nmm/madvise.c-447-\nmm/madvise.c:448:\t\tmadvise_cold_or_pageout_prep_folio(folio);\nmm/madvise.c-449-\t\tif (pageout) {\n--\nmm/madvise.c-553-\nmm/madvise.c:554:\t\tmadvise_cold_or_pageout_prep_folio(folio);\nmm/madvise.c-555-\t\tif (pageout) {\n"
} |
| 7/2 | 2026/08/03 21:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30996 output: 41 thoughts: 52 Thoughts: Here's my summary, as the thought process unfolds: **Examining Memory Management Strategy** Okay, so I'm thinking about the most effective approach here for this memory management task. Based on my initial assessment, `madvise_cold_or_pageout_pte_range` seems like a strong contender. Its name alone suggests it's designed to handle scenarios where we're looking to mark memory as potentially cold, ripe for page-out, or otherwise indicate a low-priority state. Now I need to dive into the implementation to confirm my initial hypothesis. I need to take a closer look at the actual code and functionality. Let me quickly navigate over to `mm/madvise.c` and dig into `madvise_cold_or_pageout_pte_range` to see exactly what it's doing under the hood and how it handles these page table entries. |
| 8/2 | 2026/08/03 21:26 | tool | grepper |
0mArgs:{
"Expression": "madvise_cold_or_pageout_pte_range",
"PathPrefix": "mm/madvise.c"
}Results: {
"Output": "mm/madvise.c=359=static void madvise_cold_or_pageout_prep_folio(struct folio *folio)\n--\nmm/madvise.c-373-\nmm/madvise.c:374:static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\nmm/madvise.c-375-\t\t\t\tunsigned long addr, unsigned long end,\n--\nmm/madvise.c=577=static const struct mm_walk_ops cold_walk_ops = {\nmm/madvise.c:578:\t.pmd_entry = madvise_cold_or_pageout_pte_range,\nmm/madvise.c-579-\t.walk_lock = PGWALK_RDLOCK,\n"
} |
| 9/2 | 2026/08/03 21:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31489 output: 32 thoughts: 21 Thoughts: Okay, here's my understanding, tailored for an expert audience, and delivered in the first person: **Initial Thoughts on `madvise_cold` and `madvise_pageout`** Alright, so I'm immediately homing in on `madvise_cold` and `madvise_pageout`. They’re the entry points, the hooks into the system, the key functions we're dealing with here. My mind immediately starts mapping out the context...I'm thinking, these functions are likely used for managing memory, right? Maybe for hinting to the kernel about how we expect certain memory regions to behave. The 'cold' term suggests we are telling the system that memory is infrequently accessed and likely should be paged out to make better use of memory resources. The 'pageout' term would imply that we want to force or prioritize this paging activity, perhaps to alleviate memory pressure or simply to manage our process's memory footprint efficiently. |
| 10/2 | 2026/08/03 21:26 | tool | grepper |
0mArgs:{
"Expression": "madvise_cold",
"PathPrefix": "mm/madvise.c"
}Results: {
"Output": "mm/madvise.c=343=static inline int madvise_folio_pte_batch(unsigned long addr, unsigned long end,\n--\nmm/madvise.c-358- */\nmm/madvise.c:359:static void madvise_cold_or_pageout_prep_folio(struct folio *folio)\nmm/madvise.c-360-{\n--\nmm/madvise.c-373-\nmm/madvise.c:374:static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\nmm/madvise.c-375-\t\t\t\tunsigned long addr, unsigned long end,\n--\nmm/madvise.c-447-\nmm/madvise.c:448:\t\tmadvise_cold_or_pageout_prep_folio(folio);\nmm/madvise.c-449-\t\tif (pageout) {\n--\nmm/madvise.c-553-\nmm/madvise.c:554:\t\tmadvise_cold_or_pageout_prep_folio(folio);\nmm/madvise.c-555-\t\tif (pageout) {\n--\nmm/madvise.c=577=static const struct mm_walk_ops cold_walk_ops = {\nmm/madvise.c:578:\t.pmd_entry = madvise_cold_or_pageout_pte_range,\nmm/madvise.c-579-\t.walk_lock = PGWALK_RDLOCK,\n--\nmm/madvise.c-581-\nmm/madvise.c:582:static void madvise_cold_page_range(struct mmu_gather *tlb,\nmm/madvise.c-583-\t\tstruct madvise_behavior *madv_behavior)\n--\nmm/madvise.c=599=static inline bool can_madv_lru_vma(struct vm_area_struct *vma)\n--\nmm/madvise.c-603-\nmm/madvise.c:604:static long madvise_cold(struct madvise_behavior *madv_behavior)\nmm/madvise.c-605-{\n--\nmm/madvise.c-613-\ttlb_gather_mmu(\u0026tlb, madv_behavior-\u003emm);\nmm/madvise.c:614:\tmadvise_cold_page_range(\u0026tlb, madv_behavior);\nmm/madvise.c-615-\ttlb_finish_mmu(\u0026tlb);\n--\nmm/madvise.c=1347=static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)\n--\nmm/madvise.c-1363-\tcase MADV_COLD:\nmm/madvise.c:1364:\t\treturn madvise_cold(madv_behavior);\nmm/madvise.c-1365-\tcase MADV_PAGEOUT:\n"
} |
| 11/2 | 2026/08/03 21:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32521 output: 34 thoughts: 45 Thoughts: Okay, here's my interpretation of those thoughts, framed as my own: **Analyzing Memory Management Strategies** Alright, let's zero in on this performance issue. Looking at the situation, `madvise_cold` and `madvise_pageout` are certainly promising starting points. They offer direct control over the kernel's memory management. I suspect they have an effect on page cache and swapping. To understand their implications more precisely, I should dig deeper. Now, let's explore `folio_inc_lru_refs` in `mm/vmscan.c`. It's a key function for managing LRU references for the Folio architecture, especially in newer kernels, and I believe that examining the interactions within this function can explain how the kernel responds to these memory hints. The goal is to see how the system reacts to these "cold" and "pageout" hints and where the critical path lies. Understanding this will give insight into whether the suggestions provided in the instructions are correct. |
| 12/2 | 2026/08/03 21:26 | tool | grepper |
0mArgs:{
"Expression": "folio_inc_lru_refs",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=826=enum folio_references {\n--\nmm/vmscan.c-837-/*\nmm/vmscan.c:838: * The folio_inc_lru_refs{_*} helpers below collect the referenced info\nmm/vmscan.c-839- * (hotness) from other parts, including the page table walker, the rmap walk\n--\nmm/vmscan.c-870- */\nmm/vmscan.c:871:int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec)\nmm/vmscan.c-872-{\n--\nmm/vmscan.c-946- */\nmm/vmscan.c:947:static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lruvec,\nmm/vmscan.c-948-\t\t\t\t const vma_flags_t *vma_flags, int *new_gen)\n--\nmm/vmscan.c-987- */\nmm/vmscan.c:988:static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)\nmm/vmscan.c-989-{\n--\nmm/vmscan.c-1002-#else\nmm/vmscan.c:1003:static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_flags_t *vma_flags)\nmm/vmscan.c-1004-{\n--\nmm/vmscan.c=1009=static enum folio_references folio_check_references(struct folio *folio,\n--\nmm/vmscan.c-1037-\nmm/vmscan.c:1038:\t\treturn folio_inc_lru_refs_isolated(folio, \u0026vma_flags) ?\nmm/vmscan.c-1039-\t\t FOLIOREF_ACTIVATE : FOLIOREF_KEEP;\n--\nmm/vmscan.c=3625=static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma,\n--\nmm/vmscan.c-3638-\tif (walk) {\nmm/vmscan.c:3639:\t\told_gen = folio_inc_lru_refs_walk(folio, lruvec, \u0026vma-\u003eflags, \u0026new_gen);\nmm/vmscan.c-3640-\t\tif (old_gen \u003e= 0)\n--\nmm/vmscan.c-3642-\t} else {\nmm/vmscan.c:3643:\t\tfolio_inc_lru_refs(folio, true, is_exec_file_folio(folio, \u0026vma-\u003eflags));\nmm/vmscan.c-3644-\t}\n"
} |
| 13/2 | 2026/08/03 21:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33533 output: 30 thoughts: 48 Thoughts: Okay, let's break this down. **Investigating `folio_inc_lru_refs` Usage** Right, so `folio_inc_lru_refs` is definitely a key function. I need to understand its role, and the context of its calls. I see it's called from `walk_update_folio` and `folio_mark_accessed`. That makes sense. `walk_update_folio` is part of the page table walking process, so it's probably involved in updating references as we traverse. `folio_mark_accessed`... that suggests something about access tracking and the LRU, which is right in my wheelhouse. Given this, `folio_mark_accessed` in `mm/folio.c` is the best place to start. Let's dig into that and see how `folio_inc_lru_refs` is being used and what information I can gather. I'm expecting to find insights into how access patterns influence the LRU list management. |
| 14/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "folio_mark_accessed",
"PathPrefix": "mm/folio.c"
}Results: {
"Output": "mm/folio.c=373=static bool lru_gen_clear_refs(struct folio *folio)\n--\nmm/folio.c-380-/**\nmm/folio.c:381: * folio_mark_accessed - Mark a folio as having seen activity.\nmm/folio.c-382- * @folio: The folio to mark.\n--\nmm/folio.c-390- * When a newly allocated folio is not yet visible, so safe for non-atomic ops,\nmm/folio.c:391: * __folio_set_referenced() may be substituted for folio_mark_accessed().\nmm/folio.c-392- */\nmm/folio.c:393:void folio_mark_accessed(struct folio *folio)\nmm/folio.c-394-{\n--\nmm/folio.c-427-}\nmm/folio.c:428:EXPORT_SYMBOL(folio_mark_accessed);\nmm/folio.c-429-\n--\nmm/folio.c-436- * folio_batch is drained. This gives a chance for the caller of folio_add_lru()\nmm/folio.c:437: * have the folio added to the active list using folio_mark_accessed().\nmm/folio.c-438- */\n"
} |
| 15/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34150 output: 38 thoughts: 42 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Investigating Folio Access Patterns** Hmm, I'm noticing `folio_mark_accessed` is getting a lot of use lately. That's interesting, makes sense given the focus on efficient memory management. Now, I'm curious about something related... what's the deal with `folio_inc_lru_refs`? I seem to recall seeing that exported as well. I wonder how it relates to this access marking. Is it used in conjunction? Let's quickly double-check where that is defined... right, `mm/vmscan.c`. Gotta see if there's any immediate correlation there and how the two interact. That file's going to hold the key to understanding the interplay between those functions and the scanning process. |
| 16/2 | 2026/08/03 21:27 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*folio_inc_lru_refs",
"PathPrefix": "mm/vmscan.c"
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34423 output: 37 thoughts: 29 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience: **Analyzing Memory Management Code** Hmm, this variable's definition isn't directly exposed in a header file, so it's not a simple static value. That likely means it's calculated dynamically or influenced by compile-time configurations. To understand its nature and how it's being used, the best place to start looking is `include/linux/mm_inline.h`. That file often contains crucial inline functions and macros related to memory management within the kernel, making it the most likely location for the relevant definitions and calculations. I expect I'll find the answer there, even if it's subtle. |
| 18/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "folio_inc_lru_refs",
"PathPrefix": "include/linux/mm_inline.h"
}Results: {
"Output": "include/linux/mm_inline.h=137=static inline void folio_set_lru_refs(struct folio *folio, unsigned int refs)\n--\ninclude/linux/mm_inline.h-146-\ninclude/linux/mm_inline.h:147:int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec);\ninclude/linux/mm_inline.h-148-\n--\ninclude/linux/mm_inline.h=388=static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec,\n--\ninclude/linux/mm_inline.h-399-\t * | Accessed through page tables and | Accessed through file descriptors |\ninclude/linux/mm_inline.h:400:\t * | promoted by folio_inc_lru_refs_walk() | protected by folio_inc_lru_refs/inc_gen |\ninclude/linux/mm_inline.h-401-\t * +------------------------------------------+------------------------------------------+\n"
} |
| 19/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34905 output: 29 thoughts: 83 Thoughts: **Analyzing the Memory Management Patch** Okay, so I'm looking at this patch and immediately, `folio_inc_lru_refs` jumps out at me as a key area of focus. That's usually a good indicator of changes related to LRU reference counting, which is core to the paging system. I'm also seeing `madvise_cold` and `madvise_pageout` mentioned, so those are definitely worth a closer look as they deal with memory hinting and eviction. My initial thought is to see if any new configuration options are needed or introduced with this change. It's crucial to understand the impact on system behavior. It seems like this patch is heavily modifying `#ifdef CONFIG_LRU_GEN` blocks, so that's a major clue. I'll need to check the `CONFIG_LRU_GEN` Kconfig option in `mm/Kconfig` to see exactly what this feature controls and understand the context of the modifications. Gotta dive deep into the dependencies and what scenarios are impacted. |
| 20/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "config LRU_GEN",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=1390=config PTE_MARKER_UFFD_WP\n--\nmm/Kconfig-1400-# multi-gen LRU {\nmm/Kconfig:1401:config LRU_GEN\nmm/Kconfig-1402-\tbool \"Multi-Gen LRU\"\n--\nmm/Kconfig-1409-\nmm/Kconfig:1410:config LRU_GEN_ENABLED\nmm/Kconfig-1411-\tbool \"Enable by default\"\n--\nmm/Kconfig-1415-\nmm/Kconfig:1416:config LRU_GEN_STATS\nmm/Kconfig-1417-\tbool \"Full stats for debugging\"\n--\nmm/Kconfig-1424-\nmm/Kconfig:1425:config LRU_GEN_WALKS_MMU\nmm/Kconfig-1426-\tdef_bool y\n"
} |
| 21/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35474 output: 26 thoughts: 33 Thoughts: Okay, here's my take on those thoughts, framed as my own reasoning: **Checking LRU Configuration** Right, let's make sure we've got the Least Recently Used (LRU) generation stuff enabled. I'll need to confirm that `LRU_GEN` and `LRU_GEN_ENABLED` are, in fact, turned on. Now, before I proceed further, I want to double-check `LRU_GEN`'s dependencies. I need to understand what other components or features it relies upon to ensure everything will function correctly and without any unexpected conflicts. I'll need to dive into those dependencies to get a complete picture. |
| 22/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "depends on",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=12=menuconfig SWAP\nmm/Kconfig-13-\tbool \"Support for paging of anonymous memory (swap)\"\nmm/Kconfig:14:\tdepends on MMU \u0026\u0026 BLOCK \u0026\u0026 !ARCH_NO_SWAP\nmm/Kconfig-15-\tdefault y\n--\nmm/Kconfig=22=config ZSWAP\nmm/Kconfig-23-\tbool \"Compressed cache for swap pages\"\nmm/Kconfig:24:\tdepends on SWAP\nmm/Kconfig-25-\tselect CRYPTO\n--\nmm/Kconfig=35=config ZSWAP_DEFAULT_ON\nmm/Kconfig-36-\tbool \"Enable the compressed cache for swap pages by default\"\nmm/Kconfig:37:\tdepends on ZSWAP\nmm/Kconfig-38-\thelp\n--\nmm/Kconfig=45=config ZSWAP_SHRINKER_DEFAULT_ON\nmm/Kconfig-46-\tbool \"Shrink the zswap pool on memory pressure\"\nmm/Kconfig:47:\tdepends on ZSWAP\nmm/Kconfig-48-\tdefault n\n--\nmm/Kconfig=59=choice\nmm/Kconfig-60-\tprompt \"Default compressor\"\nmm/Kconfig:61:\tdepends on ZSWAP\nmm/Kconfig-62-\tdefault ZSWAP_COMPRESSOR_DEFAULT_LZO\n--\nmm/Kconfig=114=config ZSWAP_COMPRESSOR_DEFAULT\nmm/Kconfig-115- string\nmm/Kconfig:116: depends on ZSWAP\nmm/Kconfig-117- default \"deflate\" if ZSWAP_COMPRESSOR_DEFAULT_DEFLATE\n--\nmm/Kconfig=128=menu \"Zsmalloc allocator options\"\nmm/Kconfig:129:\tdepends on ZSMALLOC\nmm/Kconfig-130-\n--\nmm/Kconfig=168=config KVFREE_RCU_BATCHED\nmm/Kconfig-169-\tdef_bool y\nmm/Kconfig:170:\tdepends on !SLUB_TINY \u0026\u0026 !TINY_RCU\nmm/Kconfig:171:\tdepends on !RCU_STRICT_GRACE_PERIOD\nmm/Kconfig-172-\nmm/Kconfig=173=config SLUB_TINY\nmm/Kconfig-174-\tbool \"Configure for minimal memory footprint\"\nmm/Kconfig:175:\tdepends on EXPERT \u0026\u0026 !COMPILE_TEST\nmm/Kconfig-176-\tselect SLAB_MERGE_DEFAULT\n--\nmm/Kconfig=200=config SLAB_FREELIST_RANDOM\nmm/Kconfig-201-\tbool \"Randomize slab freelist\"\nmm/Kconfig:202:\tdepends on !SLUB_TINY\nmm/Kconfig-203-\thelp\n--\nmm/Kconfig=208=config SLAB_FREELIST_HARDENED\nmm/Kconfig-209-\tbool \"Harden slab freelist metadata\"\nmm/Kconfig:210:\tdepends on !SLUB_TINY\nmm/Kconfig-211-\thelp\n--\nmm/Kconfig=217=config SLAB_BUCKETS\nmm/Kconfig-218-\tbool \"Support allocation from separate kmalloc buckets\"\nmm/Kconfig:219:\tdepends on !SLUB_TINY\nmm/Kconfig-220-\tdefault SLAB_FREELIST_HARDENED\n--\nmm/Kconfig=234=config SLUB_STATS\n--\nmm/Kconfig-236-\tbool \"Enable performance statistics\"\nmm/Kconfig:237:\tdepends on SYSFS \u0026\u0026 !SLUB_TINY\nmm/Kconfig-238-\thelp\n--\nmm/Kconfig=247=config KMALLOC_PARTITION_CACHES\nmm/Kconfig:248:\tdepends on !SLUB_TINY\nmm/Kconfig-249-\tbool \"Partitioned slab caches for normal kmalloc\"\n--\nmm/Kconfig=263=choice\nmm/Kconfig-264-\tprompt \"Partitioned slab cache mode\"\nmm/Kconfig:265:\tdepends on KMALLOC_PARTITION_CACHES\nmm/Kconfig-266-\tdefault KMALLOC_PARTITION_TYPED if CC_HAS_ALLOC_TOKEN\n--\nmm/Kconfig=283=config KMALLOC_PARTITION_TYPED\nmm/Kconfig-284-\tbool \"Type based slab cache selection for normal kmalloc\"\nmm/Kconfig:285:\tdepends on CC_HAS_ALLOC_TOKEN\nmm/Kconfig-286-\thelp\n--\nmm/Kconfig=354=config MMAP_ALLOW_UNINITIALIZED\nmm/Kconfig-355-\tbool \"Allow mmapped anonymous memory to be uninitialized\"\nmm/Kconfig:356:\tdepends on EXPERT \u0026\u0026 !MMU\nmm/Kconfig-357-\tdefault n\n--\nmm/Kconfig=376=config SELECT_MEMORY_MODEL\nmm/Kconfig-377-\tdef_bool y\nmm/Kconfig:378:\tdepends on ARCH_SELECT_MEMORY_MODEL\nmm/Kconfig-379-\nmm/Kconfig=380=choice\nmm/Kconfig-381-\tprompt \"Memory model\"\nmm/Kconfig:382:\tdepends on SELECT_MEMORY_MODEL\nmm/Kconfig-383-\tdefault SPARSEMEM_MANUAL if ARCH_SPARSEMEM_DEFAULT\n--\nmm/Kconfig=391=config FLATMEM_MANUAL\nmm/Kconfig-392-\tbool \"Flat Memory\"\nmm/Kconfig:393:\tdepends on !ARCH_SPARSEMEM_ENABLE || ARCH_FLATMEM_ENABLE\nmm/Kconfig-394-\thelp\n--\nmm/Kconfig=406=config SPARSEMEM_MANUAL\nmm/Kconfig-407-\tbool \"Sparse Memory\"\nmm/Kconfig:408:\tdepends on ARCH_SPARSEMEM_ENABLE\nmm/Kconfig-409-\thelp\n--\nmm/Kconfig=421=config SPARSEMEM\nmm/Kconfig-422-\tdef_bool y\nmm/Kconfig:423:\tdepends on (!SELECT_MEMORY_MODEL \u0026\u0026 ARCH_SPARSEMEM_ENABLE) || SPARSEMEM_MANUAL\nmm/Kconfig-424-\nmm/Kconfig=425=config FLATMEM\nmm/Kconfig-426-\tdef_bool y\nmm/Kconfig:427:\tdepends on !SPARSEMEM || FLATMEM_MANUAL\nmm/Kconfig-428-\n--\nmm/Kconfig=447=config SPARSEMEM_EXTREME\nmm/Kconfig-448-\tdef_bool y\nmm/Kconfig:449:\tdepends on SPARSEMEM \u0026\u0026 !SPARSEMEM_STATIC\nmm/Kconfig-450-\n--\nmm/Kconfig=454=config SPARSEMEM_VMEMMAP\nmm/Kconfig-455-\tdef_bool y\nmm/Kconfig:456:\tdepends on SPARSEMEM \u0026\u0026 SPARSEMEM_VMEMMAP_ENABLE\nmm/Kconfig-457-\thelp\n--\nmm/Kconfig=480=config HAVE_GUP_FAST\nmm/Kconfig:481:\tdepends on MMU\nmm/Kconfig-482-\tbool\n--\nmm/Kconfig=504=config EXCLUSIVE_SYSTEM_RAM\nmm/Kconfig-505-\tdef_bool y\nmm/Kconfig:506:\tdepends on !DEVMEM || STRICT_DEVMEM\nmm/Kconfig-507-\n--\nmm/Kconfig=512=menuconfig MEMORY_HOTPLUG\n--\nmm/Kconfig-514-\tselect MEMORY_ISOLATION\nmm/Kconfig:515:\tdepends on SPARSEMEM_VMEMMAP\nmm/Kconfig:516:\tdepends on ARCH_ENABLE_MEMORY_HOTPLUG\nmm/Kconfig:517:\tdepends on 64BIT\nmm/Kconfig-518-\tselect NUMA_KEEP_MEMINFO if NUMA\n--\nmm/Kconfig=584=config MHP_MEMMAP_ON_MEMORY\nmm/Kconfig-585-\tdef_bool y\nmm/Kconfig:586:\tdepends on ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE\nmm/Kconfig-587-\n--\nmm/Kconfig=590=config ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE\n--\nmm/Kconfig-596-# Default to 4 for wider testing, though 8 might be more appropriate.\nmm/Kconfig:597:# ARM's adjust_pte (unused if VIPT) depends on mm-wide page_table_lock.\nmm/Kconfig-598-# PA-RISC 7xxx's spinlock_t would enlarge struct page from 32 to 44 bytes.\n--\nmm/Kconfig=604=config SPLIT_PTE_PTLOCKS\nmm/Kconfig-605-\tdef_bool y\nmm/Kconfig:606:\tdepends on MMU\nmm/Kconfig:607:\tdepends on SMP\nmm/Kconfig:608:\tdepends on NR_CPUS \u003e= 4\nmm/Kconfig:609:\tdepends on !ARM || CPU_CACHE_VIPT\nmm/Kconfig:610:\tdepends on !PARISC || PA20\nmm/Kconfig:611:\tdepends on !SPARC32\nmm/Kconfig:612:\tdepends on !UML\nmm/Kconfig-613-\n--\nmm/Kconfig=617=config SPLIT_PMD_PTLOCKS\nmm/Kconfig-618-\tdef_bool y\nmm/Kconfig:619:\tdepends on SPLIT_PTE_PTLOCKS \u0026\u0026 ARCH_ENABLE_SPLIT_PMD_PTLOCK\nmm/Kconfig-620-\n--\nmm/Kconfig=628=config BALLOON_MIGRATION\n--\nmm/Kconfig-630-\tdefault y\nmm/Kconfig:631:\tdepends on MIGRATION \u0026\u0026 BALLOON\nmm/Kconfig-632-\thelp\n--\nmm/Kconfig=640=config COMPACTION\n--\nmm/Kconfig-643-\tselect MIGRATION\nmm/Kconfig:644:\tdepends on MMU\nmm/Kconfig-645-\thelp\n--\nmm/Kconfig=655=config COMPACT_UNEVICTABLE_DEFAULT\nmm/Kconfig-656-\tint\nmm/Kconfig:657:\tdepends on COMPACTION\nmm/Kconfig-658-\tdefault 0 if PREEMPT_RT\n--\nmm/Kconfig=671=config NUMA_MIGRATION\n--\nmm/Kconfig-673-\tdefault y\nmm/Kconfig:674:\tdepends on NUMA \u0026\u0026 MMU\nmm/Kconfig-675-\tselect MIGRATION\n--\nmm/Kconfig=682=config MIGRATION\nmm/Kconfig-683-\tbool\nmm/Kconfig:684:\tdepends on MMU\nmm/Kconfig-685-\n--\nmm/Kconfig=726=config KSM\nmm/Kconfig-727-\tbool \"Enable KSM for page merging\"\nmm/Kconfig:728:\tdepends on MMU\nmm/Kconfig-729-\tselect XXHASH\n--\nmm/Kconfig=741=config DEFAULT_MMAP_MIN_ADDR\nmm/Kconfig-742-\tint \"Low address space to protect from user allocation\"\nmm/Kconfig:743:\tdepends on MMU\nmm/Kconfig-744-\tdefault 4096\n--\nmm/Kconfig=763=config MEMORY_FAILURE\nmm/Kconfig:764:\tdepends on MMU\nmm/Kconfig:765:\tdepends on ARCH_SUPPORTS_MEMORY_FAILURE\nmm/Kconfig-766-\tbool \"Enable recovery from hardware memory errors\"\n--\nmm/Kconfig=774=config HWPOISON_INJECT\nmm/Kconfig-775-\ttristate \"HWPoison pages injector\"\nmm/Kconfig:776:\tdepends on MEMORY_FAILURE \u0026\u0026 DEBUG_KERNEL \u0026\u0026 PROC_FS\nmm/Kconfig-777-\tselect PROC_PAGE_MONITOR\n--\nmm/Kconfig=779=config NOMMU_INITIAL_TRIM_EXCESS\nmm/Kconfig-780-\tint \"Turn on mmap() excess space trimming before booting\"\nmm/Kconfig:781:\tdepends on !MMU\nmm/Kconfig-782-\tdefault 1\n--\nmm/Kconfig=813=config PERSISTENT_HUGE_ZERO_FOLIO\nmm/Kconfig-814-\tbool \"Allocate a PMD sized folio for zeroing\"\nmm/Kconfig:815:\tdepends on TRANSPARENT_HUGEPAGE\nmm/Kconfig-816-\thelp\n--\nmm/Kconfig=832=menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-833-\tbool \"Transparent Hugepage Support\"\nmm/Kconfig:834:\tdepends on HAVE_ARCH_TRANSPARENT_HUGEPAGE \u0026\u0026 !PREEMPT_RT\nmm/Kconfig-835-\tselect COMPACTION\n--\nmm/Kconfig=968=config THP_SWAP\nmm/Kconfig-969-\tdef_bool y\nmm/Kconfig:970:\tdepends on ARCH_WANTS_THP_SWAP \u0026\u0026 SWAP \u0026\u0026 64BIT\nmm/Kconfig-971-\thelp\n--\nmm/Kconfig=1014=config ARCH_SUPPORTS_HUGE_PFNMAP\nmm/Kconfig-1015-\tdef_bool n\nmm/Kconfig:1016:\tdepends on TRANSPARENT_HUGEPAGE\nmm/Kconfig-1017-\nmm/Kconfig=1018=config ARCH_SUPPORTS_PMD_PFNMAP\nmm/Kconfig-1019-\tdef_bool y\nmm/Kconfig:1020:\tdepends on ARCH_SUPPORTS_HUGE_PFNMAP \u0026\u0026 HAVE_ARCH_TRANSPARENT_HUGEPAGE\nmm/Kconfig-1021-\nmm/Kconfig=1022=config ARCH_SUPPORTS_PUD_PFNMAP\nmm/Kconfig-1023-\tdef_bool y\nmm/Kconfig:1024:\tdepends on ARCH_SUPPORTS_HUGE_PFNMAP \u0026\u0026 HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD\nmm/Kconfig-1025-\n--\nmm/Kconfig=1036=config NEED_PER_CPU_KM\nmm/Kconfig:1037:\tdepends on !SMP || !MMU\nmm/Kconfig-1038-\tbool\n--\nmm/Kconfig=1053=config CMA\nmm/Kconfig-1054-\tbool \"Contiguous Memory Allocator\"\nmm/Kconfig:1055:\tdepends on MMU\nmm/Kconfig-1056-\tselect MIGRATION\n--\nmm/Kconfig=1068=config CMA_DEBUGFS\nmm/Kconfig-1069-\tbool \"CMA debugfs interface\"\nmm/Kconfig:1070:\tdepends on CMA \u0026\u0026 DEBUG_FS\nmm/Kconfig-1071-\thelp\n--\nmm/Kconfig=1074=config CMA_SYSFS\nmm/Kconfig-1075-\tbool \"CMA information through sysfs interface\"\nmm/Kconfig:1076:\tdepends on CMA \u0026\u0026 SYSFS\nmm/Kconfig-1077-\thelp\n--\nmm/Kconfig=1081=config CMA_AREAS\nmm/Kconfig-1082-\tint \"Maximum count of the CMA areas\"\nmm/Kconfig:1083:\tdepends on CMA\nmm/Kconfig-1084-\tdefault 20 if NUMA\n--\nmm/Kconfig=1128=config MEM_SOFT_DIRTY\nmm/Kconfig-1129-\tbool \"Track memory changes\"\nmm/Kconfig:1130:\tdepends on CHECKPOINT_RESTORE \u0026\u0026 HAVE_ARCH_SOFT_DIRTY \u0026\u0026 PROC_FS\nmm/Kconfig-1131-\tselect PROC_PAGE_MONITOR\n--\nmm/Kconfig=1143=config STACK_MAX_DEFAULT_SIZE_MB\n--\nmm/Kconfig-1146-\trange 8 2048\nmm/Kconfig:1147:\tdepends on STACK_GROWSUP \u0026\u0026 (!64BIT || COMPAT)\nmm/Kconfig-1148-\thelp\n--\nmm/Kconfig=1155=config DEFERRED_STRUCT_PAGE_INIT\nmm/Kconfig-1156-\tbool \"Defer initialisation of struct pages to kthreads\"\nmm/Kconfig:1157:\tdepends on SPARSEMEM\nmm/Kconfig:1158:\tdepends on !NEED_PER_CPU_KM\nmm/Kconfig:1159:\tdepends on 64BIT\nmm/Kconfig:1160:\tdepends on !KMSAN\nmm/Kconfig-1161-\tselect PADATA\n--\nmm/Kconfig=1179=config IDLE_PAGE_TRACKING\nmm/Kconfig-1180-\tbool \"Enable idle page tracking\"\nmm/Kconfig:1181:\tdepends on SYSFS \u0026\u0026 MMU\nmm/Kconfig-1182-\tselect PAGE_IDLE_FLAG\n--\nmm/Kconfig=1217=config ZONE_DMA32\nmm/Kconfig-1218-\tbool \"Support DMA32 zone\" if ARCH_HAS_ZONE_DMA_SET\nmm/Kconfig:1219:\tdepends on !X86_32\nmm/Kconfig-1220-\tdefault y if ARM64\n--\nmm/Kconfig=1222=config ZONE_DEVICE\nmm/Kconfig-1223-\tbool \"Device memory (pmem, HMM, etc...) hotplug support\"\nmm/Kconfig:1224:\tdepends on MEMORY_HOTREMOVE\nmm/Kconfig-1225-\tselect XARRAY_MULTI\n--\nmm/Kconfig=1244=config HMM_MIRROR\nmm/Kconfig-1245-\tbool\nmm/Kconfig:1246:\tdepends on MMU\nmm/Kconfig-1247-\tselect MMU_NOTIFIER\n--\nmm/Kconfig=1252=config DEVICE_PRIVATE\nmm/Kconfig-1253-\tbool \"Unaddressable device memory (GPU memory, ...)\"\nmm/Kconfig:1254:\tdepends on ZONE_DEVICE\nmm/Kconfig-1255-\tselect GET_FREE_REGION\n--\nmm/Kconfig=1291=config GUP_TEST\nmm/Kconfig-1292-\tbool \"Enable infrastructure for get_user_pages()-related unit tests\"\nmm/Kconfig:1293:\tdepends on DEBUG_FS\nmm/Kconfig-1294-\thelp\n--\nmm/Kconfig=1311=comment \"GUP_TEST needs to have DEBUG_FS enabled\"\nmm/Kconfig:1312:\tdepends on !GUP_TEST \u0026\u0026 !DEBUG_FS\nmm/Kconfig-1313-\n--\nmm/Kconfig=1317=config DMAPOOL_TEST\nmm/Kconfig-1318-\ttristate \"Enable a module to run time tests on dma_pool\"\nmm/Kconfig:1319:\tdepends on HAS_DMA\nmm/Kconfig-1320-\thelp\n--\nmm/Kconfig=1341=config SECRETMEM\n--\nmm/Kconfig-1343-\tbool \"Enable memfd_secret() system call\" if EXPERT\nmm/Kconfig:1344:\tdepends on ARCH_HAS_SET_DIRECT_MAP\nmm/Kconfig-1345-\thelp\n--\nmm/Kconfig=1350=config ANON_VMA_NAME\nmm/Kconfig-1351-\tbool \"Anonymous VMA name support\"\nmm/Kconfig:1352:\tdepends on PROC_FS \u0026\u0026 ADVISE_SYSCALLS \u0026\u0026 MMU\nmm/Kconfig-1353-\n--\nmm/Kconfig=1374=config USERFAULTFD_RWP\nmm/Kconfig-1375-\tdef_bool y\nmm/Kconfig:1376:\tdepends on 64BIT \u0026\u0026 ARCH_HAS_PTE_PROTNONE \u0026\u0026 HAVE_ARCH_USERFAULTFD_WP\nmm/Kconfig-1377-\thelp\n--\nmm/Kconfig=1383=menuconfig USERFAULTFD\nmm/Kconfig-1384-\tbool \"Enable userfaultfd() system call\"\nmm/Kconfig:1385:\tdepends on MMU\nmm/Kconfig-1386-\thelp\n--\nmm/Kconfig=1390=config PTE_MARKER_UFFD_WP\n--\nmm/Kconfig-1392-\tdefault y\nmm/Kconfig:1393:\tdepends on USERFAULTFD \u0026\u0026 HAVE_ARCH_USERFAULTFD_WP\nmm/Kconfig-1394-\n--\nmm/Kconfig=1401=config LRU_GEN\nmm/Kconfig-1402-\tbool \"Multi-Gen LRU\"\nmm/Kconfig:1403:\tdepends on MMU\nmm/Kconfig-1404-\t# make sure folio-\u003eflags has enough spare bits\nmm/Kconfig:1405:\tdepends on 64BIT || !SPARSEMEM || SPARSEMEM_VMEMMAP\nmm/Kconfig-1406-\thelp\n--\nmm/Kconfig=1410=config LRU_GEN_ENABLED\nmm/Kconfig-1411-\tbool \"Enable by default\"\nmm/Kconfig:1412:\tdepends on LRU_GEN\nmm/Kconfig-1413-\thelp\n--\nmm/Kconfig=1416=config LRU_GEN_STATS\nmm/Kconfig-1417-\tbool \"Full stats for debugging\"\nmm/Kconfig:1418:\tdepends on LRU_GEN\nmm/Kconfig-1419-\thelp\n--\nmm/Kconfig=1425=config LRU_GEN_WALKS_MMU\nmm/Kconfig-1426-\tdef_bool y\nmm/Kconfig:1427:\tdepends on LRU_GEN \u0026\u0026 ARCH_HAS_HW_PTE_YOUNG\nmm/Kconfig-1428-# }\n--\nmm/Kconfig=1433=config PER_VMA_LOCK\nmm/Kconfig-1434-\tdef_bool y\nmm/Kconfig:1435:\tdepends on ARCH_SUPPORTS_PER_VMA_LOCK \u0026\u0026 MMU \u0026\u0026 SMP\nmm/Kconfig-1436-\thelp\n--\nmm/Kconfig=1442=config LOCK_MM_AND_FIND_VMA\nmm/Kconfig-1443-\tbool\nmm/Kconfig:1444:\tdepends on !STACK_GROWSUP\nmm/Kconfig-1445-\n--\nmm/Kconfig=1455=config NUMA_EMU\nmm/Kconfig-1456-\tbool \"NUMA emulation\"\nmm/Kconfig:1457:\tdepends on NUMA_MEMBLKS\nmm/Kconfig:1458:\tdepends on X86 || GENERIC_ARCH_NUMA\nmm/Kconfig-1459-\thelp\n--\nmm/Kconfig=1473=config PT_RECLAIM\nmm/Kconfig-1474-\tdef_bool y\nmm/Kconfig:1475:\tdepends on MMU_GATHER_RCU_TABLE_FREE \u0026\u0026 !HAVE_ARCH_TLB_REMOVE_TABLE\nmm/Kconfig-1476-\thelp\n--\nmm/Kconfig=1492=config LAZY_MMU_MODE_KUNIT_TEST\nmm/Kconfig-1493-\ttristate \"KUnit tests for the lazy MMU mode\" if !KUNIT_ALL_TESTS\nmm/Kconfig:1494:\tdepends on ARCH_HAS_LAZY_MMU_MODE\nmm/Kconfig:1495:\tdepends on KUNIT\nmm/Kconfig-1496-\tdefault KUNIT_ALL_TESTS\n"
} |
| 23/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41768 output: 27 thoughts: 70 Thoughts: **Examining Potential Configuration Options** Okay, so I'm looking at these changes, and my initial thought is that we definitely need to have `LRU_GEN` and `LRU_GEN_ENABLED` enabled. Those seem pretty fundamental based on the code involved. Beyond that, I'm seeing modifications touching `include/linux/memcontrol.h` and `mm/memcontrol.c`. That definitely screams `MEMCG` to me. There's a strong chance this change is interacting with memory control groups. To be absolutely sure, I'm going to quickly check the `CONFIG_MEMCG` definition in `init/Kconfig` to confirm if it's implicated here. I just want to make certain that I'm considering all relevant configurations. |
| 24/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "config MEMCG",
"PathPrefix": "init/Kconfig"
}Results: {
"Output": "init/Kconfig=1074=config CGROUP_FAVOR_DYNMODS\n--\ninit/Kconfig-1083-\ninit/Kconfig:1084:config MEMCG\ninit/Kconfig-1085-\tbool \"Memory controller\"\n--\ninit/Kconfig-1092-\ninit/Kconfig:1093:config MEMCG_NMI_UNSAFE\ninit/Kconfig-1094-\tbool\n--\ninit/Kconfig-1099-\ninit/Kconfig:1100:config MEMCG_NMI_SAFETY_REQUIRES_ATOMIC\ninit/Kconfig-1101-\tbool\n--\ninit/Kconfig-1106-\ninit/Kconfig:1107:config MEMCG_V1\ninit/Kconfig-1108-\tbool \"Legacy cgroup v1 memory controller\"\n"
} |
| 25/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 42255 output: 26 thoughts: 23 Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested: **Assessing Memory Control Group Implementation** Alright, so `MEMCG` – the memory control group feature – that’s a good starting point for resource management. Definitely something to keep an eye on when I'm reviewing this code. However, before I dive too deep into its specific implementation, I need to understand its relationship with other kernel configurations. I'm thinking, let's take a quick look at `CONFIG_MEMCG`'s dependencies. I need to make sure I grasp any other potentially related options, to assess the broader picture here. I should know what else might be affected. This will help me build the complete architecture of this particular feature. |
| 26/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "depends on",
"PathPrefix": "init/Kconfig"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 586 lines.\nUse more precise expression if possible.\n\ninit/Kconfig=63=config RUSTC_VERSION\n--\ninit/Kconfig-67-\t It does not depend on `RUST` since that one may need to use the version\ninit/Kconfig:68:\t in a `depends on`.\ninit/Kconfig-69-\n--\ninit/Kconfig=111=config GCC_ASM_GOTO_OUTPUT_BROKEN\ninit/Kconfig-112-\tbool\ninit/Kconfig:113:\tdepends on CC_IS_GCC\ninit/Kconfig-114-\tdefault y if GCC_VERSION \u003c 110500\n--\ninit/Kconfig=118=config CC_HAS_ASM_GOTO_OUTPUT\ninit/Kconfig-119-\tdef_bool y\ninit/Kconfig:120:\tdepends on !GCC_ASM_GOTO_OUTPUT_BROKEN\ninit/Kconfig:121:\tdepends on $(success,echo 'int foo(int x) { asm goto (\"\": \"=r\"(x) ::: bar); return x; bar: return 0; }' | $(CC) -x c - -c -o /dev/null)\ninit/Kconfig-122-\ninit/Kconfig=123=config CC_HAS_ASM_GOTO_TIED_OUTPUT\ninit/Kconfig:124:\tdepends on CC_HAS_ASM_GOTO_OUTPUT\ninit/Kconfig-125-\t# Detect buggy gcc and clang, fixed in gcc-11 clang-14.\n--\ninit/Kconfig=228=config BROKEN_ON_SMP\ninit/Kconfig-229-\tbool\ninit/Kconfig:230:\tdepends on BROKEN || !SMP\ninit/Kconfig-231-\tdefault y\n--\ninit/Kconfig=241=config COMPILE_TEST\ninit/Kconfig-242-\tbool \"Compile also drivers which will not load\"\ninit/Kconfig:243:\tdepends on HAS_IOMEM\ninit/Kconfig-244-\thelp\n--\ninit/Kconfig=272=config UAPI_HEADER_TEST\ninit/Kconfig-273-\tbool \"Compile test UAPI headers\"\ninit/Kconfig:274:\tdepends on HEADERS_INSTALL\ninit/Kconfig-275-\thelp\n--\ninit/Kconfig=292=config LOCALVERSION_AUTO\n--\ninit/Kconfig-294-\tdefault y\ninit/Kconfig:295:\tdepends on !COMPILE_TEST\ninit/Kconfig-296-\thelp\n--\ninit/Kconfig=346=choice\n--\ninit/Kconfig-348-\tdefault KERNEL_GZIP\ninit/Kconfig:349:\tdepends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED\ninit/Kconfig-350-\thelp\n--\ninit/Kconfig=368=config KERNEL_GZIP\ninit/Kconfig-369-\tbool \"Gzip\"\ninit/Kconfig:370:\tdepends on HAVE_KERNEL_GZIP\ninit/Kconfig-371-\thelp\n--\ninit/Kconfig=375=config KERNEL_BZIP2\ninit/Kconfig-376-\tbool \"Bzip2\"\ninit/Kconfig:377:\tdepends on HAVE_KERNEL_BZIP2\ninit/Kconfig-378-\thelp\n--\ninit/Kconfig=385=config KERNEL_LZMA\ninit/Kconfig-386-\tbool \"LZMA\"\ninit/Kconfig:387:\tdepends on HAVE_KERNEL_LZMA\ninit/Kconfig-388-\thelp\n--\ninit/Kconfig=393=config KERNEL_XZ\ninit/Kconfig-394-\tbool \"XZ\"\ninit/Kconfig:395:\tdepends on HAVE_KERNEL_XZ\ninit/Kconfig-396-\thelp\n--\ninit/Kconfig=409=config KERNEL_LZO\ninit/Kconfig-410-\tbool \"LZO\"\ninit/Kconfig:411:\tdepends on HAVE_KERNEL_LZO\ninit/Kconfig-412-\thelp\n--\ninit/Kconfig=417=config KERNEL_LZ4\ninit/Kconfig-418-\tbool \"LZ4\"\ninit/Kconfig:419:\tdepends on HAVE_KERNEL_LZ4\ninit/Kconfig-420-\thelp\n--\ninit/Kconfig=429=config KERNEL_ZSTD\ninit/Kconfig-430-\tbool \"ZSTD\"\ninit/Kconfig:431:\tdepends on HAVE_KERNEL_ZSTD\ninit/Kconfig-432-\thelp\n--\ninit/Kconfig=439=config KERNEL_UNCOMPRESSED\ninit/Kconfig-440-\tbool \"None\"\ninit/Kconfig:441:\tdepends on HAVE_KERNEL_UNCOMPRESSED\ninit/Kconfig-442-\thelp\n--\ninit/Kconfig=485=config SYSVIPC_SYSCTL\ninit/Kconfig-486-\tbool\ninit/Kconfig:487:\tdepends on SYSVIPC\ninit/Kconfig:488:\tdepends on SYSCTL\ninit/Kconfig-489-\tdefault y\n--\ninit/Kconfig=491=config SYSVIPC_COMPAT\ninit/Kconfig-492-\tdef_bool y\ninit/Kconfig:493:\tdepends on COMPAT \u0026\u0026 SYSVIPC\ninit/Kconfig-494-\ninit/Kconfig=495=config POSIX_MQUEUE\ninit/Kconfig-496-\tbool \"POSIX Message Queues\"\ninit/Kconfig:497:\tdepends on NET\ninit/Kconfig-498-\thelp\n--\ninit/Kconfig=511=config POSIX_MQUEUE_SYSCTL\ninit/Kconfig-512-\tbool\ninit/Kconfig:513:\tdepends on POSIX_MQUEUE\ninit/Kconfig:514:\tdepends on SYSCTL\ninit/Kconfig-515-\tdefault y\n--\ninit/Kconfig=529=config CROSS_MEMORY_ATTACH\ninit/Kconfig-530-\tbool \"Enable process_vm_readv/writev syscalls\"\ninit/Kconfig:531:\tdepends on MMU\ninit/Kconfig-532-\tdefault y\n--\ninit/Kconfig=539=config AUDIT\ninit/Kconfig-540-\tbool \"Auditing support\"\ninit/Kconfig:541:\tdepends on NET\ninit/Kconfig-542-\thelp\n--\ninit/Kconfig=551=config AUDITSYSCALL\ninit/Kconfig-552-\tdef_bool y\ninit/Kconfig:553:\tdepends on AUDIT \u0026\u0026 HAVE_ARCH_AUDITSYSCALL\ninit/Kconfig-554-\tselect FSNOTIFY\n--\ninit/Kconfig=571=config TICK_CPU_ACCOUNTING\ninit/Kconfig-572-\tbool \"Simple tick based cputime accounting\"\ninit/Kconfig:573:\tdepends on !S390 \u0026\u0026 !NO_HZ_FULL\ninit/Kconfig-574-\thelp\n--\ninit/Kconfig=581=config VIRT_CPU_ACCOUNTING_NATIVE\ninit/Kconfig-582-\tbool \"Deterministic task and CPU time accounting\"\ninit/Kconfig:583:\tdepends on HAVE_VIRT_CPU_ACCOUNTING \u0026\u0026 !NO_HZ_FULL\ninit/Kconfig-584-\tselect VIRT_CPU_ACCOUNTING\n--\ninit/Kconfig=594=config VIRT_CPU_ACCOUNTING_GEN\ninit/Kconfig-595-\tbool \"Full dynticks CPU time accounting\"\ninit/Kconfig:596:\tdepends on HAVE_CONTEXT_TRACKING_USER\ninit/Kconfig:597:\tdepends on HAVE_VIRT_CPU_ACCOUNTING_GEN\ninit/Kconfig:598:\tdepends on GENERIC_CLOCKEVENTS\ninit/Kconfig-599-\tselect VIRT_CPU_ACCOUNTING\n--\ninit/Kconfig=615=config IRQ_TIME_ACCOUNTING\ninit/Kconfig-616-\tbool \"Fine granularity task level IRQ time accounting\"\ninit/Kconfig:617:\tdepends on HAVE_IRQ_TIME_ACCOUNTING \u0026\u0026 !VIRT_CPU_ACCOUNTING_NATIVE\ninit/Kconfig-618-\thelp\n--\ninit/Kconfig=626=config HAVE_SCHED_AVG_IRQ\ninit/Kconfig-627-\tdef_bool y\ninit/Kconfig:628:\tdepends on IRQ_TIME_ACCOUNTING || PARAVIRT_TIME_ACCOUNTING\ninit/Kconfig:629:\tdepends on SMP\ninit/Kconfig-630-\ninit/Kconfig=631=config SCHED_HW_PRESSURE\n--\ninit/Kconfig-634-\tdefault y if ARM64\ninit/Kconfig:635:\tdepends on SMP\ninit/Kconfig:636:\tdepends on CPU_FREQ_THERMAL\ninit/Kconfig-637-\thelp\n--\ninit/Kconfig=650=config BSD_PROCESS_ACCT\ninit/Kconfig-651-\tbool \"BSD Process Accounting (DEPRECATED)\"\ninit/Kconfig:652:\tdepends on MULTIUSER\ninit/Kconfig-653-\tdefault n\n--\ninit/Kconfig=667=config BSD_PROCESS_ACCT_V3\ninit/Kconfig-668-\tbool \"BSD Process Accounting version 3 file format\"\ninit/Kconfig:669:\tdepends on BSD_PROCESS_ACCT\ninit/Kconfig-670-\tdefault n\n--\ninit/Kconfig=679=config TASKSTATS\ninit/Kconfig-680-\tbool \"Export task/process statistics through netlink\"\ninit/Kconfig:681:\tdepends on NET\ninit/Kconfig:682:\tdepends on MULTIUSER\ninit/Kconfig-683-\tdefault n\n--\ninit/Kconfig=693=config TASK_DELAY_ACCT\ninit/Kconfig-694-\tbool \"Enable per-task delay accounting\"\ninit/Kconfig:695:\tdepends on TASKSTATS\ninit/Kconfig-696-\tselect SCHED_INFO\n--\ninit/Kconfig=705=config TASK_XACCT\ninit/Kconfig-706-\tbool \"Enable extended accounting over taskstats\"\ninit/Kconfig:707:\tdepends on TASKSTATS\ninit/Kconfig-708-\thelp\n--\ninit/Kconfig=714=config TASK_IO_ACCOUNTING\ninit/Kconfig-715-\tbool \"Enable per-task storage I/O accounting\"\ninit/Kconfig:716:\tdepends on TASK_XACCT\ninit/Kconfig-717-\thelp\n--\ninit/Kconfig=743=config PSI_DEFAULT_DISABLED\n--\ninit/Kconfig-745-\tdefault n\ninit/Kconfig:746:\tdepends on PSI\ninit/Kconfig-747-\thelp\n--\ninit/Kconfig=765=config CPU_ISOLATION\ninit/Kconfig-766-\tbool \"CPU isolation\"\ninit/Kconfig:767:\tdepends on SMP\ninit/Kconfig-768-\tdefault y\n--\ninit/Kconfig=791=config IKCONFIG_PROC\ninit/Kconfig-792-\tbool \"Enable access to .config through /proc/config.gz\"\ninit/Kconfig:793:\tdepends on IKCONFIG \u0026\u0026 PROC_FS\ninit/Kconfig-794-\thelp\n--\ninit/Kconfig=798=config IKHEADERS\ninit/Kconfig-799-\ttristate \"Enable kernel headers through /sys/kernel/kheaders.tar.xz\"\ninit/Kconfig:800:\tdepends on SYSFS\ninit/Kconfig-801-\thelp\n--\ninit/Kconfig=807=config LOG_BUF_SHIFT\n--\ninit/Kconfig-810-\tdefault 17\ninit/Kconfig:811:\tdepends on PRINTK\ninit/Kconfig-812-\thelp\n--\ninit/Kconfig=826=config LOG_CPU_MAX_BUF_SHIFT\ninit/Kconfig-827-\tint \"CPU kernel log buffer size contribution (13 =\u003e 8 KB, 17 =\u003e 128KB)\"\ninit/Kconfig:828:\tdepends on SMP\ninit/Kconfig-829-\trange 0 21\n--\ninit/Kconfig-831-\tdefault 12\ninit/Kconfig:832:\tdepends on PRINTK\ninit/Kconfig-833-\thelp\n--\ninit/Kconfig=862=config PRINTK_INDEX\ninit/Kconfig-863-\tbool \"Printk indexing debugfs interface\"\ninit/Kconfig:864:\tdepends on PRINTK \u0026\u0026 DEBUG_FS\ninit/Kconfig-865-\thelp\n--\ninit/Kconfig=887=config UCLAMP_TASK\ninit/Kconfig-888-\tbool \"Enable utilization clamping for RT/FAIR tasks\"\ninit/Kconfig:889:\tdepends on CPU_FREQ_GOV_SCHEDUTIL\ninit/Kconfig-890-\thelp\n--\ninit/Kconfig=905=config UCLAMP_BUCKETS_COUNT\n--\ninit/Kconfig-908-\tdefault 5\ninit/Kconfig:909:\tdepends on UCLAMP_TASK\ninit/Kconfig-910-\thelp\n--\ninit/Kconfig=936=config SCHED_PROXY_EXEC\n--\ninit/Kconfig-938-\t# Avoid some build failures w/ PREEMPT_RT until it can be fixed\ninit/Kconfig:939:\tdepends on !PREEMPT_RT\ninit/Kconfig-940-\t# Need to investigate how to inform sched_ext of split contexts\ninit/Kconfig:941:\tdepends on !SCHED_CLASS_EXT\ninit/Kconfig-942-\t# Not particularly useful until we get to multi-rq proxying\ninit/Kconfig:943:\tdepends on EXPERT\ninit/Kconfig-944-\thelp\n--\ninit/Kconfig=1020=config NUMA_BALANCING\ninit/Kconfig-1021-\tbool \"Memory placement aware NUMA scheduler\"\ninit/Kconfig:1022:\tdepends on ARCH_SUPPORTS_NUMA_BALANCING\ninit/Kconfig:1023:\tdepends on ARCH_HAS_PTE_PROTNONE\ninit/Kconfig:1024:\tdepends on !ARCH_WANT_NUMA_VARIABLE_LOCALITY\ninit/Kconfig:1025:\tdepends on SMP \u0026\u0026 NUMA_MIGRATION \u0026\u0026 !PREEMPT_RT\ninit/Kconfig-1026-\thelp\n--\ninit/Kconfig=1033=config SCHED_CACHE\n--\ninit/Kconfig-1035-\tdefault y\ninit/Kconfig:1036:\tdepends on SMP\ninit/Kconfig-1037-\thelp\n--\ninit/Kconfig=1044=config NUMA_BALANCING_DEFAULT_ENABLED\n--\ninit/Kconfig-1046-\tdefault y\ninit/Kconfig:1047:\tdepends on NUMA_BALANCING\ninit/Kconfig-1048-\thelp\n--\ninit/Kconfig=1093=config MEMCG_NMI_UNSAFE\ninit/Kconfig-1094-\tbool\ninit/Kconfig:1095:\tdepends on MEMCG\ninit/Kconfig:1096:\tdepends on HAVE_NMI\ninit/Kconfig:1097:\tdepends on !ARCH_HAS_NMI_SAFE_THIS_CPU_OPS \u0026\u0026 !ARCH_HAVE_NMI_SAFE_CMPXCHG\ninit/Kconfig-1098-\tdefault y\n--\ninit/Kconfig=1100=config MEMCG_NMI_SAFETY_REQUIRES_ATOMIC\ninit/Kconfig-1101-\tbool\ninit/Kconfig:1102:\tdepends on MEMCG\ninit/Kconfig:1103:\tdepends on HAVE_NMI\ninit/Kconfig:1104:\tdepends on !ARCH_HAS_NMI_SAFE_THIS_CPU_OPS \u0026\u0026 ARCH_HAVE_NMI_SAFE_CMPXCHG\ninit/Kconfig-1105-\tdefault y\n--\ninit/Kconfig=1107=config MEMCG_V1\ninit/Kconfig-1108-\tbool \"Legacy cgroup v1 memory controller\"\ninit/Kconfig:1109:\tdepends on MEMCG\ninit/Kconfig-1110-\tdefault n\n--\ninit/Kconfig=1124=config BLK_CGROUP\ninit/Kconfig-1125-\tbool \"IO controller\"\ninit/Kconfig:1126:\tdepends on BLOCK\ninit/Kconfig-1127-\tdefault n\n--\ninit/Kconfig=1146=config CGROUP_WRITEBACK\ninit/Kconfig-1147-\tbool\ninit/Kconfig:1148:\tdepends on MEMCG \u0026\u0026 BLK_CGROUP\ninit/Kconfig-1149-\tdefault y\n--\ninit/Kconfig=1166=config FAIR_GROUP_SCHED\ninit/Kconfig-1167-\tbool \"Group scheduling for SCHED_OTHER\"\ninit/Kconfig:1168:\tdepends on CGROUP_SCHED\ninit/Kconfig-1169-\tselect GROUP_SCHED_WEIGHT\n--\ninit/Kconfig=1172=config CFS_BANDWIDTH\ninit/Kconfig-1173-\tbool \"CPU bandwidth provisioning for FAIR_GROUP_SCHED\"\ninit/Kconfig:1174:\tdepends on FAIR_GROUP_SCHED\ninit/Kconfig-1175-\tselect GROUP_SCHED_BANDWIDTH\n--\ninit/Kconfig=1184=config RT_GROUP_SCHED\ninit/Kconfig-1185-\tbool \"Group scheduling for SCHED_RR/FIFO\"\ninit/Kconfig:1186:\tdepends on CGROUP_SCHED\ninit/Kconfig-1187-\tdefault n\n--\ninit/Kconfig=1195=config RT_GROUP_SCHED_DEFAULT_DISABLED\ninit/Kconfig-1196-\tbool \"Require boot parameter to enable group scheduling for SCHED_RR/FIFO\"\ninit/Kconfig:1197:\tdepends on RT_GROUP_SCHED\ninit/Kconfig-1198-\tdefault n\n--\ninit/Kconfig=1206=config EXT_GROUP_SCHED\ninit/Kconfig-1207-\tbool\ninit/Kconfig:1208:\tdepends on SCHED_CLASS_EXT \u0026\u0026 CGROUP_SCHED\ninit/Kconfig-1209-\tselect GROUP_SCHED_WEIGHT\n--\ninit/Kconfig=1215=config EXT_SUB_SCHED\ninit/Kconfig-1216- def_bool y\ninit/Kconfig:1217: depends on SCHED_CLASS_EXT \u0026\u0026 CGROUPS\ninit/Kconfig-1218-\ninit/Kconfig=1219=config SCHED_MM_CID\ninit/Kconfig-1220-\tdef_bool y\ninit/Kconfig:1221:\tdepends on SMP \u0026\u0026 RSEQ\ninit/Kconfig-1222-\ninit/Kconfig=1223=config UCLAMP_TASK_GROUP\ninit/Kconfig-1224-\tbool \"Utilization clamping per group of tasks\"\ninit/Kconfig:1225:\tdepends on CGROUP_SCHED\ninit/Kconfig:1226:\tdepends on UCLAMP_TASK\ninit/Kconfig-1227-\tdefault n\n--\ninit/Kconfig=1292=config CGROUP_HUGETLB\ninit/Kconfig-1293-\tbool \"HugeTLB controller\"\ninit/Kconfig:1294:\tdepends on HUGETLB_PAGE\ninit/Kconfig-1295-\tselect PAGE_COUNTER\n--\ninit/Kconfig=1308=config CPUSETS\ninit/Kconfig-1309-\tbool \"Cpuset controller\"\ninit/Kconfig:1310:\tdepends on SMP\ninit/Kconfig-1311-\tselect UNION_FIND\n--\ninit/Kconfig=1321=config CPUSETS_V1\ninit/Kconfig-1322-\tbool \"Legacy cgroup v1 cpusets controller\"\ninit/Kconfig:1323:\tdepends on CPUSETS\ninit/Kconfig-1324-\tdefault n\n--\ninit/Kconfig=1335=config PROC_PID_CPUSET\ninit/Kconfig-1336-\tbool \"Include legacy /proc/\u003cpid\u003e/cpuset file\"\ninit/Kconfig:1337:\tdepends on CPUSETS_V1\ninit/Kconfig-1338-\tdefault y\n--\ninit/Kconfig=1352=config CGROUP_PERF\ninit/Kconfig-1353-\tbool \"Perf controller\"\ninit/Kconfig:1354:\tdepends on PERF_EVENTS\ninit/Kconfig-1355-\thelp\n--\ninit/Kconfig=1363=config CGROUP_BPF\ninit/Kconfig-1364-\tbool \"Support for eBPF programs attached to cgroups\"\ninit/Kconfig:1365:\tdepends on BPF_SYSCALL\ninit/Kconfig-1366-\tselect SOCK_CGROUP_DATA\n--\ninit/Kconfig-1370-\ninit/Kconfig:1371:\t In which context these programs are accessed depends on the type\ninit/Kconfig-1372-\t of attachment. For instance, programs that are attached using\n--\ninit/Kconfig=1390=config CGROUP_DEBUG\n--\ninit/Kconfig-1392-\tdefault n\ninit/Kconfig:1393:\tdepends on DEBUG_KERNEL\ninit/Kconfig-1394-\thelp\n--\ninit/Kconfig=1408=menuconfig NAMESPACES\ninit/Kconfig-1409-\tbool \"Namespaces support\" if EXPERT\ninit/Kconfig:1410:\tdepends on MULTIUSER\ninit/Kconfig-1411-\tdefault !EXPERT\n--\ninit/Kconfig=1437=config IPC_NS\ninit/Kconfig-1438-\tbool \"IPC namespace\"\ninit/Kconfig:1439:\tdepends on (SYSVIPC || POSIX_MQUEUE)\ninit/Kconfig-1440-\tdefault y\n--\ninit/Kconfig=1467=config NET_NS\ninit/Kconfig-1468-\tbool \"Network namespace\"\ninit/Kconfig:1469:\tdepends on NET\ninit/Kconfig-1470-\tdefault y\n--\ninit/Kconfig=1477=config CHECKPOINT_RESTORE\ninit/Kconfig-1478-\tbool \"Checkpoint/restore support\"\ninit/Kconfig:1479:\tdepends on PROC_FS\ninit/Kconfig-1480-\tselect PROC_CHILDREN\n--\ninit/Kconfig=1548=config BOOT_CONFIG_FORCE\ninit/Kconfig-1549-\tbool \"Force unconditional bootconfig processing\"\ninit/Kconfig:1550:\tdepends on BOOT_CONFIG\ninit/Kconfig-1551-\tdefault y if BOOT_CONFIG_EMBED\n--\ninit/Kconfig=1561=config BOOT_CONFIG_EMBED\ninit/Kconfig-1562-\tbool \"Embed bootconfig file in the kernel\"\ninit/Kconfig:1563:\tdepends on BOOT_CONFIG\ninit/Kconfig-1564-\thelp\n--\ninit/Kconfig=1572=config BOOT_CONFIG_EMBED_FILE\ninit/Kconfig-1573-\tstring \"Embedded bootconfig file path\"\ninit/Kconfig:1574:\tdepends on BOOT_CONFIG_EMBED\ninit/Kconfig-1575-\thelp\n--\ninit/Kconfig=1598=config INITRAMFS_PRESERVE_MTIME\ninit/Kconfig-1599-\tbool \"Preserve cpio archive mtimes in initramfs\"\ninit/Kconfig:1600:\tdepends on BLK_DEV_INITRD\ninit/Kconfig-1601-\tdefault y\n--\ninit/Kconfig=1609=config INITRAMFS_TEST\ninit/Kconfig-1610-\tbool \"Test initramfs cpio archive extraction\" if !KUNIT_ALL_TESTS\ninit/Kconfig:1611:\tdepends on BLK_DEV_INITRD \u0026\u0026 KUNIT=y\ninit/Kconfig-1612-\tdefault KUNIT_ALL_TESTS\n--\ninit/Kconfig=1645=config LD_DEAD_CODE_DATA_ELIMINATION\ninit/Kconfig-1646-\tbool \"Dead code and data elimination (EXPERIMENTAL)\"\ninit/Kconfig:1647:\tdepends on HAVE_LD_DEAD_CODE_DATA_ELIMINATION\ninit/Kconfig:1648:\tdepends on EXPERT\ninit/Kconfig:1649:\tdepends on $(cc-option,-ffunction-sections -fdata-sections)\ninit/Kconfig:1650:\tdepends on $(ld-option,--gc-sections)\ninit/Kconfig-1651-\thelp\n--\ninit/Kconfig=1663=config LD_ORPHAN_WARN\ninit/Kconfig-1664-\tdef_bool y\ninit/Kconfig:1665:\tdepends on ARCH_WANT_LD_ORPHAN_WARN\ninit/Kconfig:1666:\tdepends on $(ld-option,--orphan-handling=warn)\ninit/Kconfig:1667:\tdepends on $(ld-option,--orphan-handling=error)\ninit/Kconfig-1668-\ninit/Kconfig=1669=config LD_ORPHAN_WARN_LEVEL\ninit/Kconfig-1670-\tstring\ninit/Kconfig:1671:\tdepends on LD_ORPHAN_WARN\ninit/Kconfig-1672-\tdefault \"error\" if WERROR\n--\ninit/Kconfig=1724=config UID16\ninit/Kconfig-1725-\tbool \"Enable 16-bit UID system calls\" if EXPERT\ninit/Kconfig:1726:\tdepends on HAVE_UID16 \u0026\u0026 MULTIUSER\ninit/Kconfig-1727-\tdefault y\n--\ninit/Kconfig=1796=config PRINTK_RINGBUFFER_KUNIT_TEST\ninit/Kconfig-1797-\ttristate \"KUnit Test for the printk ringbuffer\" if !KUNIT_ALL_TESTS\ninit/Kconfig:1798:\tdepends on PRINTK \u0026\u0026 KUNIT\ninit/Kconfig-1799-\tdefault KUNIT_ALL_TESTS\n--\ninit/Kconfig=1818=config ELF_CORE\ninit/Kconfig:1819:\tdepends on COREDUMP\ninit/Kconfig-1820-\tdefault y\n--\ninit/Kconfig=1826=config PCSPKR_PLATFORM\ninit/Kconfig-1827-\tbool \"Enable PC-Speaker support\" if EXPERT\ninit/Kconfig:1828:\tdepends on HAVE_PCSPKR_PLATFORM\ninit/Kconfig-1829-\tselect I8253_LOCK\n--\ninit/Kconfig=1842=config FUTEX\ninit/Kconfig-1843-\tbool \"Enable futex support\" if EXPERT\ninit/Kconfig:1844:\tdepends on !(SPARC32 \u0026\u0026 SMP)\ninit/Kconfig-1845-\tdefault y\n--\ninit/Kconfig=1852=config FUTEX_PI\ninit/Kconfig-1853-\tbool\ninit/Kconfig:1854:\tdepends on FUTEX \u0026\u0026 RT_MUTEXES\ninit/Kconfig-1855-\tdefault y\n--\ninit/Kconfig=1857=config FUTEX_PRIVATE_HASH\ninit/Kconfig-1858-\tbool\ninit/Kconfig:1859:\tdepends on FUTEX \u0026\u0026 !BASE_SMALL \u0026\u0026 MMU\ninit/Kconfig-1860-\tdefault y\n--\ninit/Kconfig=1862=config FUTEX_MPOL\ninit/Kconfig-1863-\tbool\ninit/Kconfig:1864:\tdepends on FUTEX \u0026\u0026 NUMA\ninit/Kconfig-1865-\tdefault y\n--\ninit/Kconfig=1907=config SHMEM\n--\ninit/Kconfig-1909-\tdefault y\ninit/Kconfig:1910:\tdepends on MMU\ninit/Kconfig-1911-\thelp\n--\ninit/Kconfig=1935=config GCOV_PROFILE_URING\ninit/Kconfig-1936-\tbool \"Enable GCOV profiling on the io_uring subsystem\"\ninit/Kconfig:1937:\tdepends on IO_URING \u0026\u0026 GCOV_KERNEL\ninit/Kconfig-1938-\thelp\n--\ninit/Kconfig=1948=config IO_URING_MOCK_FILE\n--\ninit/Kconfig-1950-\tdefault n\ninit/Kconfig:1951:\tdepends on IO_URING\ninit/Kconfig-1952-\thelp\n--\ninit/Kconfig=1991=config RSEQ\n--\n\n"
} |
| 27/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 49892 output: 36 thoughts: 329 Thoughts: **Analyzing Configuration Dependencies and Code Usage** Okay, so I'm diving into this. Initially, I was thinking `MEMCG` might have some hidden dependencies, but it seems straightforward. I'll double-check `CONFIG_MEMCG` in `mm/Kconfig` just to be sure. Hmm, wait, no, it's actually in `init/Kconfig`. Good to know. Now, let's see about `CONFIG_LRU_GEN`. Does it depend on `CONFIG_MEMCG`? No, that's not the case. It looks like `LRU_GEN` relies on `MMU` and a few architectures, so `LRU_GEN` and `LRU_GEN_ENABLED` are sufficient. That makes sense. Next, I'll explore other relevant configurations. `CONFIG_LRU_GEN_STATS` might be helpful for testing, but it's not strictly necessary. It's probably worthwhile to keep `CONFIG_MEMCG` enabled since modifications were made to `include/linux/memcontrol.h` and `mm/memcontrol.c`. Now, I'll go through the code usage, starting with `mm/vmscan.c` to see how `folio_inc_lru_refs` and `folio_is_workingset` are used. Okay, the patch also modifies `fs/btrfs/compression.c` and `fs/proc/task_mmu.c`. Right, so `fs/btrfs/compression.c` is using `folio_is_workingset`. Meanwhile, `fs/proc/task_mmu.c` uses `smap_check_folio_referenced` and `smap_clear_folio_referenced`. Finally, I'm going to take a closer look at `fs/btrfs/compression.c` to understand that code's interaction with the changes. |
| 28/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "folio_is_workingset",
"PathPrefix": "fs/btrfs/compression.c"
}Results: {
"Output": "fs/btrfs/compression.c=366=static noinline int add_ra_bio_folios(struct inode *inode, u64 compressed_end,\n--\nfs/btrfs/compression.c-451-\nfs/btrfs/compression.c:452:\t\tif (!*memstall \u0026\u0026 folio_is_workingset(folio)) {\nfs/btrfs/compression.c-453-\t\t\tpsi_memstall_enter(pflags);\n"
} |
| 29/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 50795 output: 38 thoughts: 30 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Minor Code Optimization: Leveraging the New Helper** Alright, this looks straightforward. It seems like we're just talking about a minor modification; swapping out the existing code for the new helper function. This shouldn't be too disruptive, but I always believe in checking the details. Specifically, the place to start digging is `fs/proc/task_mmu.c`. That's where I'd expect to find the relevant logic related to task memory management, which this helper is presumably assisting. Let's see how they're handling things in there and if the switch introduces any subtle side effects or requires any further adjustments within that module. Always gotta be thorough! |
| 30/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "smap_check_folio_referenced",
"PathPrefix": "fs/proc/task_mmu.c"
}Results: {
"Output": "fs/proc/task_mmu.c=917=static void smaps_page_accumulate(struct mem_size_stats *mss,\n--\nfs/proc/task_mmu.c-946-\nfs/proc/task_mmu.c:947:static bool smap_check_folio_referenced(struct folio *folio)\nfs/proc/task_mmu.c-948-{\n--\nfs/proc/task_mmu.c=963=static void smaps_account(struct mem_size_stats *mss, struct page *page,\n--\nfs/proc/task_mmu.c-988-\t/* Accumulate the size in pages that have been accessed. */\nfs/proc/task_mmu.c:989:\tif (young || folio_test_young(folio) || smap_check_folio_referenced(folio))\nfs/proc/task_mmu.c-990-\t\tmss-\u003ereferenced += size;\n"
} |
| 31/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 51270 output: 34 thoughts: 62 Thoughts: Okay, here's my interpretation of these notes, reflecting my understanding of memory management internals: **Focusing In on a Minor Memory Management Update** Alright, so it looks like we're dealing with a relatively contained change. The note emphasizes "small change," which usually means we're looking at refinements or bug fixes rather than a massive architectural overhaul. That's good; makes the analysis less daunting. The main functional areas affected are listed: `mm/vmscan.c`, `mm/folio.c`, `mm/madvise.c`, and `mm/workingset.c`. Given the context of a "small change", I would wager this is most likely bugfixes or micro-optimizations that span a few core memory management files. Since `mm/workingset.c` is explicitly mentioned as a relevant file, I'll take a closer look at that one first, as it could be the central point of the change. Time to dive into the code and see what the diff says. |
| 32/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "lru_gen_refault",
"PathPrefix": "mm/workingset.c"
}Results: {
"Output": "mm/workingset.c=279=static bool lru_gen_test_recent(void *shadow, struct lruvec **lruvec,\n--\nmm/workingset.c-310- */\nmm/workingset.c:311:static void lru_gen_refault(struct folio *folio, void *shadow)\nmm/workingset.c-312-{\n--\nmm/workingset.c=370=static bool lru_gen_test_recent(void *shadow, struct lruvec **lruvec,\n--\nmm/workingset.c-375-\nmm/workingset.c:376:static void lru_gen_refault(struct folio *folio, void *shadow)\nmm/workingset.c-377-{\n--\nmm/workingset.c=574=void workingset_refault(struct folio *folio, void *shadow)\n--\nmm/workingset.c-584-\tif (lru_gen_enabled()) {\nmm/workingset.c:585:\t\tlru_gen_refault(folio, shadow);\nmm/workingset.c-586-\t\treturn;\n"
} |
| 33/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 51888 output: 33 thoughts: 26 Thoughts: Here's my take on the thought process, as if it were my own: **Analyzing the workingset_refault Function** Okay, so `workingset_refault` is an exported function, meaning it's designed to be used externally. That immediately piques my interest. It implies a specific role in memory management, probably related to demand paging or something similar. Let's dig in and examine the code for `workingset_refault` directly. I need to understand its exact signature, what inputs it takes, what it does with them, and crucially, how it interacts with the underlying memory management structures. I'll be looking for potential performance bottlenecks, areas for optimization, or anything that might lead to unexpected behavior in specific scenarios. My immediate goal is to establish its precise function and its place within the broader memory management architecture. |
| 34/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "workingset_refault",
"PathPrefix": "mm/workingset.c"
}Results: {
"Output": "mm/workingset.c=456=bool workingset_test_recent(void *shadow, bool file, bool *workingset,\n--\nmm/workingset.c-565-/**\nmm/workingset.c:566: * workingset_refault - Evaluate the refault of a previously evicted folio.\nmm/workingset.c-567- * @folio: The freshly allocated replacement folio.\n--\nmm/workingset.c-573- */\nmm/workingset.c:574:void workingset_refault(struct folio *folio, void *shadow)\nmm/workingset.c-575-{\n"
} |
| 35/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 52298 output: 36 thoughts: 189 Thoughts: **Fuzzing Target Identification** Okay, so I'm thinking about fuzzing targets related to memory management, and `workingset_refault` has immediately caught my attention. It's called when a page is refaulted, making it a key point for potential vulnerabilities. This patch modifies `lru_gen_refault` within `mm/workingset.c`, so that's a prime area to look at. I'm also considering `mm/vmscan.c`. Specifically, `folio_inc_lru_refs` is called from both `walk_update_folio` and `folio_mark_accessed`. Now, `folio_mark_accessed` is called from *a lot* of places, which makes `folio_inc_lru_refs` a good focus for fuzzing, given the wider coverage. Other potentially interesting symbols from this area include `madvise_cold`, `madvise_pageout`, and the previously mentioned `workingset_refault`. Just to be thorough, I should really check `mm/vmscan.c` for `folio_inc_lru_refs_walk` as well. |
| 36/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "folio_inc_lru_refs_walk",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=871=int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec)\n--\nmm/vmscan.c-946- */\nmm/vmscan.c:947:static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lruvec,\nmm/vmscan.c-948-\t\t\t\t const vma_flags_t *vma_flags, int *new_gen)\n--\nmm/vmscan.c=3625=static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma,\n--\nmm/vmscan.c-3638-\tif (walk) {\nmm/vmscan.c:3639:\t\told_gen = folio_inc_lru_refs_walk(folio, lruvec, \u0026vma-\u003eflags, \u0026new_gen);\nmm/vmscan.c-3640-\t\tif (old_gen \u003e= 0)\n"
} |
| 37/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53045 output: 34 thoughts: 55 Thoughts: Okay, here's my interpretation of these code observations, presented as my own thought process: **Static Function Observations** Alright, let's see... I'm noticing some patterns here. `folio_inc_lru_refs_walk`, `walk_update_folio`, and `walk_pte_range` are all declared as `static`. Right away, that tells me these functions are *intentionally* limited in scope. They are likely utility functions, designed to be used internally within this specific compilation unit, maybe even within this file itself. The use of `static` prevents any external linking or calls from other modules. This is a crucial design choice for encapsulation and maintainability. It reduces the chance of accidental name collisions and makes it easier to reason about the code's dependencies, as these functions will only be used by the functions defined in the same source file. Now, the interesting thing is... `lru_gen_look_around`. It's *not* declared as `static`. This is a very clear signal. This function is *exported*. It's meant to be called from other parts of the kernel, potentially from entirely different subsystems. This implies a specific design purpose – it's likely a function that contributes to the broader LRU (Least Recently Used) algorithm, perhaps providing a way to inspect the state of the LRU lists or initiate some kind of "look-around" operation to check for potential optimizations or reclaimable pages. The fact that it is not declared as `static` tells me it is probably meant to be accessed across multiple modules or parts of the kernel. This suggests that it's contributing to some larger system. It seems to have a wider scope than the other functions in the list, which are more specific and limited in usage. |
| 38/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "lru_gen_look_around",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=2925=static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)\n--\nmm/vmscan.c-2954- * walk_pmd_range(); the eviction also report them when walking the rmap\nmm/vmscan.c:2955: * in lru_gen_look_around().\nmm/vmscan.c-2956- *\n--\nmm/vmscan.c=4170=static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long seq,\n--\nmm/vmscan.c-4189-\t * If the hardware doesn't automatically set the accessed bit, fallback\nmm/vmscan.c:4190:\t * to lru_gen_look_around(), which only clears the accessed bit in a\nmm/vmscan.c-4191-\t * handful of PTEs. Spreading the work out over a period of time usually\n--\nmm/vmscan.c=4310=static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-4356- */\nmm/vmscan.c:4357:bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\nmm/vmscan.c-4358-{\n"
} |
| 39/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53868 output: 30 thoughts: 53 Thoughts: Here's my take on the thought process: **Diving into `lru_gen_look_around`** Okay, so I'm thinking about `lru_gen_look_around`. The first thing I'm noting is that it's *not* a static function. That's good, it means it's likely part of the public or semi-public interface, probably. Let me just check where it's declared – ah, right, `include/linux/mmzone.h`. That makes sense; it's definitely related to memory management, specifically with zones and LRU lists, given the name. Now, my next question is, where is this actually *used*? I'm pretty sure I've seen it called from `page_vma_mapped_walk` or something similar, but I want to be certain. Let me just quickly trace the callers... Yep, I think that’s right. Let me take a look at the code… |
| 40/2 | 2026/08/03 21:27 | tool | grepper |
0mArgs:{
"Expression": "lru_gen_look_around",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/rmap.c=918=static bool folio_referenced_one(struct folio *folio,\n--\nmm/rmap.c-982-\t\tif (lru_gen_enabled() \u0026\u0026 !lru_gen_switching() \u0026\u0026 pvmw.pte) {\nmm/rmap.c:983:\t\t\tif (lru_gen_look_around(\u0026pvmw, nr))\nmm/rmap.c-984-\t\t\t\treferenced++;\n--\nmm/vmscan.c=2925=static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)\n--\nmm/vmscan.c-2954- * walk_pmd_range(); the eviction also report them when walking the rmap\nmm/vmscan.c:2955: * in lru_gen_look_around().\nmm/vmscan.c-2956- *\n--\nmm/vmscan.c=4170=static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long seq,\n--\nmm/vmscan.c-4189-\t * If the hardware doesn't automatically set the accessed bit, fallback\nmm/vmscan.c:4190:\t * to lru_gen_look_around(), which only clears the accessed bit in a\nmm/vmscan.c-4191-\t * handful of PTEs. Spreading the work out over a period of time usually\n--\nmm/vmscan.c=4310=static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-4356- */\nmm/vmscan.c:4357:bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\nmm/vmscan.c-4358-{\n"
} |
| 41/2 | 2026/08/03 21:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54635 output: 32 thoughts: 351 Thoughts: **Navigating the Memory Management Landscape** Okay, so `lru_gen_look_around` is being called from `folio_referenced_one`, that's interesting and seems like a good place to start digging. `folio_referenced_one` seems a reasonable focus symbol. Let's circle back to `folio_inc_lru_refs`. I remember that's in `include/linux/mm_inline.h`. Ah, it's called from `folio_mark_accessed` and `walk_update_folio`. `folio_mark_accessed` definitely looks like a solid focus symbol as well. Now, what about `madvise_cold` and `madvise_pageout`? Let me see... they are static in `mm/madvise.c`. Hmm, that's a bit of a roadblock. Wait, `madvise_cold` *is* static. Can we leverage `sys_madvise`? Yes, of course, that's a syscall, so that's the preferred method. I could try to use `madvise_cold` and `madvise_pageout` directly if they are in the symbol table, but they are static so I'd need to consider the inlining issues. Actually, using `sys_madvise` seems like a better approach than messing around with static functions, or I could just target `madvise_vma_behavior` or `madvise_cold_or_pageout_pte_range`, but, wait a minute, `madvise_cold_or_pageout_pte_range` is also static! Okay, so I will have to stick with using `sys_madvise`, that's the entry point and the best way forward. I should also check the changes to `mm/huge_memory.c`. |
| 42/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "folio_mark_accessed",
"PathPrefix": "mm/huge_memory.c"
}Results: {
"Output": "mm/huge_memory.c=2437=static void zap_huge_pmd_folio(struct mm_struct *mm, struct vm_area_struct *vma,\n--\nmm/huge_memory.c-2453-\t\t likely(vma_has_recency(vma)))\nmm/huge_memory.c:2454:\t\t\tfolio_mark_accessed(folio);\nmm/huge_memory.c-2455-\t}\n--\nmm/huge_memory.c=3037=static void __split_huge_pud_locked(struct vm_area_struct *vma, pud_t *pud,\n--\nmm/huge_memory.c-3061-\tif (pud_young(old_pud) \u0026\u0026 vma_has_recency(vma))\nmm/huge_memory.c:3062:\t\tfolio_mark_accessed(folio);\nmm/huge_memory.c-3063-\tfolio_remove_rmap_pud(folio, page, vma);\n--\nmm/huge_memory.c=3141=static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n--\nmm/huge_memory.c-3184-\t\t\tif (pmd_young(old_pmd) \u0026\u0026 vma_has_recency(vma))\nmm/huge_memory.c:3185:\t\t\t\tfolio_mark_accessed(folio);\nmm/huge_memory.c-3186-\t\t\tfolio_remove_rmap_pmd(folio, page, vma);\n"
} |
| 43/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 55823 output: 33 thoughts: 92 Thoughts: Here's my understanding of the situation, tailored for my expert perspective: **Folio Access Tracking Enhancement in Huge Page Splitting** Okay, so it looks like we've got a targeted modification to the huge page splitting routines, specifically `__split_huge_pud_locked` and `__split_huge_pmd_locked`. The key difference here is the switch from `folio_set_referenced` to `folio_mark_accessed`. Now, that's interesting because it's *functionally* changing the behavior. `folio_mark_accessed` isn't just setting a bit; it's *incrementing the LRU references* as well. This subtly alters how we're tracking access to these folio-backed pages during the split process. This hints at something significant regarding page replacement strategies. Given this context, I need to understand the implications better. My next step is to drill down into the related changes in `mm/filemap.c`. I need to assess how this access tracking change impacts the broader filemap interactions, particularly how it affects the LRU and page reclaim in the context of file-backed memory. I'm keen to understand whether this is a precursor to a more granular LRU management for file-backed pages, or if it's more subtle. |
| 44/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "folio_is_workingset",
"PathPrefix": "mm/filemap.c"
}Results: {
"Output": "mm/filemap.c=1250=static inline int folio_wait_bit_common(struct folio *folio, int bit_nr,\n--\nmm/filemap.c-1261-\tif (bit_nr == PG_locked \u0026\u0026\nmm/filemap.c:1262:\t !folio_test_uptodate(folio) \u0026\u0026 folio_is_workingset(folio)) {\nmm/filemap.c-1263-\t\tdelayacct_thrashing_start(\u0026in_thrashing);\n--\nmm/filemap.c=1405=void softleaf_entry_wait_on_locked(softleaf_t entry, spinlock_t *ptl)\n--\nmm/filemap.c-1416-\tq = folio_waitqueue(folio);\nmm/filemap.c:1417:\tif (!folio_test_uptodate(folio) \u0026\u0026 folio_is_workingset(folio)) {\nmm/filemap.c-1418-\t\tdelayacct_thrashing_start(\u0026in_thrashing);\n--\nmm/filemap.c=2510=static int filemap_read_folio(struct file *file, filler_t filler,\n--\nmm/filemap.c-2512-{\nmm/filemap.c:2513:\tbool workingset = folio_is_workingset(folio);\nmm/filemap.c-2514-\tunsigned long pflags;\n--\nmm/filemap.c=3899=vm_fault_t filemap_map_pages(struct vm_fault *vmf,\n--\nmm/filemap.c-3983-\t\t !(vmf-\u003eflags \u0026 FAULT_FLAG_TRIED) \u0026\u0026\nmm/filemap.c:3984:\t\t !folio_is_workingset(folio) \u0026\u0026\nmm/filemap.c-3985-\t\t !(vma-\u003evm_flags \u0026 (VM_SEQ_READ | VM_EXEC))) {\n"
} |
| 45/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 56717 output: 33 thoughts: 54 Thoughts: Here's my summary, as though I'm thinking through this myself: **Analyzing Recent Filemap and KHugepage Changes** Okay, so they've touched `filemap_read_folio` and `filemap_map_pages`. The modifications incorporating `folio_is_workingset` look like a solid area to concentrate on. Focusing on these symbols is usually a good indicator of some interesting performance or memory management changes. Let's dig in there first. Now, I need to check out what's happened within `mm/khugepaged.c`. I expect these changes to be related to the folio modifications in some way. I'm hoping to see how they're interacting with huge page allocation or deallocation. I need to understand how the workingset logic is being propagated up to this level. |
| 46/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "folio_is_referenced",
"PathPrefix": "mm/khugepaged.c"
}Results: {
"Output": "mm/khugepaged.c=668=static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n--\nmm/khugepaged.c-811-\t\t (pte_young(pteval) || folio_test_young(folio) ||\nmm/khugepaged.c:812:\t\t folio_is_referenced(folio) ||\nmm/khugepaged.c-813-\t\t mmu_notifier_test_young(vma-\u003evm_mm, addr)))\n--\nmm/khugepaged.c=1592=static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n--\nmm/khugepaged.c-1769-\t\t (pte_young(pteval) || folio_test_young(folio) ||\nmm/khugepaged.c:1770:\t\t folio_is_referenced(folio) ||\nmm/khugepaged.c-1771-\t\t mmu_notifier_test_young(vma-\u003evm_mm, addr)))\n--\nmm/khugepaged.c=2679=static enum scan_result collapse_scan_file(struct mm_struct *mm,\n--\nmm/khugepaged.c-2754-\t\t * here, but nobody would transfer pte_young() to\nmm/khugepaged.c:2755:\t\t * folio_is_referenced() for us. And rmap walk here\nmm/khugepaged.c-2756-\t\t * is just too costly...\n"
} |
| 47/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57378 output: 34 thoughts: 38 Thoughts: Here's a summary of my thought process: **Diving into Page Management Optimization** Okay, so I see these two functions, `__collapse_huge_page_isolate` and `collapse_scan_pmd`, have been touched. This immediately flags page management and potential huge page-related modifications. Given the nature of these names, I'm expecting performance-related changes, likely aiming for efficiency during memory operations. My next step is to drill down into the context. I'm going to head directly to `mm/page_io.c`. This is where the core page I/O routines and related structures reside. That's the logical place to start looking for the upstream effect of these changes. I'll be searching for how these specific changes fit into the larger I/O or memory management picture within the kernel, such as how pages are handled or collapsed. I'll likely check function calls to see the propagation. |
| 48/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "folio_is_workingset",
"PathPrefix": "mm/page_io.c"
}Results: {
"Output": "mm/page_io.c=452=void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)\n--\nmm/page_io.c-455-\tbool synchronous = sis-\u003eflags \u0026 SWP_SYNCHRONOUS_IO;\nmm/page_io.c:456:\tbool workingset = folio_is_workingset(folio);\nmm/page_io.c-457-\tunsigned long pflags;\n"
} |
| 49/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 57773 output: 33 thoughts: 25 Thoughts: Here's my take on the changes, presented as a first-person expert summary: **Analyzing the `swap_read_folio` Modification** Okay, so they've touched `swap_read_folio`. That's a key function in handling swapping, specifically dealing with the reading of folios. Folios are the newer, more efficient replacement for pages in memory management, so any change here is significant. My immediate instinct is to dig into the context. I need to understand *why* this function has been modified. Is it related to a performance optimization? A bug fix? Addressing a security vulnerability? Or is this part of some broader refactoring or feature implementation within the swap subsystem? The next logical step is to dive into the core implementation. Let's start with `mm/readahead.c`. That's where readahead functionality lives, which means we may find information on how the kernel decides when to initiate a pre-emptive read and the behavior of such reads. Knowing that function, it can help shed light into the changes being made. I suspect whatever's being tweaked in `swap_read_folio` is likely interacting with the readahead logic, or perhaps even driving it in certain scenarios. It's time to start looking at the diffs. I need to quickly identify the affected lines, the nature of the changes (additions, deletions, modifications), and the surrounding code to grasp the intent of the change. This initial scan will help me formulate the right questions and focus my analysis appropriately. Let's see what we've got. |
| 50/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "folio_is_workingset",
"PathPrefix": "mm/readahead.c"
}Results: {
"Output": "mm/readahead.c=222=void page_cache_ra_unbounded(struct readahead_control *ractl,\n--\nmm/readahead.c-304-\t\t\tfolio_set_readahead(folio);\nmm/readahead.c:305:\t\tractl-\u003e_workingset |= folio_is_workingset(folio);\nmm/readahead.c-306-\t\tractl-\u003e_nr_pages += min_nrpages;\n--\nmm/readahead.c=459=static inline int ra_alloc_folio(struct readahead_control *ractl, pgoff_t index,\n--\nmm/readahead.c-476-\tractl-\u003e_nr_pages += 1UL \u003c\u003c order;\nmm/readahead.c:477:\tractl-\u003e_workingset |= folio_is_workingset(folio);\nmm/readahead.c-478-\treturn 0;\n--\nmm/readahead.c=786=void readahead_expand(struct readahead_control *ractl,\n--\nmm/readahead.c-819-\t\t}\nmm/readahead.c:820:\t\tif (unlikely(folio_is_workingset(folio)) \u0026\u0026\nmm/readahead.c-821-\t\t\t\t!ractl-\u003e_workingset) {\n--\nmm/readahead.c-848-\t\t}\nmm/readahead.c:849:\t\tif (unlikely(folio_is_workingset(folio)) \u0026\u0026\nmm/readahead.c-850-\t\t\t\t!ractl-\u003e_workingset) {\n"
} |
| 51/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 58562 output: 33 thoughts: 200 Thoughts: **My Current Line of Reasoning** Okay, so I've been digging into some core file system and memory management stuff, and I see some key functions that have been tweaked – specifically `page_cache_ra_unbounded`, `ra_alloc_folio`, and `readahead_expand`. Those are my entry points, it seems. That gives me a great set of symbols to really hone in on. I'm focusing on performance and how the cache is being managed. My mental checklist includes `folio_mark_accessed`, `workingset_refault`, `lru_gen_look_around`, `filemap_read_folio`, `filemap_map_pages`, and `swap_read_folio`. Those are all prime suspects in this area. Of course `page_cache_ra_unbounded` and `readahead_expand` are worth a second look, given the context. I've also got some static functions in mind – `__split_huge_pud_locked`. The `split_huge_page_to_list` and `split_huge_pmd` functions are related to it, I think. And `collapse_scan_pmd` is also of interest, potentially related to `madvise_collapse` or `khugepaged_scan_pmd`. Now I should zero in on a specific function for analysis. Given the context, let's take a closer look at `split_huge_pmd`. |
| 52/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "split_huge_pmd",
"PathPrefix": "mm/huge_memory.c"
}Results: {
"Output": "mm/huge_memory.c=1908=int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm,\n--\nmm/huge_memory.c-1990-\t\tspin_unlock(dst_ptl);\nmm/huge_memory.c:1991:\t\t__split_huge_pmd(src_vma, src_pmd, addr, false);\nmm/huge_memory.c-1992-\t\treturn -EAGAIN;\n--\nmm/huge_memory.c=2125=vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)\n--\nmm/huge_memory.c-2214-fallback:\nmm/huge_memory.c:2215:\t__split_huge_pmd(vma, vmf-\u003epmd, vmf-\u003eaddress, false);\nmm/huge_memory.c-2216-\treturn VM_FAULT_FALLBACK;\n--\nmm/huge_memory.c=3095=static void __split_huge_zero_page_pmd(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-3140-\nmm/huge_memory.c:3141:static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\nmm/huge_memory.c-3142-\t\tunsigned long haddr, bool freeze)\n--\nmm/huge_memory.c-3198-\t\t * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below\nmm/huge_memory.c:3199:\t\t * inside __split_huge_pmd() ?\nmm/huge_memory.c-3200-\t\t *\n--\nmm/huge_memory.c-3427-\nmm/huge_memory.c:3428:void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,\nmm/huge_memory.c-3429-\t\t\t pmd_t *pmd, bool freeze)\n--\nmm/huge_memory.c-3432-\tif (pmd_trans_huge(*pmd) || pmd_is_valid_softleaf(*pmd))\nmm/huge_memory.c:3433:\t\t__split_huge_pmd_locked(vma, pmd, address, freeze);\nmm/huge_memory.c-3434-}\nmm/huge_memory.c-3435-\nmm/huge_memory.c:3436:void __split_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd,\nmm/huge_memory.c-3437-\t\tunsigned long address, bool freeze)\n--\nmm/huge_memory.c-3446-\tptl = pmd_lock(vma-\u003evm_mm, pmd);\nmm/huge_memory.c:3447:\tsplit_huge_pmd_locked(vma, range.start, pmd, freeze);\nmm/huge_memory.c-3448-\tspin_unlock(ptl);\n--\nmm/huge_memory.c-3451-\nmm/huge_memory.c:3452:void split_huge_pmd_address(struct vm_area_struct *vma, unsigned long address,\nmm/huge_memory.c-3453-\t\tbool freeze)\n--\nmm/huge_memory.c-3459-\nmm/huge_memory.c:3460:\t__split_huge_pmd(vma, pmd, address, freeze);\nmm/huge_memory.c-3461-}\nmm/huge_memory.c-3462-\nmm/huge_memory.c:3463:static inline void split_huge_pmd_if_needed(struct vm_area_struct *vma, unsigned long address)\nmm/huge_memory.c-3464-{\n--\nmm/huge_memory.c-3471-\t\t\t ALIGN(address, HPAGE_PMD_SIZE)))\nmm/huge_memory.c:3472:\t\tsplit_huge_pmd_address(vma, address, false);\nmm/huge_memory.c-3473-}\n--\nmm/huge_memory.c=3475=void vma_adjust_trans_huge(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-3480-\t/* Check if we need to split start first. */\nmm/huge_memory.c:3481:\tsplit_huge_pmd_if_needed(vma, start);\nmm/huge_memory.c-3482-\nmm/huge_memory.c-3483-\t/* Check if we need to split end next. */\nmm/huge_memory.c:3484:\tsplit_huge_pmd_if_needed(vma, end);\nmm/huge_memory.c-3485-\n--\nmm/huge_memory.c-3487-\tif (next)\nmm/huge_memory.c:3488:\t\tsplit_huge_pmd_if_needed(next, end);\nmm/huge_memory.c-3489-}\n"
} |
| 53/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 60432 output: 37 thoughts: 31 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Analyzing `__split_huge_pmd` as a Focus Point** Right, `__split_huge_pmd`... That's a good place to start. It instantly signals a specific area of interest: the critical task of splitting a huge page. That function is clearly a significant player in the management of huge pages, and if there's a problem, it's likely to manifest right there. My initial thought is it is an obvious symbol to focus our attention around. Okay, now let's pivot and take a look at `khugepaged_scan_pmd`. Seeing this mentioned brings the whole background thread into play, and gives a more broad context. We're getting into the proactive, background operations that try to maintain the huge page balance. It's not the split itself, but the *trigger* for the split, and possibly the aftermath. So, now, let's correlate these two functions in my mind. |
| 54/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "khugepaged_scan_pmd",
"PathPrefix": "mm/khugepaged.c"
}Results: {
"Output": "mm/khugepaged.c=1124=static enum scan_result check_pmd_still_valid(struct mm_struct *mm,\n--\nmm/khugepaged.c-1138- * Bring missing pages in from swap, to complete THP collapse.\nmm/khugepaged.c:1139: * Only done if khugepaged_scan_pmd believes it is worthwhile.\nmm/khugepaged.c-1140- *\n--\nmm/khugepaged.c=1592=static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n--\nmm/khugepaged.c-1791-out:\nmm/khugepaged.c:1792:\ttrace_mm_khugepaged_scan_pmd(mm, folio, referenced,\nmm/khugepaged.c-1793-\t\t\t\t none_or_zero, result, unmapped);\n"
} |
| 55/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 60965 output: 34 thoughts: 29 Thoughts: Okay, here's my attempt at summarizing those thoughts as a first-person expert perspective: **Deconstructing `collapse_scan_pmd`** Right, `collapse_scan_pmd`... that function is declared as `static`, so it's only visible within this compilation unit. Hmm, where's it getting called from? Let me see... the name sounds familiar. Ah, yes! I'm pretty sure it's called from `khugepaged_scan_mm_slot`. Makes sense, they seem logically related. Let me mentally trace the memory management flow there and see if my assumption is correct. It's time to dig in and re-evaluate my mental model of these core functions. |
| 56/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "collapse_scan_pmd",
"PathPrefix": "mm/khugepaged.c"
}Results: {
"Output": "mm/khugepaged.c=668=static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n--\nmm/khugepaged.c-721-\nmm/khugepaged.c:722:\t\t/* See collapse_scan_pmd(). */\nmm/khugepaged.c-723-\t\tif (folio_maybe_mapped_shared(folio)) {\n--\nmm/khugepaged.c=1465=static unsigned int max_order_from_offset(unsigned int offset)\n--\nmm/khugepaged.c-1474- * mthp_collapse() consumes the bitmap that is generated during\nmm/khugepaged.c:1475: * collapse_scan_pmd() to determine what regions and mTHP orders fit best.\nmm/khugepaged.c-1476- *\n--\nmm/khugepaged.c=1490=static enum scan_result mthp_collapse(struct mm_struct *mm,\n--\nmm/khugepaged.c-1591-\nmm/khugepaged.c:1592:static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\nmm/khugepaged.c-1593-\t\tstruct vm_area_struct *vma, unsigned long start_addr,\n--\nmm/khugepaged.c=2790=static enum scan_result collapse_single_pmd(unsigned long addr,\n--\nmm/khugepaged.c-2802-\tif (vma_is_anonymous(vma)) {\nmm/khugepaged.c:2803:\t\tresult = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);\nmm/khugepaged.c-2804-\t\tgoto end;\n"
} |
| 57/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 61653 output: 33 thoughts: 23 Thoughts: Here's a summary of my thought process: **Static Function Investigation** Okay, first things first, `collapse_single_pmd` is declared as a static function. That means it's scoped within its current compilation unit, which limits its external visibility and potential for reuse or modification elsewhere. It's essentially a private helper function. Now, let's look closely at `madvise_collapse`. I need to understand its role and how it interacts with this static function. |
| 58/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "madvise_collapse",
"PathPrefix": "mm/khugepaged.c"
}Results: {
"Output": "mm/khugepaged.c=3151=bool current_is_khugepaged(void)\n--\nmm/khugepaged.c-3155-\nmm/khugepaged.c:3156:static int madvise_collapse_errno(enum scan_result r)\nmm/khugepaged.c-3157-{\n--\nmm/khugepaged.c-3187-\nmm/khugepaged.c:3188:int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\nmm/khugepaged.c-3189-\t\t unsigned long end, bool *lock_dropped)\n--\nmm/khugepaged.c-3275-\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\nmm/khugepaged.c:3276:\t\t\t: madvise_collapse_errno(last_fail);\nmm/khugepaged.c-3277-}\n"
} |
| 59/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 62076 output: 32 thoughts: 275 Thoughts: **My Current Assessment of Focus Symbols** Okay, so I'm honing in on the most relevant focus symbols here. I've landed on `madvise_collapse` as a solid starting point for investigation. Now, let's just lay out the current contenders for review: * `folio_mark_accessed` * `workingset_refault` * `lru_gen_look_around` * `filemap_read_folio` * `filemap_map_pages` * `swap_read_folio` * `page_cache_ra_unbounded` * `readahead_expand` * `__split_huge_pmd` * `madvise_collapse` Alright, after reviewing the list again, I think I need to revisit `folio_mark_accessed`. The usage count is high, so it’s likely important. Same goes for `workingset_refault` and `lru_gen_look_around` - they're heavily used, so I need to keep them in scope. And `__split_huge_pmd` remains a good candidate. Of course, `madvise_collapse` is solid. Now, let's pivot and take a look at `mm/madvise.c`. I'm interested in `madvise_cold` and `madvise_pageout`. Even though they're static, I should still explore them because `madvise_vma_behavior` is also static. And given the context, the entry point for this is probably the `sys_madvise` syscall. So, let's track down the implementation of that. |
| 60/2 | 2026/08/03 21:28 | tool | grepper |
0mError:no matches Args: {
"Expression": "sys_madvise",
"PathPrefix": "mm/madvise.c"
}Results: {
"error": "no matches"
} |
| 61/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 62771 output: 36 thoughts: 20 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person: **Kernel Memory Management – A Quick Mental Jog** Right, so I'm thinking about `madvise`, or something *very* similar. My mind immediately jumps to that `SYSCALL_DEFINE3` macro... yeah, it’s probably a function declared in a macro like `SYSCALL_DEFINE3(madvise, ...)`. I'm mentally parsing the arguments right now. The exact number of arguments isn't critical right now – just the fact it's a *three* argument system call definition. That implies it would take something like a memory region (address and length), and an advice code as arguments. It sets up the system call entry point and the boilerplate to handle the system call from userspace. I'm almost certainly thinking about the kernel-side implementation here. I'm focusing on the core aspects. |
| 62/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "SYSCALL_DEFINE.*madvise",
"PathPrefix": "mm/madvise.c"
}Results: {
"Output": "mm/madvise.c=1993=int do_madvise(struct mm_struct *mm, unsigned long start, size_t len_in, int behavior)\n--\nmm/madvise.c-2020-\nmm/madvise.c:2021:SYSCALL_DEFINE3(madvise, unsigned long, start, size_t, len_in, int, behavior)\nmm/madvise.c-2022-{\n--\nmm/madvise.c=2027=static ssize_t vector_madvise(struct mm_struct *mm, struct iov_iter *iter,\n--\nmm/madvise.c-2093-\nmm/madvise.c:2094:SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec,\nmm/madvise.c-2095-\t\tsize_t, vlen, int, behavior, unsigned int, flags)\n"
} |
| 63/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 63283 output: 32 thoughts: 775 Thoughts: **Focus Symbol Assessment: A Quick Scan of VM and File System Components** Okay, so I'm thinking about picking a good focus symbol, and `do_madvise` seems like a good starting point. I need to quickly assess candidates to understand where to dig deeper. Let's start with `mm/vmscan.c`. I'm going to look for promising entry points. First, let's examine `folio_inc_lru_refs`. Aha, it's not static, meaning it's accessible. Wait, it's defined in `mm/vmscan.c` and declared in `include/linux/mm_inline.h`. Let's see if it's exported... it isn't, but it's in the symbol table, so that makes it a good candidate. Now, let's keep going. `folio_inc_lru_refs_walk`, static. Not ideal for broader investigation. Same with `folio_inc_lru_refs_isolated`. Those are internal. Then, `walk_pte_range`? Static. Not it. `lru_gen_look_around`? Non-static! That's interesting, potential entry point. Okay, more static: `lru_gen_age_node`, `shrink_node`, `shrink_lruvec`, `evict_folios`, those are not useful to me. Now, `try_to_free_pages`? Non-static! `balance_pgdat`? Also non-static. `reclaim_pages`? Non-static, potentially very interesting. Now, `shrink_node_memcgs`? Static. `folio_check_references`, `shrink_folio_list`, `shrink_active_list`, and `shrink_inactive_list`? All static. So, from `mm/vmscan.c`, the list of good focus symbols boils down to: - `folio_inc_lru_refs` - `lru_gen_look_around` - `try_to_free_pages` - `balance_pgdat` - `reclaim_pages` Next, moving to `mm/workingset.c`. Let's check `workingset_refault` and `workingset_eviction`. Both are non-static. Worth a look! Let's check `mm/folio.c` for `folio_mark_accessed`. Non-static! Got it. Finally, in `mm/filemap.c`, let's check `filemap_read_folio`. Wait, is `filemap_read_folio` really static? I'll double-check that one. |
| 64/2 | 2026/08/03 21:28 | tool | grepper |
0mArgs:{
"Expression": "filemap_read_folio",
"PathPrefix": "mm/filemap.c"
}Results: {
"Output": "mm/filemap.c=2469=static void filemap_get_read_batch(struct address_space *mapping,\n--\nmm/filemap.c-2509-\nmm/filemap.c:2510:static int filemap_read_folio(struct file *file, filler_t filler,\nmm/filemap.c-2511-\t\tstruct folio *folio)\n--\nmm/filemap.c=2563=static int filemap_update_page(struct kiocb *iocb,\n--\nmm/filemap.c-2606-\nmm/filemap.c:2607:\terror = filemap_read_folio(iocb-\u003eki_filp, mapping-\u003ea_ops-\u003eread_folio,\nmm/filemap.c-2608-\t\t\tfolio);\n--\nmm/filemap.c=2619=static int filemap_create_folio(struct kiocb *iocb, struct folio_batch *fbatch)\n--\nmm/filemap.c-2657-\nmm/filemap.c:2658:\terror = filemap_read_folio(iocb-\u003eki_filp, mapping-\u003ea_ops-\u003eread_folio,\nmm/filemap.c-2659-\t\t\t\t\tfolio);\n--\nmm/filemap.c=3555=vm_fault_t filemap_fault(struct vm_fault *vmf)\n--\nmm/filemap.c-3688-\tfpin = maybe_unlock_mmap_for_io(vmf, fpin);\nmm/filemap.c:3689:\terror = filemap_read_folio(file, mapping-\u003ea_ops-\u003eread_folio, folio);\nmm/filemap.c-3690-\tif (fpin)\n--\nmm/filemap.c=4108=static struct folio *do_read_cache_folio(struct address_space *mapping,\n--\nmm/filemap.c-4155-filler:\nmm/filemap.c:4156:\terr = filemap_read_folio(file, filler, folio);\nmm/filemap.c-4157-\tif (err) {\n"
} |
| 65/2 | 2026/08/03 21:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 65230 output: 32 thoughts: 796 Thoughts: **Refining the List of Key Functions for Analysis** Okay, so I'm thinking through this list of functions and how they relate to the core changes. My initial sweep identified a set of potentially interesting symbols, and now I'm refining that list based on my expert understanding of kernel behavior. First, I noted that `filemap_read_folio` is static, which is less relevant for this analysis. However, `filemap_fault` and `filemap_map_pages` are non-static and warrant a deeper look, though I suspect they are high-frequency hot-path functions. Similarly, I see `swap_read_folio` in `mm/page_io.c`, which is also non-static, and so it stays on the list. I've also found `page_cache_ra_unbounded` and `readahead_expand` in `mm/readahead.c` that look interesting, as well as several functions related to THP and madvise. I've taken a quick look to confirm non-static status: `__split_huge_pmd` in `mm/huge_memory.c` and `madvise_collapse` in `mm/khugepaged.c`. I have also looked at `do_madvise`. Now, the key is to filter this list and home in on the functions that are most directly impacted by the changes in this patch. I want to avoid the extremely generic, high-volume functions. So, while I'm keeping `filemap_fault`, `filemap_map_pages`, `folio_mark_accessed`, and `page_cache_ra_unbounded` on the list, I'm aware that they are probably pretty hot. I'm also considering `workingset_refault` and `workingset_eviction`, which are very likely to be important. The *really* critical function introduced in this patch is `folio_inc_lru_refs` so I have kept it. I am also keeping `lru_gen_look_around` as it is an important companion function. `swap_read_folio`, `__split_huge_pmd`, `madvise_collapse`, and `do_madvise` are remaining on the list because they are specific to certain subsystem. Next, I verified that `folio_inc_lru_refs` is defined, finding it in `mm/vmscan.c` as `int folio_inc_lru_refs(struct folio *folio, bool is_fault, bool is_exec)`. Now, I'm thinking about configuration options. I should definitely enable `CONFIG_LRU_GEN` and `CONFIG_LRU_GEN_ENABLED`, as they are critical. Also, I'll need `CONFIG_MEMCG`. In addition, `CONFIG_TRANSPARENT_HUGEPAGE` is needed for `__split_huge_pmd` and `madvise_collapse`, and `CONFIG_SWAP` for `swap_read_folio`. I'll double-check `CONFIG_TRANSPARENT_HUGEPAGE` in `mm/Kconfig` to be sure I haven't missed anything. |
| 66/2 | 2026/08/03 21:29 | tool | grepper |
0mArgs:{
"Expression": "config TRANSPARENT_HUGEPAGE",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=829=config MM_ID\n--\nmm/Kconfig-831-\nmm/Kconfig:832:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-833-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=850=choice\n--\nmm/Kconfig-855-\nmm/Kconfig:856:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-857-\t\tbool \"always\"\n--\nmm/Kconfig-862-\nmm/Kconfig:863:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-864-\t\tbool \"madvise\"\n--\nmm/Kconfig-871-\nmm/Kconfig:872:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-873-\t\tbool \"never\"\n--\nmm/Kconfig=879=choice\n--\nmm/Kconfig-888-\nmm/Kconfig:889:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-890-\t\tbool \"never\"\n--\nmm/Kconfig-898-\nmm/Kconfig:899:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-900-\t\tbool \"always\"\n--\nmm/Kconfig-906-\nmm/Kconfig:907:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-908-\t\tbool \"within_size\"\n--\nmm/Kconfig-914-\nmm/Kconfig:915:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-916-\t\tbool \"advise\"\n--\nmm/Kconfig=924=choice\n--\nmm/Kconfig-933-\nmm/Kconfig:934:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-935-\t\tbool \"never\"\n--\nmm/Kconfig-942-\nmm/Kconfig:943:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-944-\t\tbool \"always\"\n--\nmm/Kconfig-950-\nmm/Kconfig:951:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-952-\t\tbool \"within_size\"\n--\nmm/Kconfig-958-\nmm/Kconfig:959:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-960-\t\tbool \"advise\"\n"
} |
| 67/2 | 2026/08/03 21:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 67460 output: 27 thoughts: 32 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Quick Configuration Check** Alright, *TRANSPARENT\_HUGEPAGE* seems like a solid choice – always a good sign for improved memory management. But before I get too invested, let's just do a quick sanity check. I'm going to jump over to *mm/Kconfig* because I need to quickly confirm the *CONFIG\_SWAP* setting. I just want to ensure that the swap configuration plays nicely with whatever decisions are made regarding THP. Gotta make sure we're not inadvertently creating any conflicts or performance bottlenecks down the line. It's all about ensuring these two configurations are correctly set, that they integrate seamlessly, and don't introduce performance issues or compatibility problems. |
| 68/2 | 2026/08/03 21:29 | tool | grepper |
0mArgs:{
"Expression": "config SWAP",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=9=config ARCH_NO_SWAP\n--\nmm/Kconfig-11-\nmm/Kconfig:12:menuconfig SWAP\nmm/Kconfig-13-\tbool \"Support for paging of anonymous memory (swap)\"\n"
} |
| 69/2 | 2026/08/03 21:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 67759 output: 229 thoughts: 377 Thoughts: **My Assessment of the New LRU Reference Tracking Mechanism** Okay, so this `SWAP` feature looks promising. Seems like a good starting point. I see the related configurations: `LRU_GEN`, `LRU_GEN_ENABLED`, `MEMCG`, `TRANSPARENT_HUGEPAGE`, and of course, `SWAP` are all in play here. I need to focus on these symbols: `folio_inc_lru_refs`, `lru_gen_look_around`, `workingset_refault`, `workingset_eviction`, `swap_read_folio`, `__split_huge_pmd`, `madvise_collapse`, and `do_madvise`. Essentially, this patch is introducing a new LRU reference tracking system. It's replacing the old `PG_referenced` and `PG_workingset` flags with a multi-bit reference count. This refactor is significant as it touches key areas of memory management, including page fault handling, page cache read-ahead, swap, THP splitting, and madvise calls. Because it's impacting core page reclaim and aging logic, it's definitely something that should be thoroughly fuzzed. Let me just confirm the best entry points. Ah, `do_madvise` is perfect. That's the main entry for the `madvise` syscall. `madvise_collapse` looks solid as well, as does `__split_huge_pmd`. `swap_read_folio` is also definitely one to examine, along with `workingset_refault` and `workingset_eviction`. And of course, `lru_gen_look_around`. Finally, `folio_inc_lru_refs` is likely the key function introduced by this patch. Right, I'm ready to use the `set-results` tool now. |
| 70/2 | 2026/08/03 21:29 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG",
"TRANSPARENT_HUGEPAGE",
"SWAP"
],
"FocusSymbols": [
"folio_inc_lru_refs",
"lru_gen_look_around",
"workingset_refault",
"workingset_eviction",
"swap_read_folio",
"__split_huge_pmd",
"madvise_collapse",
"do_madvise"
],
"Reasoning": "This patch introduces a new LRU reference tracking mechanism for both MGLRU and non-MGLRU, replacing PG_referenced and PG_workingset with a multi-bit reference count. It modifies core memory management paths including page fault handling, page cache read-ahead, swap, THP splitting, and madvise behaviors. The changes are highly functional and affect the core page reclaim and aging logic, making it worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG",
"TRANSPARENT_HUGEPAGE",
"SWAP"
],
"FocusSymbols": [
"folio_inc_lru_refs",
"lru_gen_look_around",
"workingset_refault",
"workingset_eviction",
"swap_read_folio",
"__split_huge_pmd",
"madvise_collapse",
"do_madvise"
],
"Reasoning": "This patch introduces a new LRU reference tracking mechanism for both MGLRU and non-MGLRU, replacing PG_referenced and PG_workingset with a multi-bit reference count. It modifies core memory management paths including page fault handling, page cache read-ahead, swap, THP splitting, and madvise behaviors. The changes are highly functional and affect the core page reclaim and aging logic, making it worth fuzzing.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|