| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/18 05:47 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG"
],
"FocusSymbols": [
"mem_cgroup_update_lru_size",
"folio_migrate_flags",
"lru_gen_look_around"
],
"KMSANReasoning": "The patch refactors internal memory management logic, specifically folio LRU generation flags and reference tracking, and changes a counter (`lru_zone_size`) to an atomic variable to fix concurrency issues. It does not introduce any new structures that are copied to user space, nor does it allocate complex data structures that might be left partially uninitialized. The local variable `file` introduced in `walk_update_folio` is only used when `folio_update_gen` returns a non-negative value, which guarantees it has been initialized. Therefore, there is no risk of uninitialized memory usage or info-leaks. Standard KASAN and other bug detectors are sufficient to catch any potential out-of-bounds accesses or use-after-free bugs related to these changes.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the Multi-Gen LRU (MGLRU) implementation, changing how LRU generation and reference flags are stored in `folio-\u003eflags`. It replaces direct bitmask operations with new helper functions and changes `lru_zone_size` in `struct mem_cgroup_per_node` to use `atomic_long_t` instead of `unsigned long`. These changes affect core memory management paths, including page migration, LRU size tracking, and page table scanning. The code is reachable and functionally alters how folios are tracked and aged in the LRU lists, making it worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/18 05:47 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 8a925d2546c538d4b3b0dc1755caac894989d9a0\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 18 05:47:39 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h\nindex e78bc98ab229b..b13e3f0563195 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 (WARN_ON_ONCE(val \u003c 0))\n+\t\treturn 0;\n+\n+\treturn val;\n }\n \n void __mem_cgroup_handle_over_high(gfp_t gfp_mask);\ndiff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h\nindex 621c8653d8f7e..4bb390d9516ef 100644\n--- a/include/linux/mm_inline.h\n+++ b/include/linux/mm_inline.h\n@@ -10,6 +10,11 @@\n #include \u003clinux/userfaultfd_k.h\u003e\n #include \u003clinux/leafops.h\u003e\n \n+static inline int folio_flags_is_file_lru(const unsigned long *flags)\n+{\n+\treturn !test_bit(PG_swapbacked, flags);\n+}\n+\n /**\n * folio_is_file_lru - Should the folio be on a file LRU or anon LRU?\n * @folio: The folio to test.\n@@ -27,7 +32,7 @@\n */\n static inline int folio_is_file_lru(const struct folio *folio)\n {\n-\treturn !folio_test_swapbacked(folio);\n+\treturn folio_flags_is_file_lru(const_folio_flags(folio, 0));\n }\n \n static __always_inline void __update_lru_size(struct lruvec *lruvec,\n@@ -142,10 +147,43 @@ static inline int lru_tier_from_refs(int refs, bool workingset)\n \treturn workingset ? MAX_NR_TIERS - 1 : order_base_2(refs);\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+\tint gen = ((flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF);\n+\n+\tBUILD_BUG_ON(LRU_GEN_MASK \u0026 LRU_REFS_MASK);\n+\tgen -= 1;\n+\tVM_WARN_ON_ONCE(gen != -1 \u0026\u0026 gen \u003e LRU_GEN_MAX);\n+\treturn gen;\n+}\n+\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+\tVM_WARN_ON_ONCE(gen \u003e LRU_GEN_MAX || gen \u003c 0);\n+\tBUILD_BUG_ON((LRU_GEN_MAX + 1) != MAX_NR_GENS);\n+\n+\t*flags \u0026= ~LRU_GEN_MASK;\n+\t*flags |= (gen + 1UL) \u003c\u003c LRU_GEN_PGOFF;\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 \tif (!(flags \u0026 BIT(PG_referenced)))\n \t\treturn 0;\n \t/*\n@@ -155,18 +193,47 @@ static inline int folio_lru_refs(const struct folio *folio)\n \treturn ((flags \u0026 LRU_REFS_MASK) \u003e\u003e LRU_REFS_PGOFF) + 1;\n }\n \n-static inline int folio_lru_gen(const struct folio *folio)\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 != (LRU_REFS_MASK \u003e\u003e LRU_REFS_PGOFF) + 1);\n+\n+\t*flags \u0026= ~LRU_REFS_FLAGS;\n+\tif (!refs)\n+\t\treturn;\n+\t*flags |= (BIT(PG_referenced) | ((refs - 1UL) \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 flags = READ_ONCE(folio-\u003eflags.f);\n+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n \n-\treturn ((flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF) - 1;\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+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 \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@@ -270,7 +337,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 +362,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,11 +371,19 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,\n \treturn true;\n }\n \n+/**\n+ * folio_migrate_refs - copy the reference state to a new folio\n+ * @new: the destination folio\n+ * @old: the source folio\n+ *\n+ * Transfer the reference state to @new during migration: the MGLRU\n+ * refs count, including PG_referenced, or just PG_referenced for the\n+ * active/inactive LRU.\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+\tBUILD_BUG_ON(LRU_REFS_MASK \u0026 BIT(PG_referenced));\n+\tfolio_set_lru_refs(new, folio_lru_refs(old));\n }\n #else /* !CONFIG_LRU_GEN */\n \n@@ -339,7 +414,8 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,\n \n static inline void folio_migrate_refs(struct folio *new, const struct folio *old)\n {\n-\n+\tif (folio_test_referenced(old))\n+\t\tfolio_set_referenced(new);\n }\n #endif /* CONFIG_LRU_GEN */\n \ndiff --git a/include/linux/mmzone.h b/include/linux/mmzone.h\nindex 94f9c3ff54160..d0b5c6217d257 100644\n--- a/include/linux/mmzone.h\n+++ b/include/linux/mmzone.h\n@@ -492,11 +492,15 @@ enum lruvec_flags {\n * folio-\u003eflags, masked by LRU_REFS_MASK.\n */\n #define MAX_NR_TIERS\t\t4U\n+#define LRU_TIER_MIN\t\t0U\n+#define LRU_TIER_MAX\t\t(MAX_NR_TIERS - 1)\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+#define LRU_REFS_MAX\t\tBIT(LRU_REFS_WIDTH)\n \n /*\n * For folios accessed multiple times through file descriptors,\ndiff --git a/mm/folio.c b/mm/folio.c\nindex 59c477120b9a8..0adfe4f5ef72f 100644\n--- a/mm/folio.c\n+++ b/mm/folio.c\n@@ -353,26 +353,28 @@ static void __lru_cache_activate_folio(struct folio *folio)\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+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n+\tint refs;\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+\tif (!folio_lru_refs(folio)) {\n+\t\tfolio_set_lru_refs(folio, 1);\n \t\treturn;\n \t}\n \n \tdo {\n-\t\tif ((old_flags \u0026 LRU_REFS_MASK) == LRU_REFS_MASK) {\n+\t\tnew_flags = old_flags;\n+\t\trefs = lru_refs_from_flags(old_flags);\n+\t\tif (refs == LRU_REFS_MAX) {\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+\t\tlru_refs_set_flags(\u0026new_flags, refs + 1);\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n }\n \n static bool lru_gen_clear_refs(struct folio *folio)\n@@ -384,7 +386,8 @@ 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+\tfolio_clear_workingset(folio);\n \n \trcu_read_lock();\n \tseq = READ_ONCE(folio_lruvec(folio)-\u003elrugen.min_seq[type]);\ndiff --git a/mm/memcontrol.c b/mm/memcontrol.c\nindex 1d3339520809f..9d0ee3d3bda7c 100644\n--- a/mm/memcontrol.c\n+++ b/mm/memcontrol.c\n@@ -1529,28 +1529,12 @@ 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 15b45832bcfa7..82307332711f4 100644\n--- a/mm/migrate.c\n+++ b/mm/migrate.c\n@@ -776,8 +776,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@@ -807,7 +805,9 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)\n \tif (folio_test_idle(folio))\n \t\tfolio_set_idle(newfolio);\n \n+\t/* Copy the reference state, including PG_referenced */\n \tfolio_migrate_refs(newfolio, folio);\n+\n \t/*\n \t * Copy NUMA information to the new page, to prevent over-eager\n \t * future migrations of this same page.\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex c1404a59523d6..7e3ae0c6cba3b 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -843,19 +843,22 @@ static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_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\tfolio_set_lru_refs(folio, 0);\n+\t\t\tfolio_set_workingset(folio);\n \t\t\treturn true;\n \t\t}\n \n-\t\tset_mask_bits(\u0026folio-\u003eflags.f, LRU_REFS_MASK, BIT(PG_referenced));\n+\t\tfolio_set_lru_refs(folio, 1);\n \t\treturn false;\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+\tif (folio_lru_refs(folio) \u003e 1) {\n+\t\tfolio_set_lru_refs(folio, 0);\n+\t\tfolio_set_workingset(folio);\n+\t} else {\n \t\tfolio_mark_accessed(folio);\n+\t}\n \treturn true;\n }\n #else\n@@ -3195,8 +3198,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@@ -3205,7 +3208,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@@ -3266,11 +3269,11 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)\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+static int folio_update_gen(struct folio *folio, int new_gen, int *is_file,\n+\t\t\t 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+\tunsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));\n+\tint old_gen;\n \n \t/*\n \t * See the comment on LRU_REFS_FLAGS, and activate file-backed\n@@ -3279,20 +3282,25 @@ static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma\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\tfolio_set_lru_refs(folio, 1);\n \t\treturn -1;\n \t}\n \n \tdo {\n+\t\told_gen = lru_gen_from_flags(old_flags);\n+\t\tnew_flags = old_flags;\n+\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+\t\tif (old_gen \u003c 0)\n+\t\t\tbreak;\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+\t\tlru_gen_set_flags(\u0026new_flags, new_gen);\n+\t\tlru_refs_set_flags(\u0026new_flags, 0);\n+\t\tnew_flags |= BIT(PG_workingset);\n+\t} while (!try_cmpxchg(folio_flags(folio, 0), \u0026old_flags, new_flags));\n \n-\treturn ((old_flags \u0026 LRU_GEN_MASK) \u003e\u003e LRU_GEN_PGOFF) - 1;\n+\t*is_file = folio_flags_is_file_lru(\u0026old_flags);\n+\treturn old_gen;\n }\n \n /* protect pages accessed multiple times through file descriptors */\n@@ -3300,22 +3308,25 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)\n {\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 new_gen, old_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\told_gen = lru_gen_from_flags(old_flags);\n+\t\t/* This helper should never be called for off-list folios */\n+\t\tVM_WARN_ON_ONCE(old_gen \u003c 0);\n+\t\tif (old_gen \u003c 0)\n+\t\t\treturn min_gen;\n \n-\t\tnew_gen = (old_gen + 1) % MAX_NR_GENS;\n+\t\t/* folio_update_gen() has promoted this page? */\n+\t\tif (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_flags = old_flags;\n+\t\tnew_gen = (min_gen + 1) % MAX_NR_GENS;\n+\t\tlru_gen_set_flags(\u0026new_flags, new_gen);\n+\t\tlru_refs_set_flags(\u0026new_flags, 0);\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@@ -3323,9 +3334,8 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)\n }\n \n static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio,\n-\t\t\t int old_gen, int new_gen)\n+\t\t\t int old_gen, int new_gen, int type)\n {\n-\tint type = folio_is_file_lru(folio);\n \tint zone = folio_zonenum(folio);\n \tint delta = folio_nr_pages(folio);\n \n@@ -3512,22 +3522,24 @@ 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, file;\n \n \tif (!folio)\n \t\treturn;\n \n+\tnew_gen = lru_gen_from_seq(READ_ONCE(lruvec-\u003elrugen.max_seq));\n+\n \tif (dirty \u0026\u0026 !folio_test_dirty(folio) \u0026\u0026\n \t !(folio_test_anon(folio) \u0026\u0026 folio_test_swapbacked(folio) \u0026\u0026\n \t !folio_test_swapcache(folio)))\n \t\tfolio_mark_dirty(folio);\n \n \tif (walk) {\n-\t\told_gen = folio_update_gen(folio, new_gen, \u0026vma-\u003eflags);\n+\t\told_gen = folio_update_gen(folio, new_gen, \u0026file, \u0026vma-\u003eflags);\n \t\tif (old_gen \u003e= 0 \u0026\u0026 old_gen != new_gen)\n-\t\t\tupdate_batch_size(walk, folio, old_gen, new_gen);\n+\t\t\tupdate_batch_size(walk, folio, old_gen, new_gen, file);\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@@ -3549,8 +3561,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@@ -3601,7 +3611,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@@ -3614,7 +3624,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@@ -3637,8 +3647,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@@ -3692,7 +3700,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@@ -3706,7 +3714,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@@ -4270,8 +4278,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@@ -4308,8 +4314,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@@ -4341,7 +4345,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@@ -4353,13 +4357,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@@ -4711,7 +4716,7 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca\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+\t\tfolio_set_lru_refs(folio, 0);\n \n \tsuccess = lru_gen_del_folio(lruvec, folio, true);\n \tVM_WARN_ON_ONCE_FOLIO(!success, folio);\n@@ -4804,9 +4809,9 @@ static int get_tier_idx(struct lruvec *lruvec, int type)\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+\tread_ctrl_pos(lruvec, type, LRU_TIER_MIN, LRU_TIER_MIN, 2, \u0026sp);\n+\tfor (tier = LRU_TIER_MIN + 1; tier \u003c= LRU_TIER_MAX; tier++) {\n+\t\tread_ctrl_pos(lruvec, type, tier, tier, 3, \u0026pv);\n \t\tif (!positive_ctrl_err(\u0026sp, \u0026pv))\n \t\t\tbreak;\n \t}\n@@ -4827,8 +4832,10 @@ 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, LRU_TIER_MIN, LRU_TIER_MAX,\n+\t\t swappiness, \u0026sp);\n+\tread_ctrl_pos(lruvec, LRU_GEN_FILE, LRU_TIER_MIN, LRU_TIER_MAX,\n+\t\t MAX_SWAPPINESS - swappiness, \u0026pv);\n \n \treturn positive_ctrl_err(\u0026sp, \u0026pv);\n }\n@@ -4927,8 +4934,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n \t\t}\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\tif (lru_gen_folio_seq(lruvec, folio, false) == min_seq[type]) {\n+\t\t\tfolio_set_lru_refs(folio, 0);\n+\t\t\tfolio_set_active(folio);\n+\t\t}\n \t}\n \n \tmove_folios_to_lru(\u0026list);\n"
} |
| 2/1 | 2026/08/18 05:47 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG"
],
"FocusSymbols": [
"mem_cgroup_update_lru_size",
"folio_migrate_flags",
"lru_gen_look_around"
],
"Reasoning": "The patch modifies the Multi-Gen LRU (MGLRU) implementation, changing how LRU generation and reference flags are stored in `folio-\u003eflags`. It replaces direct bitmask operations with new helper functions and changes `lru_zone_size` in `struct mem_cgroup_per_node` to use `atomic_long_t` instead of `unsigned long`. These changes affect core memory management paths, including page migration, LRU size tracking, and page table scanning. The code is reachable and functionally alters how folios are tracked and aged in the LRU lists, 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 8a925d2546c538d4b3b0dc1755caac894989d9a0
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 18 05:47:39 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index e78bc98ab229b..b13e3f0563195 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 (WARN_ON_ONCE(val < 0))
+ return 0;
+
+ return val;
}
void __mem_cgroup_handle_over_high(gfp_t gfp_mask);
diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
index 621c8653d8f7e..4bb390d9516ef 100644
--- a/include/linux/mm_inline.h
+++ b/include/linux/mm_inline.h
@@ -10,6 +10,11 @@
#include <linux/userfaultfd_k.h>
#include <linux/leafops.h>
+static inline int folio_flags_is_file_lru(const unsigned long *flags)
+{
+ return !test_bit(PG_swapbacked, flags);
+}
+
/**
* folio_is_file_lru - Should the folio be on a file LRU or anon LRU?
* @folio: The folio to test.
@@ -27,7 +32,7 @@
*/
static inline int folio_is_file_lru(const struct folio *folio)
{
- return !folio_test_swapbacked(folio);
+ return folio_flags_is_file_lru(const_folio_flags(folio, 0));
}
static __always_inline void __update_lru_size(struct lruvec *lruvec,
@@ -142,10 +147,43 @@ static inline int lru_tier_from_refs(int refs, bool workingset)
return workingset ? MAX_NR_TIERS - 1 : order_base_2(refs);
}
-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)
+{
+ int gen = ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF);
+
+ BUILD_BUG_ON(LRU_GEN_MASK & LRU_REFS_MASK);
+ gen -= 1;
+ VM_WARN_ON_ONCE(gen != -1 && gen > LRU_GEN_MAX);
+ return gen;
+}
+
+/**
+ * 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);
+ VM_WARN_ON_ONCE(gen > LRU_GEN_MAX || gen < 0);
+ BUILD_BUG_ON((LRU_GEN_MAX + 1) != MAX_NR_GENS);
+
+ *flags &= ~LRU_GEN_MASK;
+ *flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+}
+/**
+ * lru_refs_from_flags - Return LRU referenced / access count from folio flags.
+ * @flags: folio flags
+ */
+static inline int lru_refs_from_flags(unsigned long flags)
+{
if (!(flags & BIT(PG_referenced)))
return 0;
/*
@@ -155,18 +193,47 @@ static inline int folio_lru_refs(const struct folio *folio)
return ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) + 1;
}
-static inline int folio_lru_gen(const struct folio *folio)
+/**
+ * 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 != (LRU_REFS_MASK >> LRU_REFS_PGOFF) + 1);
+
+ *flags &= ~LRU_REFS_FLAGS;
+ if (!refs)
+ return;
+ *flags |= (BIT(PG_referenced) | ((refs - 1UL) << 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 flags = READ_ONCE(folio->flags.f);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
- return ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+ do {
+ new_flags = old_flags;
+ lru_refs_set_flags(&new_flags, refs);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
+}
+
+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;
- 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);
@@ -270,7 +337,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 +362,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,11 +371,19 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
return true;
}
+/**
+ * folio_migrate_refs - copy the reference state to a new folio
+ * @new: the destination folio
+ * @old: the source folio
+ *
+ * Transfer the reference state to @new during migration: the MGLRU
+ * refs count, including PG_referenced, or just PG_referenced for the
+ * active/inactive LRU.
+ */
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);
+ BUILD_BUG_ON(LRU_REFS_MASK & BIT(PG_referenced));
+ folio_set_lru_refs(new, folio_lru_refs(old));
}
#else /* !CONFIG_LRU_GEN */
@@ -339,7 +414,8 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
static inline void folio_migrate_refs(struct folio *new, const struct folio *old)
{
-
+ if (folio_test_referenced(old))
+ folio_set_referenced(new);
}
#endif /* CONFIG_LRU_GEN */
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 94f9c3ff54160..d0b5c6217d257 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -492,11 +492,15 @@ enum lruvec_flags {
* folio->flags, masked by LRU_REFS_MASK.
*/
#define MAX_NR_TIERS 4U
+#define LRU_TIER_MIN 0U
+#define LRU_TIER_MAX (MAX_NR_TIERS - 1)
#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)
+#define LRU_REFS_MAX BIT(LRU_REFS_WIDTH)
/*
* For folios accessed multiple times through file descriptors,
diff --git a/mm/folio.c b/mm/folio.c
index 59c477120b9a8..0adfe4f5ef72f 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -353,26 +353,28 @@ static void __lru_cache_activate_folio(struct folio *folio)
static void lru_gen_inc_refs(struct folio *folio)
{
- unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
+ int refs;
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));
+ if (!folio_lru_refs(folio)) {
+ folio_set_lru_refs(folio, 1);
return;
}
do {
- if ((old_flags & LRU_REFS_MASK) == LRU_REFS_MASK) {
+ new_flags = old_flags;
+ refs = lru_refs_from_flags(old_flags);
+ if (refs == LRU_REFS_MAX) {
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));
+ lru_refs_set_flags(&new_flags, refs + 1);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
}
static bool lru_gen_clear_refs(struct folio *folio)
@@ -384,7 +386,8 @@ 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);
+ folio_clear_workingset(folio);
rcu_read_lock();
seq = READ_ONCE(folio_lruvec(folio)->lrugen.min_seq[type]);
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 1d3339520809f..9d0ee3d3bda7c 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1529,28 +1529,12 @@ 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 15b45832bcfa7..82307332711f4 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -776,8 +776,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)) {
@@ -807,7 +805,9 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)
if (folio_test_idle(folio))
folio_set_idle(newfolio);
+ /* Copy the reference state, including PG_referenced */
folio_migrate_refs(newfolio, folio);
+
/*
* Copy NUMA information to the new page, to prevent over-eager
* future migrations of this same page.
diff --git a/mm/vmscan.c b/mm/vmscan.c
index c1404a59523d6..7e3ae0c6cba3b 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -843,19 +843,22 @@ static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_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));
+ folio_set_lru_refs(folio, 0);
+ folio_set_workingset(folio);
return true;
}
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced));
+ folio_set_lru_refs(folio, 1);
return false;
}
/* Promote on second access */
- if (folio_lru_refs(folio) > 1)
- set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset));
- else
+ if (folio_lru_refs(folio) > 1) {
+ folio_set_lru_refs(folio, 0);
+ folio_set_workingset(folio);
+ } else {
folio_mark_accessed(folio);
+ }
return true;
}
#else
@@ -3195,8 +3198,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;
@@ -3205,7 +3208,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] +
@@ -3266,11 +3269,11 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)
******************************************************************************/
/* promote pages accessed through page tables */
-static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags)
+static int folio_update_gen(struct folio *folio, int new_gen, int *is_file,
+ 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);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
+ int old_gen;
/*
* See the comment on LRU_REFS_FLAGS, and activate file-backed
@@ -3279,20 +3282,25 @@ static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma
*/
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));
+ folio_set_lru_refs(folio, 1);
return -1;
}
do {
+ old_gen = lru_gen_from_flags(old_flags);
+ new_flags = old_flags;
+
/* lru_gen_del_folio() has isolated this page? */
- if (!(old_flags & LRU_GEN_MASK))
- return -1;
+ if (old_gen < 0)
+ break;
- 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));
+ lru_gen_set_flags(&new_flags, new_gen);
+ lru_refs_set_flags(&new_flags, 0);
+ new_flags |= BIT(PG_workingset);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
- return ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+ *is_file = folio_flags_is_file_lru(&old_flags);
+ return old_gen;
}
/* protect pages accessed multiple times through file descriptors */
@@ -3300,22 +3308,25 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
{
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 new_gen, old_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;
+ old_gen = lru_gen_from_flags(old_flags);
+ /* This helper should never be called for off-list folios */
+ VM_WARN_ON_ONCE(old_gen < 0);
+ if (old_gen < 0)
+ return min_gen;
- new_gen = (old_gen + 1) % MAX_NR_GENS;
+ /* folio_update_gen() has promoted this page? */
+ if (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_flags = old_flags;
+ new_gen = (min_gen + 1) % MAX_NR_GENS;
+ lru_gen_set_flags(&new_flags, new_gen);
+ lru_refs_set_flags(&new_flags, 0);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
lru_gen_update_size(lruvec, folio, old_gen, new_gen);
@@ -3323,9 +3334,8 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
}
static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio,
- int old_gen, int new_gen)
+ int old_gen, int new_gen, int type)
{
- int type = folio_is_file_lru(folio);
int zone = folio_zonenum(folio);
int delta = folio_nr_pages(folio);
@@ -3512,22 +3522,24 @@ 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, file;
if (!folio)
return;
+ new_gen = lru_gen_from_seq(READ_ONCE(lruvec->lrugen.max_seq));
+
if (dirty && !folio_test_dirty(folio) &&
!(folio_test_anon(folio) && folio_test_swapbacked(folio) &&
!folio_test_swapcache(folio)))
folio_mark_dirty(folio);
if (walk) {
- old_gen = folio_update_gen(folio, new_gen, &vma->flags);
+ old_gen = folio_update_gen(folio, new_gen, &file, &vma->flags);
if (old_gen >= 0 && old_gen != new_gen)
- update_batch_size(walk, folio, old_gen, new_gen);
+ update_batch_size(walk, folio, old_gen, new_gen, file);
} else if (lru_gen_set_refs(folio, &vma->flags)) {
old_gen = folio_lru_gen(folio);
if (old_gen >= 0 && old_gen != new_gen)
@@ -3549,8 +3561,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;
@@ -3601,7 +3611,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;
@@ -3614,7 +3624,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))
@@ -3637,8 +3647,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));
@@ -3692,7 +3700,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;
@@ -3706,7 +3714,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);
@@ -4270,8 +4278,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);
@@ -4308,8 +4314,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();
@@ -4341,7 +4345,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;
@@ -4353,13 +4357,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);
@@ -4711,7 +4716,7 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca
/* see the comment on LRU_REFS_FLAGS */
if (!folio_test_referenced(folio))
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, 0);
+ folio_set_lru_refs(folio, 0);
success = lru_gen_del_folio(lruvec, folio, true);
VM_WARN_ON_ONCE_FOLIO(!success, folio);
@@ -4804,9 +4809,9 @@ static int get_tier_idx(struct lruvec *lruvec, int type)
* 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, LRU_TIER_MIN, LRU_TIER_MIN, 2, &sp);
+ for (tier = LRU_TIER_MIN + 1; tier <= LRU_TIER_MAX; tier++) {
+ read_ctrl_pos(lruvec, type, tier, tier, 3, &pv);
if (!positive_ctrl_err(&sp, &pv))
break;
}
@@ -4827,8 +4832,10 @@ 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, LRU_TIER_MIN, LRU_TIER_MAX,
+ swappiness, &sp);
+ read_ctrl_pos(lruvec, LRU_GEN_FILE, LRU_TIER_MIN, LRU_TIER_MAX,
+ MAX_SWAPPINESS - swappiness, &pv);
return positive_ctrl_err(&sp, &pv);
}
@@ -4927,8 +4934,10 @@ 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));
+ if (lru_gen_folio_seq(lruvec, folio, false) == min_seq[type]) {
+ folio_set_lru_refs(folio, 0);
+ folio_set_active(folio);
+ }
}
move_folios_to_lru(&list);
|
| 3/2 | 2026/08/18 05:47 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11649 output: 19 thoughts: 452 Thoughts: **Analyzing the MGLRU Patch: A Deep Dive** Okay, so I'm looking at this patch that's tweaking the Multi-Gen LRU implementation within the Linux kernel, and the initial impression is that this is significant. It's modifying how the LRU generation and reference flags are managed, specifically within the `folio->flags` structure. Rather than direct bitmask manipulations, it's replacing them with helper functions, like `lru_gen_from_flags`, and so on. That seems like a good move, potentially improving readability and maintainability. The change from `unsigned long` to `atomic_long_t` for `lru_zone_size` in `struct mem_cgroup_per_node` immediately catches my eye. This suggests there are likely concurrency implications that need careful examination, and that could introduce subtle bugs if the atomic operations aren't handled correctly. Functionally, this impacts core memory management logic – both the MGLRU itself *and* the memcg LRU size tracking, so it's a high-impact area. This definitely warrants fuzzing. I'll need to identify specific functions to target. A quick scan of the modified functions list reveals the following: `mem_cgroup_get_zone_lru_size`, `mem_cgroup_update_lru_size`, `folio_migrate_flags`, `lru_gen_look_around`, `walk_pte_range`, `walk_pmd_range_locked`, `evict_folios`, `get_tier_idx`, `get_type_to_scan`, `folio_inc_gen`, `folio_update_gen`, `lru_gen_inc_refs`, and `lru_gen_clear_refs`. Now, let's focus. Given the potential concurrency issues and their impact on memcg, I'll prioritize `mem_cgroup_update_lru_size` and `mem_cgroup_get_zone_lru_size`. Of course, I'll also concentrate on those specific to MGLRU like `lru_gen_look_around` and `evict_folios`. These are likely the most critical parts of the code. Before I go further I need to check the Kconfig. I'll make sure that `CONFIG_LRU_GEN` is enabled because otherwise, this patch won't even be relevant, so I need to verify that. Gotta make sure the foundation is sound before diving into the details. |
| 4/2 | 2026/08/18 05:47 | tool | grepper |
0mArgs:{
"Expression": "config LRU_GEN"
}Results: {
"Output": "mm/Kconfig=1393=config PTE_MARKER_UFFD_WP\n--\nmm/Kconfig-1403-# multi-gen LRU {\nmm/Kconfig:1404:config LRU_GEN\nmm/Kconfig-1405-\tbool \"Multi-Gen LRU\"\n--\nmm/Kconfig-1412-\nmm/Kconfig:1413:config LRU_GEN_ENABLED\nmm/Kconfig-1414-\tbool \"Enable by default\"\n--\nmm/Kconfig-1418-\nmm/Kconfig:1419:config LRU_GEN_STATS\nmm/Kconfig-1420-\tbool \"Full stats for debugging\"\n--\nmm/Kconfig-1427-\nmm/Kconfig:1428:config LRU_GEN_WALKS_MMU\nmm/Kconfig-1429-\tdef_bool y\n"
} |
| 5/2 | 2026/08/18 05:47 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12893 output: 25 thoughts: 111 Thoughts: **Considering LRU Generation Configuration** Okay, so I'm looking at this `LRU_GEN` configuration option. Makes sense, that's the core. To make things easy, we should enable it by default by adding `LRU_GEN_ENABLED`, or give the user control with a sysfs entry. I think we should include both options for maximum flexibility. So, I'll add `LRU_GEN` and `LRU_GEN_ENABLED` to the config. Now, I need to understand the impact of these changes. I should check the functions that are being modified. Hmm, let's see, what about `mem_cgroup_update_lru_size`? I wonder if that's an exported function, or if it's static? I'll quickly use grep to find all instances of `mem_cgroup_update_lru_size` to get a better overview of its usage. That should give me a good handle on where this modification is happening within the code. |
| 6/2 | 2026/08/18 05:47 | tool | grepper |
0mArgs:{
"Expression": "mem_cgroup_update_lru_size"
}Results: {
"Output": "include/linux/memcontrol.h=886=static inline bool mem_cgroup_online(struct mem_cgroup *memcg)\n--\ninclude/linux/memcontrol.h-892-\ninclude/linux/memcontrol.h:893:void mem_cgroup_update_lru_size(struct lruvec *lruvec, enum lru_list lru,\ninclude/linux/memcontrol.h-894-\t\tint zid, long nr_pages);\n--\ninclude/linux/mm_inline.h=52=static __always_inline void update_lru_size(struct lruvec *lruvec,\n--\ninclude/linux/mm_inline.h-57-#ifdef CONFIG_MEMCG\ninclude/linux/mm_inline.h:58:\tmem_cgroup_update_lru_size(lruvec, lru, zid, nr_pages);\ninclude/linux/mm_inline.h-59-#endif\n--\nmm/folio.c=1123=static void lruvec_reparent_lru(struct lruvec *child_lruvec,\n--\nmm/folio.c-1135-\nmm/folio.c:1136:\t\tmem_cgroup_update_lru_size(parent_lruvec, lru, zid, size);\nmm/folio.c-1137-\t}\n--\nmm/memcontrol.c=1501=struct lruvec *folio_lruvec_lock_irqsave(struct folio *folio,\n--\nmm/memcontrol.c-1518-/**\nmm/memcontrol.c:1519: * mem_cgroup_update_lru_size - account for adding or removing an lru page\nmm/memcontrol.c-1520- * @lruvec: mem_cgroup per zone lru vector\n--\nmm/memcontrol.c-1527- */\nmm/memcontrol.c:1528:void mem_cgroup_update_lru_size(struct lruvec *lruvec, enum lru_list lru,\nmm/memcontrol.c-1529-\t\t\t\tint zid, long nr_pages)\n--\nmm/vmscan.c=1592=unsigned int reclaim_clean_pages_from_list(struct zone *zone,\n--\nmm/vmscan.c-1644- * Update LRU sizes after isolating pages. The LRU size updates must\nmm/vmscan.c:1645: * be complete before mem_cgroup_update_lru_size due to a sanity check.\nmm/vmscan.c-1646- */\n--\nmm/vmscan.c=4625=void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int nid)\n--\nmm/vmscan.c-4642-\nmm/vmscan.c:4643:\t\t\tmem_cgroup_update_lru_size(parent_lruvec, lru, zid, size);\nmm/vmscan.c-4644-\t\t}\n"
} |
| 7/2 | 2026/08/18 05:47 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14015 output: 22 thoughts: 34 Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as someone familiar with memory management: **Checking on LRU Size Updates** Hmm, `mem_cgroup_update_lru_size` is exported and non-static. Interesting. That likely means it's designed to be called from other modules or parts of the kernel. This raises some questions about how broadly it's used and what dependencies it has. I should probably review the call sites to get a clearer picture of its impact on the system. Now, what about `lru_gen_look_around`? I need to quickly recall its definition and purpose. Is that also exported? Is it static? That's going to influence how I see things. I need to know how it relates to `mem_cgroup_update_lru_size` and how it might impact LRU size calculations. Maybe I should quickly grep the codebase to find where it's defined and used to get the lay of the land. It's probably called by some kind of scheduler or page reclaim mechanism, so understanding its role is important for the complete memory picture. |
| 8/2 | 2026/08/18 05:47 | tool | grepper |
0mArgs:{
"Expression": "lru_gen_look_around"
}Results: {
"Output": "Documentation/mm/multigen_lru.rst=187=can incur the highest CPU cost in the reclaim path.\nDocumentation/mm/multigen_lru.rst-188-\nDocumentation/mm/multigen_lru.rst:189:``lru_gen_look_around()`` exploits spatial locality to reduce the\nDocumentation/mm/multigen_lru.rst-190-trips into the rmap. It scans the adjacent PTEs of a young PTE and\n--\nDocumentation/mm/multigen_lru.rst=199=in the set.\nDocumentation/mm/multigen_lru.rst-200-\nDocumentation/mm/multigen_lru.rst:201:In the eviction path, specifically, in ``lru_gen_look_around()``, if a\nDocumentation/mm/multigen_lru.rst-202-PMD has a sufficient number of hot pages, its address is placed in the\n--\ninclude/linux/mmzone.h=695=void lru_gen_init_lruvec(struct lruvec *lruvec);\ninclude/linux/mmzone.h:696:bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr);\ninclude/linux/mmzone.h-697-\n--\ninclude/linux/mmzone.h=714=static inline void lru_gen_init_lruvec(struct lruvec *lruvec)\n--\ninclude/linux/mmzone.h-717-\ninclude/linux/mmzone.h:718:static inline bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw,\ninclude/linux/mmzone.h-719-\t\tunsigned int nr)\n--\nmm/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=2791=static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)\n--\nmm/vmscan.c-2820- * walk_pmd_range(); the eviction also report them when walking the rmap\nmm/vmscan.c:2821: * in lru_gen_look_around().\nmm/vmscan.c-2822- *\n--\nmm/vmscan.c=4078=static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long seq,\n--\nmm/vmscan.c-4097-\t * If the hardware doesn't automatically set the accessed bit, fallback\nmm/vmscan.c:4098:\t * to lru_gen_look_around(), which only clears the accessed bit in a\nmm/vmscan.c-4099-\t * handful of PTEs. Spreading the work out over a period of time usually\n--\nmm/vmscan.c=4217=static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-4263- */\nmm/vmscan.c:4264:bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\nmm/vmscan.c-4265-{\n"
} |
| 9/2 | 2026/08/18 05:47 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15218 output: 19 thoughts: 36 Thoughts: Here's my thinking: **Examining Potential Optimization Opportunities** Okay, let's see. I'm looking at `lru_gen_look_around`, and it's called from `folio_referenced_one`. That's the immediate context. My mind immediately jumps to broader implications. This likely has implications for the LRU cache interaction, so I'm thinking about the migration flags. I need to understand how these pieces fit together. Are there interactions or dependencies there that can be streamlined? Is there shared functionality? Are the flags used during look around, or is there the ability to set them during migration? This line of thought prompts me to review how `folio_migrate_flags` relates to the LRU and referenced page operations. There could be an opportunity for optimization there. |
| 10/2 | 2026/08/18 05:48 | tool | grepper |
0mArgs:{
"Expression": "folio_migrate_flags"
}Results: {
"Output": "fs/aio.c=460=static int aio_migrate_folio(struct address_space *mapping, struct folio *dst,\n--\nfs/aio.c-512-\tfolio_copy(dst, src);\nfs/aio.c:513:\tfolio_migrate_flags(dst, src);\nfs/aio.c-514-\tBUG_ON(ctx-\u003ering_folios[idx] != src);\n--\nfs/hugetlbfs/inode.c=1024=static int hugetlbfs_migrate_folio(struct address_space *mapping,\n--\nfs/hugetlbfs/inode.c-1039-\nfs/hugetlbfs/inode.c:1040:\tfolio_migrate_flags(dst, src);\nfs/hugetlbfs/inode.c-1041-\n--\ninclude/linux/migrate.h=69=void softleaf_entry_wait_on_locked(softleaf_t entry, spinlock_t *ptl)\ninclude/linux/migrate.h-70-\t\t__releases(ptl);\ninclude/linux/migrate.h:71:void folio_migrate_flags(struct folio *newfolio, struct folio *folio);\ninclude/linux/migrate.h-72-int folio_migrate_mapping(struct address_space *mapping,\n--\ninclude/linux/mm_inline.h=352=static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)\n--\ninclude/linux/mm_inline.h-362-\ninclude/linux/mm_inline.h:363:\t/* for folio_migrate_flags() */\ninclude/linux/mm_inline.h-364-\tflags = !reclaiming \u0026\u0026 lru_gen_is_active(lruvec, gen) ? BIT(PG_active) : 0;\n--\nmm/migrate.c=739=int migrate_huge_page_move_mapping(struct address_space *mapping,\n--\nmm/migrate.c-774- */\nmm/migrate.c:775:void folio_migrate_flags(struct folio *newfolio, struct folio *folio)\nmm/migrate.c-776-{\n--\nmm/migrate.c-863-}\nmm/migrate.c:864:EXPORT_SYMBOL(folio_migrate_flags);\nmm/migrate.c-865-\n--\nmm/migrate.c=870=static int __migrate_folio(struct address_space *mapping, struct folio *dst,\n--\nmm/migrate.c-890-\nmm/migrate.c:891:\tfolio_migrate_flags(dst, src);\nmm/migrate.c-892-\treturn 0;\n--\nmm/migrate_device.c=1109=static void __migrate_device_pages(unsigned long *src_pfns,\n--\nmm/migrate_device.c-1251-\t\t\telse\nmm/migrate_device.c:1252:\t\t\t\tfolio_migrate_flags(newfolio, folio);\nmm/migrate_device.c-1253-\t\t}\n"
} |
| 11/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16213 output: 17 thoughts: 717 Thoughts: **Analyzing Potential Focus Symbols in Memory Management** Okay, let's break this down. My initial scan flagged `folio_migrate_flags` as exported, which immediately makes it a candidate for closer inspection. But I quickly checked around, and I see that a slew of others are static: `folio_update_gen`, `folio_inc_gen`, `lru_gen_inc_refs`, `lru_gen_clear_refs`, `walk_pte_range`, `walk_pmd_range_locked`, and `evict_folios` all seem to be internal to their respective modules, so I'll put them on a back burner for now. Now, let's dig into the non-static ones more deeply. I started with `mem_cgroup_update_lru_size`. Tracing it, I see it's called from `update_lru_size`, which is marked `__always_inline`, and in turn gets called by `lruvec_add_folio`, `lruvec_del_folio`, etc. That confirms it's a critical function related to memory cgroup LRU management, and it's definitely a good focus. Next, I checked `lru_gen_look_around`. It's non-static and gets called by `folio_referenced_one`, which makes it another interesting symbol. Then, back to `folio_migrate_flags`. It's exported and called from several important migration functions: `aio_migrate_folio`, `hugetlbfs_migrate_folio`, `__migrate_folio`, and `__migrate_device_pages`. This is another strong contender for focus. I also quickly checked `mem_cgroup_get_zone_lru_size`, `folio_lru_refs` and `folio_set_lru_refs` but they are `static inline`, so they don't seem to have as wide a scope. So, based on the initial assessment, the key focus symbols appear to be: * `mem_cgroup_update_lru_size` * `lru_gen_look_around` * `folio_migrate_flags` To be absolutely sure, I went back to re-check all the code. I verified that those three: `mem_cgroup_update_lru_size`, `folio_migrate_flags`, and `lru_gen_look_around` were actually the functions that had been modified. Just to be thorough, I went back and re-checked several static functions to be sure. I checked the static functions: `evict_folios`, `get_tier_idx`, `get_type_to_scan`, `read_ctrl_pos`, `folio_update_gen`, `folio_inc_gen`, `update_batch_size`, `walk_update_folio`, `walk_pte_range`, `walk_pmd_range_locked`, and `isolate_folio`, just to make sure I wasn't missing anything significant. Alright, the final list of non-static functions modified is: * `mem_cgroup_update_lru_size` * `folio_migrate_flags` * `lru_gen_look_around` |
| 12/2 | 2026/08/18 05:48 | tool | grepper |
0mArgs:{
"Expression": "config MEMCG"
}Results: {
"Output": "init/Kconfig=1067=config CGROUP_FAVOR_DYNMODS\n--\ninit/Kconfig-1076-\ninit/Kconfig:1077:config MEMCG\ninit/Kconfig-1078-\tbool \"Memory controller\"\n--\ninit/Kconfig-1085-\ninit/Kconfig:1086:config MEMCG_NMI_UNSAFE\ninit/Kconfig-1087-\tbool\n--\ninit/Kconfig-1092-\ninit/Kconfig:1093:config MEMCG_NMI_SAFETY_REQUIRES_ATOMIC\ninit/Kconfig-1094-\tbool\n--\ninit/Kconfig-1099-\ninit/Kconfig:1100:config MEMCG_V1\ninit/Kconfig-1101-\tbool \"Legacy cgroup v1 memory controller\"\n"
} |
| 13/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17881 output: 19 thoughts: 39 Thoughts: Okay, here's my take on those thoughts, framed as an expert's self-reflection: **Deep Dive on LRU_GEN and mem_cgroup_update_lru_size** Alright, so *mem_cgroup_update_lru_size* absolutely needs `MEMCG`. No question there. It's fundamental. Now, the real question is, is there anything else that *LRU_GEN* depends on? I need to quickly scan and verify that I haven't missed a dependency or interaction. I need to make sure I understand the full configuration context. I know this seems like a no-brainer to check, but these things often have hidden implications, and I want to be 100% sure I haven't overlooked any other dependent configurations. Better safe than sorry. Let's make sure our bases are covered. |
| 14/2 | 2026/08/18 05:48 | tool | grepper |
0mArgs:{
"Expression": "config LRU_GEN"
}Results: {
"Output": "mm/Kconfig=1393=config PTE_MARKER_UFFD_WP\n--\nmm/Kconfig-1403-# multi-gen LRU {\nmm/Kconfig:1404:config LRU_GEN\nmm/Kconfig-1405-\tbool \"Multi-Gen LRU\"\n--\nmm/Kconfig-1412-\nmm/Kconfig:1413:config LRU_GEN_ENABLED\nmm/Kconfig-1414-\tbool \"Enable by default\"\n--\nmm/Kconfig-1418-\nmm/Kconfig:1419:config LRU_GEN_STATS\nmm/Kconfig-1420-\tbool \"Full stats for debugging\"\n--\nmm/Kconfig-1427-\nmm/Kconfig:1428:config LRU_GEN_WALKS_MMU\nmm/Kconfig-1429-\tdef_bool y\n"
} |
| 15/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18360 output: 33 thoughts: 13 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing the Dependencies of LRU_GEN** Okay, so I need to understand the architecture around `LRU_GEN`. First, let's just scope out what's directly connected. What modules or functions call it? What does it call? I'm thinking I'll need a quick call graph visualization. Graphviz maybe? Or I could quickly dump the dependencies using my preferred tool chain. I’m leaning towards a programmatic approach for this since a visual representation, while helpful, probably won't give me the low level insight I need for a task. Then, I'll need to dig into the data structures `LRU_GEN` interacts with. Are these shared, protected by locks, or single-threaded? I need to understand the concurrency model immediately if it's not single threaded. This probably entails tracing how the keys and values are stored and retrieved. I'm also curious about the eviction policy implementation. Is it a simple clock-sweep, a more complex adaptive scheme, or something custom? The performance characteristics of `LRU_GEN` will be highly dependent on this. I'll need to look at the metrics collection. Finally, I want to consider how the cache is configured. Are there configuration parameters for size, eviction threshold, or other behavior? Any unit tests on the eviction policy implementation would be helpful for a quicker understanding, and I would want to look at those next. I am also curious about the test coverage surrounding this component; I’d want to make sure the core functions are fully exercised, especially in edge-case scenarios like very high or very low workloads. |
| 16/2 | 2026/08/18 05:48 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 1400,
"LineCount": 20
}Results: {
"Contents": "1400:\t\t purposes. It is required to enable userfaultfd write protection on\n1401:\t\t file-backed memory types like shmem and hugetlbfs.\n1402:\t\n1403:\t# multi-gen LRU {\n1404:\tconfig LRU_GEN\n1405:\t\tbool \"Multi-Gen LRU\"\n1406:\t\tdepends on MMU\n1407:\t\t# make sure folio-\u003eflags has enough spare bits\n1408:\t\tdepends on 64BIT || !SPARSEMEM || SPARSEMEM_VMEMMAP\n1409:\t\thelp\n1410:\t\t A high performance LRU implementation to overcommit memory. See\n1411:\t\t Documentation/admin-guide/mm/multigen_lru.rst for details.\n1412:\t\n1413:\tconfig LRU_GEN_ENABLED\n1414:\t\tbool \"Enable by default\"\n1415:\t\tdepends on LRU_GEN\n1416:\t\thelp\n1417:\t\t This option enables the multi-gen LRU by default.\n1418:\t\n1419:\tconfig LRU_GEN_STATS\n"
} |
| 17/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19070 output: 22 thoughts: 197 Thoughts: **My Assessment of This Memory Management Configuration** Okay, so I'm looking at this configuration related to Least Recently Used (LRU) and it seems pretty straightforward at first glance. It's clear that `LRU_GEN` relies on a couple of key things: the `MMU` (obviously, essential for memory management) and either a 64-bit architecture or a non-sparse memory setup or the `SPARSEMEM_VMEMMAP` configuration. Makes sense. Now, my immediate reaction is that we should be enabling both `LRU_GEN` and `LRU_GEN_ENABLED`. Beyond that, the dependencies have made it clear that `MEMCG` is definitely required to allow `mem_cgroup_update_lru_size` to operate correctly. That function tells me that there are some significant updates to the memory control group functionality here, which definitely piques my interest. I need to see if there are any other configuration dependencies or nuances here. The files that have been modified—`mm/vmscan.c`, `mm/migrate.c`, `mm/memcontrol.c`, and `mm/folio.c`—these are *core* memory management files, so this is a crucial change. I need to dig in. Now, let's take a look at the key symbols touched here to get a sense of the scope: `mem_cgroup_update_lru_size`, that's the big one, given the earlier `MEMCG` tie-in. Then, `folio_migrate_flags`, which suggests migration capabilities are being influenced, and `lru_gen_look_around`, which makes me suspect some sort of LRU-based look-ahead optimization is involved. Finally, I should check the functions outside of static, just to make sure there are no other significant public interface changes to consider. I'll focus on the changes in `mm/folio.c` as they seem most targeted to these low-level page representations. |
| 18/2 | 2026/08/18 05:48 | tool | grepper |
0mArgs:{
"Expression": "lru_gen_inc_refs"
}Results: {
"Output": "include/linux/mmzone.h=424=enum lruvec_flags {\n--\ninclude/linux/mmzone.h-506- * For folios accessed multiple times through file descriptors,\ninclude/linux/mmzone.h:507: * lru_gen_inc_refs() sets additional bits of LRU_REFS_WIDTH in folio-\u003eflags\ninclude/linux/mmzone.h-508- * after PG_referenced, then PG_workingset after LRU_REFS_WIDTH. After all its\n--\ninclude/linux/mmzone.h-511- * folio_inc_gen() does that, it clears LRU_REFS_FLAGS so that\ninclude/linux/mmzone.h:512: * lru_gen_inc_refs() can start over. Note that for this case, LRU_REFS_MASK is\ninclude/linux/mmzone.h-513- * only valid when PG_referenced is set.\n--\nmm/folio.c=322=static void __lru_cache_activate_folio(struct folio *folio)\n--\nmm/folio.c-353-\nmm/folio.c:354:static void lru_gen_inc_refs(struct folio *folio)\nmm/folio.c-355-{\n--\nmm/folio.c=380=static bool lru_gen_clear_refs(struct folio *folio)\n--\nmm/folio.c-400-\nmm/folio.c:401:static void lru_gen_inc_refs(struct folio *folio)\nmm/folio.c-402-{\n--\nmm/folio.c=425=void folio_mark_accessed(struct folio *folio)\n--\nmm/folio.c-429-\tif (lru_gen_enabled()) {\nmm/folio.c:430:\t\tlru_gen_inc_refs(folio);\nmm/folio.c-431-\t\treturn;\n"
} |
| 19/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20201 output: 19 thoughts: 28 Thoughts: Here's my take on those thoughts, from my perspective: **Thinking Through `lru_gen_inc_refs` and Scan Changes** Okay, so `lru_gen_inc_refs` is static. That means it's scoped to the file it's in, which I'd expect given its role. Now, let me see... where are the most relevant changes around this likely? Definitely need to go over `mm/vmscan.c`. It's where the core VM scanning logic lives, so any modifications to how reference counts are managed, or how generations are handled, would likely show up there. Time to dig in and see what's changed and understand the implications. |
| 20/2 | 2026/08/18 05:48 | tool | grepper |
0mArgs:{
"Expression": "folio_update_gen"
}Results: {
"Output": "include/linux/mm_inline.h=288=static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec,\n--\ninclude/linux/mm_inline.h-298-\t * | Accessed through page tables and | Accessed through file descriptors |\ninclude/linux/mm_inline.h:299:\t * | promoted by folio_update_gen() | and protected by folio_inc_gen() |\ninclude/linux/mm_inline.h-300-\t * +-----------------------------------+-----------------------------------+\n--\ninclude/linux/mmzone.h=424=enum lruvec_flags {\n--\ninclude/linux/mmzone.h-514- *\ninclude/linux/mmzone.h:515: * For folios accessed multiple times through page tables, folio_update_gen()\ninclude/linux/mmzone.h-516- * from a page table walk or lru_gen_set_refs() from a rmap walk sets\n--\ninclude/linux/mmzone.h-518- * Thereafter, those two paths set PG_workingset and promote folios to the\ninclude/linux/mmzone.h:519: * youngest generation. Like folio_inc_gen(), folio_update_gen() also clears\ninclude/linux/mmzone.h-520- * PG_referenced. Note that for this case, LRU_REFS_MASK is not used.\n--\nmm/vmscan.c=827=enum folio_references {\n--\nmm/vmscan.c-837- * with PG_active set. In contrast, the aging (page table walk) path uses\nmm/vmscan.c:838: * folio_update_gen().\nmm/vmscan.c-839- */\n--\nmm/vmscan.c=3256=static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)\n--\nmm/vmscan.c-3271-/* promote pages accessed through page tables */\nmm/vmscan.c:3272:static int folio_update_gen(struct folio *folio, int new_gen, int *is_file,\nmm/vmscan.c-3273-\t\t\t const vma_flags_t *vma_flags)\n--\nmm/vmscan.c=3307=static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)\n--\nmm/vmscan.c-3320-\nmm/vmscan.c:3321:\t\t/* folio_update_gen() has promoted this page? */\nmm/vmscan.c-3322-\t\tif (old_gen != min_gen)\n--\nmm/vmscan.c=3524=static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma,\n--\nmm/vmscan.c-3539-\tif (walk) {\nmm/vmscan.c:3540:\t\told_gen = folio_update_gen(folio, new_gen, \u0026file, \u0026vma-\u003eflags);\nmm/vmscan.c-3541-\t\tif (old_gen \u003e= 0 \u0026\u0026 old_gen != new_gen)\n"
} |
| 21/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21200 output: 19 thoughts: 24 Thoughts: Here's my summary, as if I were thinking these thoughts: **Analyzing Potential Folio Update Impacts** Okay, so `folio_update_gen` is static, which is interesting. I need to understand the implications of this, particularly in the context of recent modifications. Let's dig into that. Now, I'll shift my focus and start investigating the changes in `mm/migrate.c`. This file often interacts heavily with folio management, and it's a likely point of impact for anything related to updating folios. I'll carefully scrutinize the diffs in that area. I suspect there may be important clues related to what I'm currently looking at. |
| 22/2 | 2026/08/18 05:48 | tool | grepper |
0mArgs:{
"Expression": "folio_migrate_refs"
}Results: {
"Output": "include/linux/mm_inline.h=352=static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)\n--\ninclude/linux/mm_inline.h-374-/**\ninclude/linux/mm_inline.h:375: * folio_migrate_refs - copy the reference state to a new folio\ninclude/linux/mm_inline.h-376- * @new: the destination folio\n--\ninclude/linux/mm_inline.h-382- */\ninclude/linux/mm_inline.h:383:static inline void folio_migrate_refs(struct folio *new, const struct folio *old)\ninclude/linux/mm_inline.h-384-{\n--\ninclude/linux/mm_inline.h=410=static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio, bool reclaiming)\n--\ninclude/linux/mm_inline.h-414-\ninclude/linux/mm_inline.h:415:static inline void folio_migrate_refs(struct folio *new, const struct folio *old)\ninclude/linux/mm_inline.h-416-{\n--\nmm/migrate.c=775=void folio_migrate_flags(struct folio *newfolio, struct folio *folio)\n--\nmm/migrate.c-808-\t/* Copy the reference state, including PG_referenced */\nmm/migrate.c:809:\tfolio_migrate_refs(newfolio, folio);\nmm/migrate.c-810-\n"
} |
| 23/2 | 2026/08/18 05:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21784 output: 210 thoughts: 734 Thoughts: **My Analysis of the MGLRU Patch** Alright, let's break this down. This patch is touching core memory management with the Multi-Gen LRU implementation, specifically how we manage LRU generation and reference flags within the `folio->flags` structure. It looks like it's shifting from direct bitmask manipulation to a set of helper functions, and also modifying `lru_zone_size` in `mem_cgroup_per_node` to use an `atomic_long_t`. This implies potential performance and correctness implications, particularly around concurrency. So, the key functions I'm focusing on are: `mem_cgroup_update_lru_size`, `folio_migrate_flags`, and `lru_gen_look_around`. The `folio_migrate_refs` function is `static inline`, so I don't need to consider it further at this point. I started by verifying the configuration dependencies. `lru_gen_look_around` is definitely guarded by `CONFIG_LRU_GEN`, which is expected. `mem_cgroup_update_lru_size` requires `CONFIG_MEMCG`. `folio_migrate_flags` is in `mm/migrate.c`, so I should keep an eye on how it works. I'm also confirming we need `LRU_GEN`, `LRU_GEN_ENABLED`, and `MEMCG` enabled for this patch to function correctly. I checked `LRU_GEN`'s dependencies, which include `MMU` and the 64-bit or sparse memory configurations. Those aren't things I'm too concerned about adding, as those are usually enabled by default anyway. I also checked to see if any other non-static functions were modified in this patch. `mem_cgroup_get_zone_lru_size` is static inline. `folio_flags_is_file_lru`, `lru_gen_from_flags`, `lru_gen_set_flags`, `lru_refs_from_flags`, `lru_refs_set_flags`, and `folio_set_lru_refs` are also static inline. So, the focus symbols are the correct ones. In essence, this patch introduces changes that alter how folios are tracked and aged in the LRU lists. The helper functions replace bitmask operations, which is good, but the switch to `atomic_long_t` in `mem_cgroup_per_node` introduces atomic operations. This, combined with the fact that these functions are invoked in core memory management paths – including page migration, LRU size tracking, and page table scanning – makes them prime candidates for fuzzing. They are all reachable and functionally alter memory management's core behavior. I've confirmed the focus symbols are valid. `mem_cgroup_update_lru_size` isn't `EXPORT_SYMBOL`'d but it's used from `mm/folio.c` and `mm/vmscan.c`. `folio_migrate_flags` is exported, and `lru_gen_look_around` is used in `mm/rmap.c`. Looks good to me. |
| 24/2 | 2026/08/18 05:49 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG"
],
"FocusSymbols": [
"mem_cgroup_update_lru_size",
"folio_migrate_flags",
"lru_gen_look_around"
],
"Reasoning": "The patch modifies the Multi-Gen LRU (MGLRU) implementation, changing how LRU generation and reference flags are stored in `folio-\u003eflags`. It replaces direct bitmask operations with new helper functions and changes `lru_zone_size` in `struct mem_cgroup_per_node` to use `atomic_long_t` instead of `unsigned long`. These changes affect core memory management paths, including page migration, LRU size tracking, and page table scanning. The code is reachable and functionally alters how folios are tracked and aged in the LRU lists, making it worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"LRU_GEN",
"LRU_GEN_ENABLED",
"MEMCG"
],
"FocusSymbols": [
"mem_cgroup_update_lru_size",
"folio_migrate_flags",
"lru_gen_look_around"
],
"Reasoning": "The patch modifies the Multi-Gen LRU (MGLRU) implementation, changing how LRU generation and reference flags are stored in `folio-\u003eflags`. It replaces direct bitmask operations with new helper functions and changes `lru_zone_size` in `struct mem_cgroup_per_node` to use `atomic_long_t` instead of `unsigned long`. These changes affect core memory management paths, including page migration, LRU size tracking, and page table scanning. The code is reachable and functionally alters how folios are tracked and aged in the LRU lists, making it worth fuzzing.",
"WorthFuzzing": true
} |
| 25/1 | 2026/08/18 05:49 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 26/2 | 2026/08/18 05:49 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch refactors internal memory management logic, specifically folio LRU generation flags and reference tracking, and changes a counter (`lru_zone_size`) to an atomic variable to fix concurrency issues. It does not introduce any new structures that are copied to user space, nor does it allocate complex data structures that might be left partially uninitialized. The local variable `file` introduced in `walk_update_folio` is only used when `folio_update_gen` returns a non-negative value, which guarantees it has been initialized. Therefore, there is no risk of uninitialized memory usage or info-leaks. Standard KASAN and other bug detectors are sufficient to catch any potential out-of-bounds accesses or use-after-free bugs related to these changes.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 8a925d2546c538d4b3b0dc1755caac894989d9a0
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 18 05:47:39 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index e78bc98ab229b..b13e3f0563195 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 (WARN_ON_ONCE(val < 0))
+ return 0;
+
+ return val;
}
void __mem_cgroup_handle_over_high(gfp_t gfp_mask);
diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
index 621c8653d8f7e..4bb390d9516ef 100644
--- a/include/linux/mm_inline.h
+++ b/include/linux/mm_inline.h
@@ -10,6 +10,11 @@
#include <linux/userfaultfd_k.h>
#include <linux/leafops.h>
+static inline int folio_flags_is_file_lru(const unsigned long *flags)
+{
+ return !test_bit(PG_swapbacked, flags);
+}
+
/**
* folio_is_file_lru - Should the folio be on a file LRU or anon LRU?
* @folio: The folio to test.
@@ -27,7 +32,7 @@
*/
static inline int folio_is_file_lru(const struct folio *folio)
{
- return !folio_test_swapbacked(folio);
+ return folio_flags_is_file_lru(const_folio_flags(folio, 0));
}
static __always_inline void __update_lru_size(struct lruvec *lruvec,
@@ -142,10 +147,43 @@ static inline int lru_tier_from_refs(int refs, bool workingset)
return workingset ? MAX_NR_TIERS - 1 : order_base_2(refs);
}
-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)
+{
+ int gen = ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF);
+
+ BUILD_BUG_ON(LRU_GEN_MASK & LRU_REFS_MASK);
+ gen -= 1;
+ VM_WARN_ON_ONCE(gen != -1 && gen > LRU_GEN_MAX);
+ return gen;
+}
+
+/**
+ * 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);
+ VM_WARN_ON_ONCE(gen > LRU_GEN_MAX || gen < 0);
+ BUILD_BUG_ON((LRU_GEN_MAX + 1) != MAX_NR_GENS);
+
+ *flags &= ~LRU_GEN_MASK;
+ *flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+}
+/**
+ * lru_refs_from_flags - Return LRU referenced / access count from folio flags.
+ * @flags: folio flags
+ */
+static inline int lru_refs_from_flags(unsigned long flags)
+{
if (!(flags & BIT(PG_referenced)))
return 0;
/*
@@ -155,18 +193,47 @@ static inline int folio_lru_refs(const struct folio *folio)
return ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) + 1;
}
-static inline int folio_lru_gen(const struct folio *folio)
+/**
+ * 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 != (LRU_REFS_MASK >> LRU_REFS_PGOFF) + 1);
+
+ *flags &= ~LRU_REFS_FLAGS;
+ if (!refs)
+ return;
+ *flags |= (BIT(PG_referenced) | ((refs - 1UL) << 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 flags = READ_ONCE(folio->flags.f);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
- return ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+ do {
+ new_flags = old_flags;
+ lru_refs_set_flags(&new_flags, refs);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
+}
+
+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;
- 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);
@@ -270,7 +337,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 +362,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,11 +371,19 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
return true;
}
+/**
+ * folio_migrate_refs - copy the reference state to a new folio
+ * @new: the destination folio
+ * @old: the source folio
+ *
+ * Transfer the reference state to @new during migration: the MGLRU
+ * refs count, including PG_referenced, or just PG_referenced for the
+ * active/inactive LRU.
+ */
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);
+ BUILD_BUG_ON(LRU_REFS_MASK & BIT(PG_referenced));
+ folio_set_lru_refs(new, folio_lru_refs(old));
}
#else /* !CONFIG_LRU_GEN */
@@ -339,7 +414,8 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
static inline void folio_migrate_refs(struct folio *new, const struct folio *old)
{
-
+ if (folio_test_referenced(old))
+ folio_set_referenced(new);
}
#endif /* CONFIG_LRU_GEN */
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 94f9c3ff54160..d0b5c6217d257 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -492,11 +492,15 @@ enum lruvec_flags {
* folio->flags, masked by LRU_REFS_MASK.
*/
#define MAX_NR_TIERS 4U
+#define LRU_TIER_MIN 0U
+#define LRU_TIER_MAX (MAX_NR_TIERS - 1)
#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)
+#define LRU_REFS_MAX BIT(LRU_REFS_WIDTH)
/*
* For folios accessed multiple times through file descriptors,
diff --git a/mm/folio.c b/mm/folio.c
index 59c477120b9a8..0adfe4f5ef72f 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -353,26 +353,28 @@ static void __lru_cache_activate_folio(struct folio *folio)
static void lru_gen_inc_refs(struct folio *folio)
{
- unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
+ int refs;
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));
+ if (!folio_lru_refs(folio)) {
+ folio_set_lru_refs(folio, 1);
return;
}
do {
- if ((old_flags & LRU_REFS_MASK) == LRU_REFS_MASK) {
+ new_flags = old_flags;
+ refs = lru_refs_from_flags(old_flags);
+ if (refs == LRU_REFS_MAX) {
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));
+ lru_refs_set_flags(&new_flags, refs + 1);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
}
static bool lru_gen_clear_refs(struct folio *folio)
@@ -384,7 +386,8 @@ 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);
+ folio_clear_workingset(folio);
rcu_read_lock();
seq = READ_ONCE(folio_lruvec(folio)->lrugen.min_seq[type]);
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 1d3339520809f..9d0ee3d3bda7c 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1529,28 +1529,12 @@ 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 15b45832bcfa7..82307332711f4 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -776,8 +776,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)) {
@@ -807,7 +805,9 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)
if (folio_test_idle(folio))
folio_set_idle(newfolio);
+ /* Copy the reference state, including PG_referenced */
folio_migrate_refs(newfolio, folio);
+
/*
* Copy NUMA information to the new page, to prevent over-eager
* future migrations of this same page.
diff --git a/mm/vmscan.c b/mm/vmscan.c
index c1404a59523d6..7e3ae0c6cba3b 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -843,19 +843,22 @@ static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_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));
+ folio_set_lru_refs(folio, 0);
+ folio_set_workingset(folio);
return true;
}
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced));
+ folio_set_lru_refs(folio, 1);
return false;
}
/* Promote on second access */
- if (folio_lru_refs(folio) > 1)
- set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset));
- else
+ if (folio_lru_refs(folio) > 1) {
+ folio_set_lru_refs(folio, 0);
+ folio_set_workingset(folio);
+ } else {
folio_mark_accessed(folio);
+ }
return true;
}
#else
@@ -3195,8 +3198,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;
@@ -3205,7 +3208,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] +
@@ -3266,11 +3269,11 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)
******************************************************************************/
/* promote pages accessed through page tables */
-static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags)
+static int folio_update_gen(struct folio *folio, int new_gen, int *is_file,
+ 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);
+ unsigned long new_flags, old_flags = READ_ONCE(*folio_flags(folio, 0));
+ int old_gen;
/*
* See the comment on LRU_REFS_FLAGS, and activate file-backed
@@ -3279,20 +3282,25 @@ static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma
*/
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));
+ folio_set_lru_refs(folio, 1);
return -1;
}
do {
+ old_gen = lru_gen_from_flags(old_flags);
+ new_flags = old_flags;
+
/* lru_gen_del_folio() has isolated this page? */
- if (!(old_flags & LRU_GEN_MASK))
- return -1;
+ if (old_gen < 0)
+ break;
- 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));
+ lru_gen_set_flags(&new_flags, new_gen);
+ lru_refs_set_flags(&new_flags, 0);
+ new_flags |= BIT(PG_workingset);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
- return ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+ *is_file = folio_flags_is_file_lru(&old_flags);
+ return old_gen;
}
/* protect pages accessed multiple times through file descriptors */
@@ -3300,22 +3308,25 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
{
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 new_gen, old_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;
+ old_gen = lru_gen_from_flags(old_flags);
+ /* This helper should never be called for off-list folios */
+ VM_WARN_ON_ONCE(old_gen < 0);
+ if (old_gen < 0)
+ return min_gen;
- new_gen = (old_gen + 1) % MAX_NR_GENS;
+ /* folio_update_gen() has promoted this page? */
+ if (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_flags = old_flags;
+ new_gen = (min_gen + 1) % MAX_NR_GENS;
+ lru_gen_set_flags(&new_flags, new_gen);
+ lru_refs_set_flags(&new_flags, 0);
+ } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
lru_gen_update_size(lruvec, folio, old_gen, new_gen);
@@ -3323,9 +3334,8 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
}
static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio,
- int old_gen, int new_gen)
+ int old_gen, int new_gen, int type)
{
- int type = folio_is_file_lru(folio);
int zone = folio_zonenum(folio);
int delta = folio_nr_pages(folio);
@@ -3512,22 +3522,24 @@ 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, file;
if (!folio)
return;
+ new_gen = lru_gen_from_seq(READ_ONCE(lruvec->lrugen.max_seq));
+
if (dirty && !folio_test_dirty(folio) &&
!(folio_test_anon(folio) && folio_test_swapbacked(folio) &&
!folio_test_swapcache(folio)))
folio_mark_dirty(folio);
if (walk) {
- old_gen = folio_update_gen(folio, new_gen, &vma->flags);
+ old_gen = folio_update_gen(folio, new_gen, &file, &vma->flags);
if (old_gen >= 0 && old_gen != new_gen)
- update_batch_size(walk, folio, old_gen, new_gen);
+ update_batch_size(walk, folio, old_gen, new_gen, file);
} else if (lru_gen_set_refs(folio, &vma->flags)) {
old_gen = folio_lru_gen(folio);
if (old_gen >= 0 && old_gen != new_gen)
@@ -3549,8 +3561,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;
@@ -3601,7 +3611,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;
@@ -3614,7 +3624,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))
@@ -3637,8 +3647,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));
@@ -3692,7 +3700,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;
@@ -3706,7 +3714,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);
@@ -4270,8 +4278,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);
@@ -4308,8 +4314,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();
@@ -4341,7 +4345,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;
@@ -4353,13 +4357,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);
@@ -4711,7 +4716,7 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca
/* see the comment on LRU_REFS_FLAGS */
if (!folio_test_referenced(folio))
- set_mask_bits(&folio->flags.f, LRU_REFS_MASK, 0);
+ folio_set_lru_refs(folio, 0);
success = lru_gen_del_folio(lruvec, folio, true);
VM_WARN_ON_ONCE_FOLIO(!success, folio);
@@ -4804,9 +4809,9 @@ static int get_tier_idx(struct lruvec *lruvec, int type)
* 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, LRU_TIER_MIN, LRU_TIER_MIN, 2, &sp);
+ for (tier = LRU_TIER_MIN + 1; tier <= LRU_TIER_MAX; tier++) {
+ read_ctrl_pos(lruvec, type, tier, tier, 3, &pv);
if (!positive_ctrl_err(&sp, &pv))
break;
}
@@ -4827,8 +4832,10 @@ 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, LRU_TIER_MIN, LRU_TIER_MAX,
+ swappiness, &sp);
+ read_ctrl_pos(lruvec, LRU_GEN_FILE, LRU_TIER_MIN, LRU_TIER_MAX,
+ MAX_SWAPPINESS - swappiness, &pv);
return positive_ctrl_err(&sp, &pv);
}
@@ -4927,8 +4934,10 @@ 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));
+ if (lru_gen_folio_seq(lruvec, folio, false) == min_seq[type]) {
+ folio_set_lru_refs(folio, 0);
+ folio_set_active(folio);
+ }
}
move_folios_to_lru(&list);
|
| 27/3 | 2026/08/18 05:49 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11646 output: 184 thoughts: 1629 Thoughts: **KMSAN Not Needed: A Deep Dive into Memory Management Refactoring** Alright, let's break down this patch. My initial thought is to analyze these changes from a memory safety perspective, and specifically if KMSAN is *needed* here. First, I see the modifications are focused on the memory management subsystem, particularly the LRU generation and reference tracking. This involves changes across several files, focusing on `struct folio` and LRU-related flags. The key changes jump out: `lru_zone_size` in `mem_cgroup_per_node` going from `unsigned long` to `atomic_long_t` – sounds like a concurrency improvement, likely thread-safe now. Second, moving LRU generation/reference tracking from direct bit manipulation of `folio->flags` to helper functions – a definite refactoring to improve readability and potentially isolate the flag access. Then, updates to functions such as `folio_migrate_refs`, `read_ctrl_pos`, `folio_update_gen`, and `walk_update_folio` to align with the new helpers. Now, my primary concern with a patch like this is the potential for uninitialized memory use. The question I immediately ask is: do these changes introduce a vulnerability where some previously unitialized memory is now used in control flow, or leaked to user space? I need to analyze this from the standpoint of structures being exposed to user space, new allocations, and where uninitialized data is now read. Initially, it doesn't *seem* like there are any obvious red flags. No new structures are being sent to user space, nothing seems to be allocated without initialization. I can see no new complex data structures and no changes that would expose uninitialized memory. The patch primarily manipulates flags, LRU statistics, and updates a counter. That, on its own, seems to reduce the probability of introducing any new uninitialized memory vulnerabilities. Let's double-check. I'll meticulously go through the code, focusing on areas where values are set and used. Specifically, I'll pay attention to `folio_update_gen` and `walk_update_folio`. In `folio_update_gen`, it *looks* like `*is_file` might be uninitialized if the early return is taken. But wait, `*is_file` is set *before* the function returns, even after a fast path. So, `is_file` is used in `walk_update_folio` as an output. However, upon closer inspection, I see that the return value of `folio_update_gen` is checked. If it *is* the fast path, then `update_batch_size` isn't even called, meaning `file` is never used. So even *that* seemingly potential issue doesn't appear to be a problem. This is good. No uninitialized memory is read. What about `lru_zone_size`? I recall that it's part of a `mem_cgroup_per_node`, which is allocated using `kzalloc_node`. That’s zero-initialized. Excellent. Now, let's zoom out again. Is there *any* other uninitialized memory risk I'm missing? I think I've covered the critical parts. I see no new structures sent to user space, nothing allocated that *isn't* initialized, and all local variables are initialized before use. The patch appears to be pure internal memory management logic, refactoring and improving some flag handling and fixing a concurrency issue. So, the conclusion is: KMSAN isn't needed here. Standard memory safety tools like KASAN and other bug detectors should be sufficient. KMSAN is for catching *uninitialized memory reads*, and there are none in this patch. Just to be absolutely sure, let's apply the KMSAN test again: 1. Kernel structures sent to user space? None. 2. Conditional logic depending on uninitialized variables? Nope. `file` in `walk_update_folio` is safe. 3. Allocation of complex data structures? None. 4. Bounds checks changes? None. So, my final verdict is: **NeedsKMSAN = false.** The patch refactors flag manipulation and an atomic variable update. Standard tools will catch any issues. |
| 28/3 | 2026/08/18 05:49 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch refactors internal memory management logic, specifically folio LRU generation flags and reference tracking, and changes a counter (`lru_zone_size`) to an atomic variable to fix concurrency issues. It does not introduce any new structures that are copied to user space, nor does it allocate complex data structures that might be left partially uninitialized. The local variable `file` introduced in `walk_update_folio` is only used when `folio_update_gen` returns a non-negative value, which guarantees it has been initialized. Therefore, there is no risk of uninitialized memory usage or info-leaks. Standard KASAN and other bug detectors are sufficient to catch any potential out-of-bounds accesses or use-after-free bugs related to these changes.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch refactors internal memory management logic, specifically folio LRU generation flags and reference tracking, and changes a counter (`lru_zone_size`) to an atomic variable to fix concurrency issues. It does not introduce any new structures that are copied to user space, nor does it allocate complex data structures that might be left partially uninitialized. The local variable `file` introduced in `walk_update_folio` is only used when `folio_update_gen` returns a non-negative value, which guarantees it has been initialized. Therefore, there is no risk of uninitialized memory usage or info-leaks. Standard KASAN and other bug detectors are sufficient to catch any potential out-of-bounds accesses or use-after-free bugs related to these changes.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|