| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/28 08:49 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"MEMCG",
"MEMCG_V1",
"LRU_GEN"
],
"FocusSymbols": [
"try_to_free_pages",
"try_to_free_mem_cgroup_pages",
"wakeup_kswapd",
"workingset_refault"
],
"Reasoning": "The patch modifies the core memory management subsystem, specifically the LRU reclaim logic and cost calculation for both legacy LRU and MGLRU. It introduces monotonic counters for LRU stats and changes how scan balance is determined. These are functional changes to reachable core kernel code that should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/28 08:49 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit c23bd308d4a776d56012b8c2c8fff1e3fde2a2a2\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Jul 28 08:49:23 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h\nindex 8170bb8066a22f..7ae67ac2351076 100644\n--- a/include/linux/memcontrol.h\n+++ b/include/linux/memcontrol.h\n@@ -947,6 +947,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item);\n bool memcg_stat_item_valid(int idx);\n bool memcg_vm_event_item_valid(enum vm_event_item idx);\n unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx);\n+unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,\n+\t\t\t\t\t enum node_stat_item idx);\n unsigned long lruvec_page_state_local(struct lruvec *lruvec,\n \t\t\t\t enum node_stat_item idx);\n \n@@ -1399,6 +1401,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec,\n \treturn node_page_state(lruvec_pgdat(lruvec), idx);\n }\n \n+static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,\n+\t\t\t\t\t\t\tenum node_stat_item idx)\n+{\n+\treturn node_page_state_monotonic(lruvec_pgdat(lruvec), idx);\n+}\n+\n static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec,\n \t\t\t\t\t\t enum node_stat_item idx)\n {\ndiff --git a/include/linux/mmzone.h b/include/linux/mmzone.h\nindex ca27121871475c..85303c5867c84b 100644\n--- a/include/linux/mmzone.h\n+++ b/include/linux/mmzone.h\n@@ -323,6 +323,8 @@ enum node_stat_item {\n \tPGSCAN_PROACTIVE,\n \tPGSCAN_ANON,\n \tPGSCAN_FILE,\n+\tPGROTATE_ANON,\n+\tPGROTATE_FILE,\n \tPGREFILL,\n #ifdef CONFIG_HUGETLB_PAGE\n \tNR_HUGETLB,\n@@ -755,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent,\n \n #endif /* CONFIG_LRU_GEN */\n \n+struct lru_cost {\n+\tunsigned long\t\tcount;\n+\tunsigned long\t\tlast_rotated;\n+\tunsigned long\t\tlast_io;\n+};\n+\n struct lruvec {\n \tstruct list_head\t\tlists[NR_LRU_LISTS];\n \t/* per lruvec lru_lock for memcg */\n@@ -763,9 +771,12 @@ struct lruvec {\n \t * These track the cost of reclaiming one LRU - file or anon -\n \t * over the other. As the observed cost of reclaiming one LRU\n \t * increases, the reclaim scan balance tips toward the other.\n+\t * Updated and decayed at prepare_scan_control() time; cost_lock\n+\t * serialises that update.\n \t */\n-\tunsigned long\t\t\tanon_cost;\n-\tunsigned long\t\t\tfile_cost;\n+\tstruct lru_cost\t\t\tcost[ANON_AND_FILE];\n+\t/* Protects cost[]. */\n+\tspinlock_t\t\t\tcost_lock;\n \t/* Non-resident age, driven by LRU movement */\n \tatomic_long_t\t\t\tnonresident_age;\n \t/* Refaults at the time of last reclaim cycle */\ndiff --git a/include/linux/swap.h b/include/linux/swap.h\nindex 8f0f68e245baa3..b8e2e1af2b1425 100644\n--- a/include/linux/swap.h\n+++ b/include/linux/swap.h\n@@ -309,9 +309,6 @@ extern unsigned long totalreserve_pages;\n \n \n /* linux/mm/swap.c */\n-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file,\n-\t\tunsigned int nr_io, unsigned int nr_rotated);\n-void lru_note_cost_refault(struct folio *);\n void folio_add_lru(struct folio *);\n void folio_add_lru_vma(struct folio *, struct vm_area_struct *);\n void mark_page_accessed(struct page *);\ndiff --git a/include/linux/vmstat.h b/include/linux/vmstat.h\nindex 3c9c266cf782ad..5b31d8e7ae4058 100644\n--- a/include/linux/vmstat.h\n+++ b/include/linux/vmstat.h\n@@ -20,7 +20,6 @@ struct reclaim_stat {\n \tunsigned nr_congested;\n \tunsigned nr_writeback;\n \tunsigned nr_immediate;\n-\tunsigned nr_pageout;\n \tunsigned nr_activate[ANON_AND_FILE];\n \tunsigned nr_ref_keep;\n \tunsigned nr_unmap_fail;\n@@ -194,6 +193,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item)\n \treturn x;\n }\n \n+/*\n+ * Non-clamping variant of global_node_page_state() intended for callers that\n+ * snapshot a monotonically-incremented counter and subtract two samples.\n+ * Returns the raw wrapping value so that unsigned modular subtraction stays\n+ * correct across a signed-long overflow (a real hazard on 32-bit) that the\n+ * clamp in global_node_page_state() would otherwise turn into a huge spurious\n+ * delta. Do NOT use for non-monotonic page-count reads.\n+ */\n+static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item)\n+{\n+\treturn (unsigned long)atomic_long_read(\u0026vm_node_stat[item]);\n+}\n+\n static inline unsigned long global_node_page_state(enum node_stat_item item)\n {\n \tVM_WARN_ON_ONCE(vmstat_item_in_bytes(item));\n@@ -259,11 +271,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat,\n \t\t\t\t\t\tenum node_stat_item item);\n extern unsigned long node_page_state_pages(struct pglist_data *pgdat,\n \t\t\t\t\t enum node_stat_item item);\n+extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat,\n+\t\t\t\t\t enum node_stat_item item);\n extern void fold_vm_numa_events(void);\n #else\n #define sum_zone_node_page_state(node, item) global_zone_page_state(item)\n #define node_page_state(node, item) global_node_page_state(item)\n #define node_page_state_pages(node, item) global_node_page_state_pages(item)\n+#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item)\n static inline void fold_vm_numa_events(void)\n {\n }\ndiff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c\nindex 7650692115678c..091bc9ffee44a0 100644\n--- a/mm/memcontrol-v1.c\n+++ b/mm/memcontrol-v1.c\n@@ -1988,8 +1988,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s)\n \t\tfor_each_online_pgdat(pgdat) {\n \t\t\tmz = memcg-\u003enodeinfo[pgdat-\u003enode_id];\n \n-\t\t\tanon_cost += mz-\u003elruvec.anon_cost;\n-\t\t\tfile_cost += mz-\u003elruvec.file_cost;\n+\t\t\tanon_cost += mz-\u003elruvec.cost[WORKINGSET_ANON].count;\n+\t\t\tfile_cost += mz-\u003elruvec.cost[WORKINGSET_FILE].count;\n \t\t}\n \t\tseq_buf_printf(s, \"anon_cost %lu\\n\", anon_cost);\n \t\tseq_buf_printf(s, \"file_cost %lu\\n\", file_cost);\ndiff --git a/mm/memcontrol.c b/mm/memcontrol.c\nindex 6dc4888a90f3fd..ce9c4fb92470b6 100644\n--- a/mm/memcontrol.c\n+++ b/mm/memcontrol.c\n@@ -393,6 +393,7 @@ static const unsigned int memcg_node_stat_items[] = {\n \tNR_SHMEM_THPS,\n \tNR_FILE_THPS,\n \tNR_ANON_THPS,\n+\tNR_VMSCAN_WRITE,\n \tNR_VMALLOC,\n \tNR_KERNEL_STACK_KB,\n \tNR_PAGETABLE,\n@@ -419,6 +420,8 @@ static const unsigned int memcg_node_stat_items[] = {\n \tPGSCAN_PROACTIVE,\n \tPGSCAN_ANON,\n \tPGSCAN_FILE,\n+\tPGROTATE_ANON,\n+\tPGROTATE_FILE,\n \tPGREFILL,\n #ifdef CONFIG_HUGETLB_PAGE\n \tNR_HUGETLB,\n@@ -502,6 +505,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx)\n \treturn x;\n }\n \n+/**\n+ * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling\n+ * @lruvec: the LRU vector to read from\n+ * @idx: the node_stat_item to read\n+ *\n+ * Returns the raw state[idx] value cast to unsigned long, skipping the\n+ * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers\n+ * that snapshot a monotonically-incremented counter and subtract two\n+ * samples: unsigned modular arithmetic then yields the correct delta across\n+ * a signed-long wraparound (a real hazard on 32-bit) that the clamp would\n+ * otherwise turn into a huge spurious delta.\n+ *\n+ * Do NOT use for non-monotonic page-count reads where a transient negative\n+ * reading from per-CPU delta skew must present as zero.\n+ *\n+ * XXX: This helper (and its node/global peers) exists because some\n+ * monotonically-incremented event counters are stored in\n+ * enum node_stat_item.\n+ */\n+unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,\n+\t\t\t\t\t enum node_stat_item idx)\n+{\n+\tstruct mem_cgroup_per_node *pn;\n+\tint i;\n+\n+\tif (mem_cgroup_disabled())\n+\t\treturn node_page_state_monotonic(lruvec_pgdat(lruvec), idx);\n+\n+\ti = memcg_stats_index(idx);\n+\tif (WARN_ONCE(BAD_STAT_IDX(i), \"%s: missing stat item %d\\n\", __func__, idx))\n+\t\treturn 0;\n+\n+\tpn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);\n+\treturn (unsigned long)READ_ONCE(pn-\u003elruvec_stats-\u003estate[i]);\n+}\n+\n unsigned long lruvec_page_state_local(struct lruvec *lruvec,\n \t\t\t\t enum node_stat_item idx)\n {\ndiff --git a/mm/mmzone.c b/mm/mmzone.c\nindex 0c8f181d9d5006..17139db4d291ce 100644\n--- a/mm/mmzone.c\n+++ b/mm/mmzone.c\n@@ -78,6 +78,7 @@ void lruvec_init(struct lruvec *lruvec)\n \n \tmemset(lruvec, 0, sizeof(struct lruvec));\n \tspin_lock_init(\u0026lruvec-\u003elru_lock);\n+\tspin_lock_init(\u0026lruvec-\u003ecost_lock);\n \tzswap_lruvec_state_init(lruvec);\n \n \tfor_each_lru(lru)\ndiff --git a/mm/swap.c b/mm/swap.c\nindex 588f50d8f1a8c9..74b281778cbc92 100644\n--- a/mm/swap.c\n+++ b/mm/swap.c\n@@ -272,73 +272,6 @@ void folio_rotate_reclaimable(struct folio *folio)\n \tfolio_batch_add_and_move(folio, lru_move_tail);\n }\n \n-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file,\n-\t\tunsigned int nr_io, unsigned int nr_rotated)\n-\t\t__releases(lruvec-\u003elru_lock)\n-\t\t__releases(rcu)\n-{\n-\tunsigned long cost;\n-\n-\t/*\n-\t * Reflect the relative cost of incurring IO and spending CPU\n-\t * time on rotations. This doesn't attempt to make a precise\n-\t * comparison, it just says: if reloads are about comparable\n-\t * between the LRU lists, or rotations are overwhelmingly\n-\t * different between them, adjust scan balance for CPU work.\n-\t */\n-\tcost = nr_io * SWAP_CLUSTER_MAX + nr_rotated;\n-\tif (!cost) {\n-\t\tspin_unlock_irq(\u0026lruvec-\u003elru_lock);\n-\t\trcu_read_unlock();\n-\t\treturn;\n-\t}\n-\n-\tfor (;;) {\n-\t\tunsigned long lrusize;\n-\n-\t\t/* Record cost event */\n-\t\tif (file)\n-\t\t\tlruvec-\u003efile_cost += cost;\n-\t\telse\n-\t\t\tlruvec-\u003eanon_cost += cost;\n-\n-\t\t/*\n-\t\t * Decay previous events\n-\t\t *\n-\t\t * Because workloads change over time (and to avoid\n-\t\t * overflow) we keep these statistics as a floating\n-\t\t * average, which ends up weighing recent refaults\n-\t\t * more than old ones.\n-\t\t */\n-\t\tlrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) +\n-\t\t\t lruvec_page_state(lruvec, NR_ACTIVE_ANON) +\n-\t\t\t lruvec_page_state(lruvec, NR_INACTIVE_FILE) +\n-\t\t\t lruvec_page_state(lruvec, NR_ACTIVE_FILE);\n-\n-\t\tif (lruvec-\u003efile_cost + lruvec-\u003eanon_cost \u003e lrusize / 4) {\n-\t\t\tlruvec-\u003efile_cost /= 2;\n-\t\t\tlruvec-\u003eanon_cost /= 2;\n-\t\t}\n-\n-\t\tspin_unlock_irq(\u0026lruvec-\u003elru_lock);\n-\t\tlruvec = parent_lruvec(lruvec);\n-\t\tif (!lruvec) {\n-\t\t\trcu_read_unlock();\n-\t\t\tbreak;\n-\t\t}\n-\t\tspin_lock_irq(\u0026lruvec-\u003elru_lock);\n-\t}\n-}\n-\n-void lru_note_cost_refault(struct folio *folio)\n-{\n-\tstruct lruvec *lruvec;\n-\n-\tlruvec = folio_lruvec_lock_irq(folio);\n-\tlru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio),\n-\t\t\t\tfolio_nr_pages(folio), 0);\n-}\n-\n static void lru_activate(struct lruvec *lruvec, struct folio *folio)\n {\n \tlong nr_pages = folio_nr_pages(folio);\n@@ -1164,8 +1097,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int\n \n \tchild_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid));\n \tparent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid));\n-\tparent_lruvec-\u003eanon_cost += child_lruvec-\u003eanon_cost;\n-\tparent_lruvec-\u003efile_cost += child_lruvec-\u003efile_cost;\n \n \tfor_each_lru(lru)\n \t\tlruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid);\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 1a142c58700d00..4d8ab14e3be443 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -668,7 +668,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping,\n \t\tfolio_clear_reclaim(folio);\n \n \ttrace_mm_vmscan_write_folio(folio);\n-\tnode_stat_add_folio(folio, NR_VMSCAN_WRITE);\n+\tlruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio));\n \treturn PAGE_SUCCESS;\n }\n \n@@ -1413,8 +1413,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,\n \t\t\t\t\tsc-\u003enr_scanned -= (nr_pages - 1);\n \t\t\t\t\tnr_pages = 1;\n \t\t\t\t}\n-\t\t\t\tstat-\u003enr_pageout += nr_pages;\n-\n \t\t\t\tif (folio_test_writeback(folio))\n \t\t\t\t\tgoto keep;\n \t\t\t\tif (folio_test_dirty(folio))\n@@ -2038,10 +2036,10 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan,\n \titem = PGSTEAL_KSWAPD + reclaimer_offset(sc);\n \tmod_lruvec_state(lruvec, item, nr_reclaimed);\n \tmod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed);\n+\tif (nr_scanned \u003e nr_reclaimed)\n+\t\tmod_lruvec_state(lruvec, PGROTATE_ANON + file,\n+\t\t\t\t nr_scanned - nr_reclaimed);\n \n-\tlruvec_lock_irq(lruvec);\n-\tlru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout,\n-\t\t\t\t\tnr_scanned - nr_reclaimed);\n \thandle_reclaim_writeback(nr_taken, pgdat, sc, \u0026stat);\n \ttrace_mm_vmscan_lru_shrink_inactive(pgdat-\u003enode_id,\n \t\t\tnr_scanned, nr_reclaimed, \u0026stat, sc-\u003epriority, file);\n@@ -2147,9 +2145,9 @@ static void shrink_active_list(unsigned long nr_to_scan,\n \tcount_vm_events(PGDEACTIVATE, nr_deactivate);\n \tcount_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate);\n \tmod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken);\n+\tif (nr_rotated)\n+\t\tmod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated);\n \n-\tlruvec_lock_irq(lruvec);\n-\tlru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated);\n \ttrace_mm_vmscan_lru_shrink_active(pgdat-\u003enode_id, nr_taken, nr_activate,\n \t\t\tnr_deactivate, nr_rotated, sc-\u003epriority, file);\n }\n@@ -2282,8 +2280,10 @@ enum scan_balance {\n \n static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc)\n {\n-\tunsigned long file;\n+\tstruct lru_cost *anon_cost, *file_cost;\n \tstruct lruvec *target_lruvec;\n+\tunsigned long lrusize;\n+\tunsigned long file;\n \n \tif (lru_gen_enabled() \u0026\u0026 !lru_gen_switching())\n \t\treturn;\n@@ -2299,11 +2299,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc)\n \n \t/*\n \t * Determine the scan balance between anon and file LRUs.\n+\t *\n+\t * The cost model is based on rotations, refaults and\n+\t * reclaim-driven writes (anon only) on each side.\n+\t *\n+\t * These event counters are monotonic, so each reclaim cycle\n+\t * the delta since the last scan is extracted and incorporated\n+\t * into a decaying average. This ensures currency, as workloads\n+\t * change over time, and avoids overflow in the calculations.\n+\t *\n+\t * Use lruvec_page_state_monotonic() so unsigned subtraction\n+\t * yields the correct delta across a signed-long wraparound of\n+\t * the underlying counter (a real hazard on 32-bit that the\n+\t * clamp in lruvec_page_state() would otherwise turn into a huge\n+\t * spurious delta).\n \t */\n-\tspin_lock_irq(\u0026target_lruvec-\u003elru_lock);\n-\tsc-\u003eanon_cost = target_lruvec-\u003eanon_cost;\n-\tsc-\u003efile_cost = target_lruvec-\u003efile_cost;\n-\tspin_unlock_irq(\u0026target_lruvec-\u003elru_lock);\n+\tspin_lock(\u0026target_lruvec-\u003ecost_lock);\n+\n+\tfor (int f = 0; f \u003c= 1; f++) {\n+\t\tstruct lru_cost *cost = \u0026target_lruvec-\u003ecost[f];\n+\t\tunsigned long rotated, io, nr_rotated, nr_io;\n+\n+\t\trotated = lruvec_page_state_monotonic(target_lruvec,\n+\t\t\t\t\t\t PGROTATE_ANON + f);\n+\t\tio = lruvec_page_state_monotonic(target_lruvec,\n+\t\t\t\t\t\t WORKINGSET_RESTORE_BASE + f);\n+\t\tif (f == WORKINGSET_ANON)\n+\t\t\tio += lruvec_page_state_monotonic(target_lruvec,\n+\t\t\t\t\t\t\t NR_VMSCAN_WRITE);\n+\n+\t\tnr_rotated = rotated - cost-\u003elast_rotated;\n+\t\tnr_io = io - cost-\u003elast_io;\n+\n+\t\t/*\n+\t\t * Reflect the relative cost of incurring IO and spending\n+\t\t * CPU time on rotations. This doesn't attempt to make a\n+\t\t * precise comparison, it just says: if reloads are about\n+\t\t * comparable between the LRU lists, or rotations are\n+\t\t * overwhelmingly different between them, adjust scan\n+\t\t * balance for CPU work.\n+\t\t */\n+\t\tcost-\u003ecount += nr_io * SWAP_CLUSTER_MAX + nr_rotated;\n+\n+\t\tcost-\u003elast_rotated = rotated;\n+\t\tcost-\u003elast_io = io;\n+\t}\n+\n+\tanon_cost = \u0026target_lruvec-\u003ecost[WORKINGSET_ANON];\n+\tfile_cost = \u0026target_lruvec-\u003ecost[WORKINGSET_FILE];\n+\n+\tlrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) +\n+\t\t lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) +\n+\t\t lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) +\n+\t\t lruvec_page_state(target_lruvec, NR_ACTIVE_FILE);\n+\n+\twhile (anon_cost-\u003ecount + file_cost-\u003ecount \u003e lrusize / 4) {\n+\t\tanon_cost-\u003ecount /= 2;\n+\t\tfile_cost-\u003ecount /= 2;\n+\t}\n+\n+\tsc-\u003eanon_cost = anon_cost-\u003ecount;\n+\tsc-\u003efile_cost = file_cost-\u003ecount;\n+\n+\tspin_unlock(\u0026target_lruvec-\u003ecost_lock);\n \n \t/*\n \t * Target desirable inactive:active list ratios for the anon\n@@ -4809,7 +4867,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n \tstruct reclaim_stat stat;\n \tstruct lru_gen_mm_walk *walk;\n \tint scanned, reclaimed;\n-\tint isolated = 0, type, type_scanned;\n+\tint isolated = 0, nr_isolated = 0, type, type_scanned;\n+\tunsigned long total_reclaimed = 0;\n \tbool skip_retry = false;\n \tstruct mem_cgroup *memcg = lruvec_memcg(lruvec);\n \tstruct pglist_data *pgdat = lruvec_pgdat(lruvec);\n@@ -4821,6 +4880,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n \n \tscanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness,\n \t\t\t\t \u0026list, \u0026isolated, \u0026type, \u0026type_scanned);\n+\tnr_isolated = isolated;\n \n \t/* Scanning may have emptied the oldest gen, flush it */\n \tif (scanned)\n@@ -4833,6 +4893,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n retry:\n \treclaimed = shrink_folio_list(\u0026list, pgdat, sc, \u0026stat, false, memcg);\n \tsc-\u003enr_reclaimed += reclaimed;\n+\ttotal_reclaimed += reclaimed;\n \t/* Retry pass is only meant for clean folios without new isolation */\n \tif (isolated)\n \t\thandle_reclaim_writeback(isolated, pgdat, sc, \u0026stat);\n@@ -4884,6 +4945,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n \t\tgoto retry;\n \t}\n \n+\tif (nr_isolated \u003e total_reclaimed)\n+\t\tmod_lruvec_state(lruvec, PGROTATE_ANON + type,\n+\t\t\t\t nr_isolated - total_reclaimed);\n+\n \treturn scanned;\n }\n \ndiff --git a/mm/vmstat.c b/mm/vmstat.c\nindex f534972f517de0..87d4a678136767 100644\n--- a/mm/vmstat.c\n+++ b/mm/vmstat.c\n@@ -1024,6 +1024,17 @@ unsigned long node_page_state(struct pglist_data *pgdat,\n \n \treturn node_page_state_pages(pgdat, item);\n }\n+\n+/*\n+ * Non-clamping variant of node_page_state() intended for callers that\n+ * snapshot a monotonically-incremented counter and subtract two samples.\n+ * See global_node_page_state_monotonic() for the rationale.\n+ */\n+unsigned long node_page_state_monotonic(struct pglist_data *pgdat,\n+\t\t\t\t\tenum node_stat_item item)\n+{\n+\treturn (unsigned long)atomic_long_read(\u0026pgdat-\u003evm_stat[item]);\n+}\n #endif\n \n /*\n@@ -1289,6 +1300,8 @@ const char * const vmstat_text[] = {\n \t[I(PGSCAN_PROACTIVE)]\t\t\t= \"pgscan_proactive\",\n \t[I(PGSCAN_ANON)]\t\t\t= \"pgscan_anon\",\n \t[I(PGSCAN_FILE)]\t\t\t= \"pgscan_file\",\n+\t[I(PGROTATE_ANON)]\t\t\t= \"pgrotate_anon\",\n+\t[I(PGROTATE_FILE)]\t\t\t= \"pgrotate_file\",\n \t[I(PGREFILL)]\t\t\t\t= \"pgrefill\",\n #ifdef CONFIG_HUGETLB_PAGE\n \t[I(NR_HUGETLB)]\t\t\t\t= \"nr_hugetlb\",\ndiff --git a/mm/workingset.c b/mm/workingset.c\nindex f351798e723acd..7ac2b88c80ae56 100644\n--- a/mm/workingset.c\n+++ b/mm/workingset.c\n@@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow)\n \t/* Folio was active prior to eviction */\n \tif (workingset) {\n \t\tfolio_set_workingset(folio);\n-\t\t/*\n-\t\t * XXX: Move to folio_add_lru() when it supports new vs\n-\t\t * putback\n-\t\t */\n-\t\tlru_note_cost_refault(folio);\n \t\tmod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr);\n \t}\n out:\n"
} |
| 2/1 | 2026/07/28 08:49 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"MEMCG",
"MEMCG_V1",
"LRU_GEN"
],
"FocusSymbols": [
"try_to_free_pages",
"try_to_free_mem_cgroup_pages",
"wakeup_kswapd",
"workingset_refault"
],
"Reasoning": "The patch modifies the core memory management subsystem, specifically the LRU reclaim logic and cost calculation for both legacy LRU and MGLRU. It introduces monotonic counters for LRU stats and changes how scan balance is determined. These are functional changes to reachable core kernel code that should be fuzzed.",
"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 c23bd308d4a776d56012b8c2c8fff1e3fde2a2a2
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Jul 28 08:49:23 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 8170bb8066a22f..7ae67ac2351076 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -947,6 +947,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item);
bool memcg_stat_item_valid(int idx);
bool memcg_vm_event_item_valid(enum vm_event_item idx);
unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx);
+unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,
+ enum node_stat_item idx);
unsigned long lruvec_page_state_local(struct lruvec *lruvec,
enum node_stat_item idx);
@@ -1399,6 +1401,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec,
return node_page_state(lruvec_pgdat(lruvec), idx);
}
+static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,
+ enum node_stat_item idx)
+{
+ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx);
+}
+
static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec,
enum node_stat_item idx)
{
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ca27121871475c..85303c5867c84b 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -323,6 +323,8 @@ enum node_stat_item {
PGSCAN_PROACTIVE,
PGSCAN_ANON,
PGSCAN_FILE,
+ PGROTATE_ANON,
+ PGROTATE_FILE,
PGREFILL,
#ifdef CONFIG_HUGETLB_PAGE
NR_HUGETLB,
@@ -755,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent,
#endif /* CONFIG_LRU_GEN */
+struct lru_cost {
+ unsigned long count;
+ unsigned long last_rotated;
+ unsigned long last_io;
+};
+
struct lruvec {
struct list_head lists[NR_LRU_LISTS];
/* per lruvec lru_lock for memcg */
@@ -763,9 +771,12 @@ struct lruvec {
* These track the cost of reclaiming one LRU - file or anon -
* over the other. As the observed cost of reclaiming one LRU
* increases, the reclaim scan balance tips toward the other.
+ * Updated and decayed at prepare_scan_control() time; cost_lock
+ * serialises that update.
*/
- unsigned long anon_cost;
- unsigned long file_cost;
+ struct lru_cost cost[ANON_AND_FILE];
+ /* Protects cost[]. */
+ spinlock_t cost_lock;
/* Non-resident age, driven by LRU movement */
atomic_long_t nonresident_age;
/* Refaults at the time of last reclaim cycle */
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8f0f68e245baa3..b8e2e1af2b1425 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -309,9 +309,6 @@ extern unsigned long totalreserve_pages;
/* linux/mm/swap.c */
-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file,
- unsigned int nr_io, unsigned int nr_rotated);
-void lru_note_cost_refault(struct folio *);
void folio_add_lru(struct folio *);
void folio_add_lru_vma(struct folio *, struct vm_area_struct *);
void mark_page_accessed(struct page *);
diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h
index 3c9c266cf782ad..5b31d8e7ae4058 100644
--- a/include/linux/vmstat.h
+++ b/include/linux/vmstat.h
@@ -20,7 +20,6 @@ struct reclaim_stat {
unsigned nr_congested;
unsigned nr_writeback;
unsigned nr_immediate;
- unsigned nr_pageout;
unsigned nr_activate[ANON_AND_FILE];
unsigned nr_ref_keep;
unsigned nr_unmap_fail;
@@ -194,6 +193,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item)
return x;
}
+/*
+ * Non-clamping variant of global_node_page_state() intended for callers that
+ * snapshot a monotonically-incremented counter and subtract two samples.
+ * Returns the raw wrapping value so that unsigned modular subtraction stays
+ * correct across a signed-long overflow (a real hazard on 32-bit) that the
+ * clamp in global_node_page_state() would otherwise turn into a huge spurious
+ * delta. Do NOT use for non-monotonic page-count reads.
+ */
+static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item)
+{
+ return (unsigned long)atomic_long_read(&vm_node_stat[item]);
+}
+
static inline unsigned long global_node_page_state(enum node_stat_item item)
{
VM_WARN_ON_ONCE(vmstat_item_in_bytes(item));
@@ -259,11 +271,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat,
enum node_stat_item item);
extern unsigned long node_page_state_pages(struct pglist_data *pgdat,
enum node_stat_item item);
+extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat,
+ enum node_stat_item item);
extern void fold_vm_numa_events(void);
#else
#define sum_zone_node_page_state(node, item) global_zone_page_state(item)
#define node_page_state(node, item) global_node_page_state(item)
#define node_page_state_pages(node, item) global_node_page_state_pages(item)
+#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item)
static inline void fold_vm_numa_events(void)
{
}
diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
index 7650692115678c..091bc9ffee44a0 100644
--- a/mm/memcontrol-v1.c
+++ b/mm/memcontrol-v1.c
@@ -1988,8 +1988,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s)
for_each_online_pgdat(pgdat) {
mz = memcg->nodeinfo[pgdat->node_id];
- anon_cost += mz->lruvec.anon_cost;
- file_cost += mz->lruvec.file_cost;
+ anon_cost += mz->lruvec.cost[WORKINGSET_ANON].count;
+ file_cost += mz->lruvec.cost[WORKINGSET_FILE].count;
}
seq_buf_printf(s, "anon_cost %lu\n", anon_cost);
seq_buf_printf(s, "file_cost %lu\n", file_cost);
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 6dc4888a90f3fd..ce9c4fb92470b6 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -393,6 +393,7 @@ static const unsigned int memcg_node_stat_items[] = {
NR_SHMEM_THPS,
NR_FILE_THPS,
NR_ANON_THPS,
+ NR_VMSCAN_WRITE,
NR_VMALLOC,
NR_KERNEL_STACK_KB,
NR_PAGETABLE,
@@ -419,6 +420,8 @@ static const unsigned int memcg_node_stat_items[] = {
PGSCAN_PROACTIVE,
PGSCAN_ANON,
PGSCAN_FILE,
+ PGROTATE_ANON,
+ PGROTATE_FILE,
PGREFILL,
#ifdef CONFIG_HUGETLB_PAGE
NR_HUGETLB,
@@ -502,6 +505,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx)
return x;
}
+/**
+ * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling
+ * @lruvec: the LRU vector to read from
+ * @idx: the node_stat_item to read
+ *
+ * Returns the raw state[idx] value cast to unsigned long, skipping the
+ * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers
+ * that snapshot a monotonically-incremented counter and subtract two
+ * samples: unsigned modular arithmetic then yields the correct delta across
+ * a signed-long wraparound (a real hazard on 32-bit) that the clamp would
+ * otherwise turn into a huge spurious delta.
+ *
+ * Do NOT use for non-monotonic page-count reads where a transient negative
+ * reading from per-CPU delta skew must present as zero.
+ *
+ * XXX: This helper (and its node/global peers) exists because some
+ * monotonically-incremented event counters are stored in
+ * enum node_stat_item.
+ */
+unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,
+ enum node_stat_item idx)
+{
+ struct mem_cgroup_per_node *pn;
+ int i;
+
+ if (mem_cgroup_disabled())
+ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx);
+
+ i = memcg_stats_index(idx);
+ if (WARN_ONCE(BAD_STAT_IDX(i), "%s: missing stat item %d\n", __func__, idx))
+ return 0;
+
+ pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
+ return (unsigned long)READ_ONCE(pn->lruvec_stats->state[i]);
+}
+
unsigned long lruvec_page_state_local(struct lruvec *lruvec,
enum node_stat_item idx)
{
diff --git a/mm/mmzone.c b/mm/mmzone.c
index 0c8f181d9d5006..17139db4d291ce 100644
--- a/mm/mmzone.c
+++ b/mm/mmzone.c
@@ -78,6 +78,7 @@ void lruvec_init(struct lruvec *lruvec)
memset(lruvec, 0, sizeof(struct lruvec));
spin_lock_init(&lruvec->lru_lock);
+ spin_lock_init(&lruvec->cost_lock);
zswap_lruvec_state_init(lruvec);
for_each_lru(lru)
diff --git a/mm/swap.c b/mm/swap.c
index 588f50d8f1a8c9..74b281778cbc92 100644
--- a/mm/swap.c
+++ b/mm/swap.c
@@ -272,73 +272,6 @@ void folio_rotate_reclaimable(struct folio *folio)
folio_batch_add_and_move(folio, lru_move_tail);
}
-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file,
- unsigned int nr_io, unsigned int nr_rotated)
- __releases(lruvec->lru_lock)
- __releases(rcu)
-{
- unsigned long cost;
-
- /*
- * Reflect the relative cost of incurring IO and spending CPU
- * time on rotations. This doesn't attempt to make a precise
- * comparison, it just says: if reloads are about comparable
- * between the LRU lists, or rotations are overwhelmingly
- * different between them, adjust scan balance for CPU work.
- */
- cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated;
- if (!cost) {
- spin_unlock_irq(&lruvec->lru_lock);
- rcu_read_unlock();
- return;
- }
-
- for (;;) {
- unsigned long lrusize;
-
- /* Record cost event */
- if (file)
- lruvec->file_cost += cost;
- else
- lruvec->anon_cost += cost;
-
- /*
- * Decay previous events
- *
- * Because workloads change over time (and to avoid
- * overflow) we keep these statistics as a floating
- * average, which ends up weighing recent refaults
- * more than old ones.
- */
- lrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) +
- lruvec_page_state(lruvec, NR_ACTIVE_ANON) +
- lruvec_page_state(lruvec, NR_INACTIVE_FILE) +
- lruvec_page_state(lruvec, NR_ACTIVE_FILE);
-
- if (lruvec->file_cost + lruvec->anon_cost > lrusize / 4) {
- lruvec->file_cost /= 2;
- lruvec->anon_cost /= 2;
- }
-
- spin_unlock_irq(&lruvec->lru_lock);
- lruvec = parent_lruvec(lruvec);
- if (!lruvec) {
- rcu_read_unlock();
- break;
- }
- spin_lock_irq(&lruvec->lru_lock);
- }
-}
-
-void lru_note_cost_refault(struct folio *folio)
-{
- struct lruvec *lruvec;
-
- lruvec = folio_lruvec_lock_irq(folio);
- lru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio),
- folio_nr_pages(folio), 0);
-}
-
static void lru_activate(struct lruvec *lruvec, struct folio *folio)
{
long nr_pages = folio_nr_pages(folio);
@@ -1164,8 +1097,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int
child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid));
parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid));
- parent_lruvec->anon_cost += child_lruvec->anon_cost;
- parent_lruvec->file_cost += child_lruvec->file_cost;
for_each_lru(lru)
lruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid);
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 1a142c58700d00..4d8ab14e3be443 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -668,7 +668,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping,
folio_clear_reclaim(folio);
trace_mm_vmscan_write_folio(folio);
- node_stat_add_folio(folio, NR_VMSCAN_WRITE);
+ lruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio));
return PAGE_SUCCESS;
}
@@ -1413,8 +1413,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
sc->nr_scanned -= (nr_pages - 1);
nr_pages = 1;
}
- stat->nr_pageout += nr_pages;
-
if (folio_test_writeback(folio))
goto keep;
if (folio_test_dirty(folio))
@@ -2038,10 +2036,10 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan,
item = PGSTEAL_KSWAPD + reclaimer_offset(sc);
mod_lruvec_state(lruvec, item, nr_reclaimed);
mod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed);
+ if (nr_scanned > nr_reclaimed)
+ mod_lruvec_state(lruvec, PGROTATE_ANON + file,
+ nr_scanned - nr_reclaimed);
- lruvec_lock_irq(lruvec);
- lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout,
- nr_scanned - nr_reclaimed);
handle_reclaim_writeback(nr_taken, pgdat, sc, &stat);
trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id,
nr_scanned, nr_reclaimed, &stat, sc->priority, file);
@@ -2147,9 +2145,9 @@ static void shrink_active_list(unsigned long nr_to_scan,
count_vm_events(PGDEACTIVATE, nr_deactivate);
count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate);
mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken);
+ if (nr_rotated)
+ mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated);
- lruvec_lock_irq(lruvec);
- lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated);
trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate,
nr_deactivate, nr_rotated, sc->priority, file);
}
@@ -2282,8 +2280,10 @@ enum scan_balance {
static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc)
{
- unsigned long file;
+ struct lru_cost *anon_cost, *file_cost;
struct lruvec *target_lruvec;
+ unsigned long lrusize;
+ unsigned long file;
if (lru_gen_enabled() && !lru_gen_switching())
return;
@@ -2299,11 +2299,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc)
/*
* Determine the scan balance between anon and file LRUs.
+ *
+ * The cost model is based on rotations, refaults and
+ * reclaim-driven writes (anon only) on each side.
+ *
+ * These event counters are monotonic, so each reclaim cycle
+ * the delta since the last scan is extracted and incorporated
+ * into a decaying average. This ensures currency, as workloads
+ * change over time, and avoids overflow in the calculations.
+ *
+ * Use lruvec_page_state_monotonic() so unsigned subtraction
+ * yields the correct delta across a signed-long wraparound of
+ * the underlying counter (a real hazard on 32-bit that the
+ * clamp in lruvec_page_state() would otherwise turn into a huge
+ * spurious delta).
*/
- spin_lock_irq(&target_lruvec->lru_lock);
- sc->anon_cost = target_lruvec->anon_cost;
- sc->file_cost = target_lruvec->file_cost;
- spin_unlock_irq(&target_lruvec->lru_lock);
+ spin_lock(&target_lruvec->cost_lock);
+
+ for (int f = 0; f <= 1; f++) {
+ struct lru_cost *cost = &target_lruvec->cost[f];
+ unsigned long rotated, io, nr_rotated, nr_io;
+
+ rotated = lruvec_page_state_monotonic(target_lruvec,
+ PGROTATE_ANON + f);
+ io = lruvec_page_state_monotonic(target_lruvec,
+ WORKINGSET_RESTORE_BASE + f);
+ if (f == WORKINGSET_ANON)
+ io += lruvec_page_state_monotonic(target_lruvec,
+ NR_VMSCAN_WRITE);
+
+ nr_rotated = rotated - cost->last_rotated;
+ nr_io = io - cost->last_io;
+
+ /*
+ * Reflect the relative cost of incurring IO and spending
+ * CPU time on rotations. This doesn't attempt to make a
+ * precise comparison, it just says: if reloads are about
+ * comparable between the LRU lists, or rotations are
+ * overwhelmingly different between them, adjust scan
+ * balance for CPU work.
+ */
+ cost->count += nr_io * SWAP_CLUSTER_MAX + nr_rotated;
+
+ cost->last_rotated = rotated;
+ cost->last_io = io;
+ }
+
+ anon_cost = &target_lruvec->cost[WORKINGSET_ANON];
+ file_cost = &target_lruvec->cost[WORKINGSET_FILE];
+
+ lrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) +
+ lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) +
+ lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) +
+ lruvec_page_state(target_lruvec, NR_ACTIVE_FILE);
+
+ while (anon_cost->count + file_cost->count > lrusize / 4) {
+ anon_cost->count /= 2;
+ file_cost->count /= 2;
+ }
+
+ sc->anon_cost = anon_cost->count;
+ sc->file_cost = file_cost->count;
+
+ spin_unlock(&target_lruvec->cost_lock);
/*
* Target desirable inactive:active list ratios for the anon
@@ -4809,7 +4867,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
struct reclaim_stat stat;
struct lru_gen_mm_walk *walk;
int scanned, reclaimed;
- int isolated = 0, type, type_scanned;
+ int isolated = 0, nr_isolated = 0, type, type_scanned;
+ unsigned long total_reclaimed = 0;
bool skip_retry = false;
struct mem_cgroup *memcg = lruvec_memcg(lruvec);
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
@@ -4821,6 +4880,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness,
&list, &isolated, &type, &type_scanned);
+ nr_isolated = isolated;
/* Scanning may have emptied the oldest gen, flush it */
if (scanned)
@@ -4833,6 +4893,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
retry:
reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg);
sc->nr_reclaimed += reclaimed;
+ total_reclaimed += reclaimed;
/* Retry pass is only meant for clean folios without new isolation */
if (isolated)
handle_reclaim_writeback(isolated, pgdat, sc, &stat);
@@ -4884,6 +4945,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
goto retry;
}
+ if (nr_isolated > total_reclaimed)
+ mod_lruvec_state(lruvec, PGROTATE_ANON + type,
+ nr_isolated - total_reclaimed);
+
return scanned;
}
diff --git a/mm/vmstat.c b/mm/vmstat.c
index f534972f517de0..87d4a678136767 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1024,6 +1024,17 @@ unsigned long node_page_state(struct pglist_data *pgdat,
return node_page_state_pages(pgdat, item);
}
+
+/*
+ * Non-clamping variant of node_page_state() intended for callers that
+ * snapshot a monotonically-incremented counter and subtract two samples.
+ * See global_node_page_state_monotonic() for the rationale.
+ */
+unsigned long node_page_state_monotonic(struct pglist_data *pgdat,
+ enum node_stat_item item)
+{
+ return (unsigned long)atomic_long_read(&pgdat->vm_stat[item]);
+}
#endif
/*
@@ -1289,6 +1300,8 @@ const char * const vmstat_text[] = {
[I(PGSCAN_PROACTIVE)] = "pgscan_proactive",
[I(PGSCAN_ANON)] = "pgscan_anon",
[I(PGSCAN_FILE)] = "pgscan_file",
+ [I(PGROTATE_ANON)] = "pgrotate_anon",
+ [I(PGROTATE_FILE)] = "pgrotate_file",
[I(PGREFILL)] = "pgrefill",
#ifdef CONFIG_HUGETLB_PAGE
[I(NR_HUGETLB)] = "nr_hugetlb",
diff --git a/mm/workingset.c b/mm/workingset.c
index f351798e723acd..7ac2b88c80ae56 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow)
/* Folio was active prior to eviction */
if (workingset) {
folio_set_workingset(folio);
- /*
- * XXX: Move to folio_add_lru() when it supports new vs
- * putback
- */
- lru_note_cost_refault(folio);
mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr);
}
out:
|
| 3/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9276 output: 31 thoughts: 504 Thoughts: **My Analysis of a Memory Management Patch** Okay, so I'm looking at a patch that fundamentally alters how the Least Recently Used (LRU) cost is computed within the memory management subsystem. This is a pretty significant change, touching core files like `mm/vmscan.c`, `mm/swap.c`, and so on. They're ditching the old `lru_note_cost_unlock_irq` and `lru_note_cost_refault` mechanisms. Instead, they're introducing a new approach using `prepare_scan_control` and `lruvec_page_state_monotonic`, which sound like they leverage monotonic counters, alongside new enum items and changes to the `lruvec` struct. My initial thought is that this demands a thorough fuzzing campaign. It’s a functional change that could introduce subtle bugs, performance regressions, or even security vulnerabilities in the memory reclaim process. Let's pinpoint the key functions to concentrate on. I see modifications and additions to a bunch of functions: `lruvec_page_state_monotonic`, `global_node_page_state_monotonic`, `node_page_state_monotonic`, `prepare_scan_control`, `evict_folios`, `shrink_inactive_list`, `shrink_active_list`, and `pageout`. Pretty much the heart of vmscan, it looks like. I need to focus on `prepare_scan_control`, `evict_folios`, `shrink_inactive_list`, `shrink_active_list`, and `pageout`. Wait… all of these are static functions? Hmm. The prompt is reminding me to prefer non-static, non-inlined API entrypoints. That means I need to identify the *callers* of these functions, rather than the functions themselves. Let's see... `prepare_scan_control` is called by `get_scan_count` or `shrink_node` or `shrink_lruvec`. I'll focus on the non-static callers of these. Looks like I will need to search for `prepare_scan_control` in `mm/vmscan.c`. |
| 4/2 | 2026/07/28 08:49 | tool | grepper |
0mArgs:{
"Expression": "prepare_scan_control",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=2274=enum scan_balance {\n--\nmm/vmscan.c-2280-\nmm/vmscan.c:2281:static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc)\nmm/vmscan.c-2282-{\n--\nmm/vmscan.c=6208=static void shrink_node(pg_data_t *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-6230-\nmm/vmscan.c:6231:\tprepare_scan_control(pgdat, sc);\nmm/vmscan.c-6232-\n"
} |
| 5/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10454 output: 29 thoughts: 14 Thoughts: Here's my thought process, as if I were considering this myself: **Static Function Check** Okay, so I'm revisiting this `shrink_node` function. A quick check tells me it's likely a static function, but I can't quite remember. It would make sense given its purpose. I'll need to confirm that this has been declared correctly. Time to verify its scope. If it *is* static, the implications are very important, especially in a multithreaded environment. Let me double-check the code immediately. I need to make sure this isn't going to cause any unintended consequences down the line with shared data or state. |
| 6/2 | 2026/07/28 08:49 | tool | grepper |
0mArgs:{
"Expression": "shrink_node",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=1942=static void handle_reclaim_writeback(unsigned long nr_taken,\n--\nmm/vmscan.c-1980-/*\nmm/vmscan.c:1981: * shrink_inactive_list() is a helper for shrink_node(). It returns the number\nmm/vmscan.c-1982- * of reclaimed pages\n--\nmm/vmscan.c=5177=static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)\n--\nmm/vmscan.c-5197-\nmm/vmscan.c:5198:static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)\nmm/vmscan.c-5199-{\n--\nmm/vmscan.c=5926=static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)\n--\nmm/vmscan.c-5930-\nmm/vmscan.c:5931:static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)\nmm/vmscan.c-5932-{\n--\nmm/vmscan.c=6077=static inline bool should_continue_reclaim(struct pglist_data *pgdat,\n--\nmm/vmscan.c-6128-\nmm/vmscan.c:6129:static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc)\nmm/vmscan.c-6130-{\n--\nmm/vmscan.c-6207-\nmm/vmscan.c:6208:static void shrink_node(pg_data_t *pgdat, struct scan_control *sc)\nmm/vmscan.c-6209-{\n--\nmm/vmscan.c-6215-\t\tmemset(\u0026sc-\u003enr, 0, sizeof(sc-\u003enr));\nmm/vmscan.c:6216:\t\tlru_gen_shrink_node(pgdat, sc);\nmm/vmscan.c-6217-\n--\nmm/vmscan.c-6232-\nmm/vmscan.c:6233:\tshrink_node_memcgs(pgdat, sc);\nmm/vmscan.c-6234-\n--\nmm/vmscan.c=6393=static void shrink_zones(struct zonelist *zonelist, struct scan_control *sc)\n--\nmm/vmscan.c-6471-\t\tlast_pgdat = zone-\u003ezone_pgdat;\nmm/vmscan.c:6472:\t\tshrink_node(zone-\u003ezone_pgdat, sc);\nmm/vmscan.c-6473-\t}\n--\nmm/vmscan.c=6738=unsigned long try_to_free_pages(struct zonelist *zonelist, int order,\n--\nmm/vmscan.c-6783-/* Only used by soft limit reclaim. Do not reuse for anything else. */\nmm/vmscan.c:6784:unsigned long mem_cgroup_shrink_node(struct mem_cgroup *memcg,\nmm/vmscan.c-6785-\t\t\t\t\t\tgfp_t gfp_mask, bool noswap,\n--\nmm/vmscan.c-6810-\t * here is not a good idea, since it limits the pages we can scan.\nmm/vmscan.c:6811:\t * if we don't reclaim here, the shrink_node from balance_pgdat\nmm/vmscan.c-6812-\t * will pick up pages from other mem cgroup's as well. We hack\n--\nmm/vmscan.c=6932=static bool pgdat_balanced(pg_data_t *pgdat, int order, int highest_zoneidx)\n--\nmm/vmscan.c-6956-\t\t * its own and needs kcompactd support. Once it's\nmm/vmscan.c:6957:\t\t * reclaimed a compaction gap, and kswapd_shrink_node\nmm/vmscan.c-6958-\t\t * has dropped order, simply ensure there are enough\n--\nmm/vmscan.c=7014=static bool prepare_kswapd_sleep(pg_data_t *pgdat, int order,\n--\nmm/vmscan.c-7052- */\nmm/vmscan.c:7053:static bool kswapd_shrink_node(pg_data_t *pgdat,\nmm/vmscan.c-7054-\t\t\t struct scan_control *sc)\n--\nmm/vmscan.c-7069-\t */\nmm/vmscan.c:7070:\tshrink_node(pgdat, sc);\nmm/vmscan.c-7071-\n--\nmm/vmscan.c=7126=static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx)\n--\nmm/vmscan.c-7235-\nmm/vmscan.c:7236:\t\t/* Call soft limit reclaim before calling shrink_node. */\nmm/vmscan.c-7237-\t\tsc.nr_scanned = 0;\n--\nmm/vmscan.c-7247-\t\t */\nmm/vmscan.c:7248:\t\tif (kswapd_shrink_node(pgdat, \u0026sc))\nmm/vmscan.c-7249-\t\t\traise_priority = false;\n--\nmm/vmscan.c=7803=static unsigned long __node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask,\n--\nmm/vmscan.c-7830-\t\tdo {\nmm/vmscan.c:7831:\t\t\tshrink_node(pgdat, sc);\nmm/vmscan.c-7832-\t\t} while (sc-\u003enr_reclaimed \u003c nr_pages \u0026\u0026 --sc-\u003epriority \u003e= 0);\n"
} |
| 7/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12160 output: 33 thoughts: 51 Thoughts: Okay, here's my take on those entry points as a memory management expert, presented as a personal thought process: **Diving into the Memory Reclamation Landscape** Right, let's get into the nitty-gritty of memory reclaim. I see `try_to_free_pages`, `balance_pgdat`, `__node_reclaim`, and `mem_cgroup_shrink_node` listed as the entry points. Interesting. That gives me a good starting point to think about the different ways we can trigger memory pressure resolution. `try_to_free_pages` seems like the obvious first stop. It's the most direct, trying to explicitly free pages. I'll need to dig in there and see how it works, what triggers it, and what the overall strategy is. I'll start by taking a close look at that routine – let's see how it orchestrates the freeing process and interacts with the other functions. I wonder how it decides what to reclaim, and how aggressively. It's the gateway, so it's critical to understand the foundation of memory reclamation. |
| 8/2 | 2026/07/28 08:49 | tool | grepper |
0mArgs:{
"Expression": "try_to_free_pages",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=6485=static void snapshot_refaults(struct mem_cgroup *target_memcg, pg_data_t *pgdat)\n--\nmm/vmscan.c-6515- */\nmm/vmscan.c:6516:static unsigned long do_try_to_free_pages(struct zonelist *zonelist,\nmm/vmscan.c-6517-\t\t\t\t\t struct scan_control *sc)\n--\nmm/vmscan.c=6658=static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist,\n--\nmm/vmscan.c-6737-\nmm/vmscan.c:6738:unsigned long try_to_free_pages(struct zonelist *zonelist, int order,\nmm/vmscan.c-6739-\t\t\t\tgfp_t gfp_mask, nodemask_t *nodemask)\n--\nmm/vmscan.c-6772-\nmm/vmscan.c:6773:\tnr_reclaimed = do_try_to_free_pages(zonelist, \u0026sc);\nmm/vmscan.c-6774-\n--\nmm/vmscan.c=6824=unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,\n--\nmm/vmscan.c-6855-\nmm/vmscan.c:6856:\tnr_reclaimed = do_try_to_free_pages(zonelist, \u0026sc);\nmm/vmscan.c-6857-\n--\nmm/vmscan.c=7622=unsigned long shrink_all_memory(unsigned long nr_to_reclaim)\n--\nmm/vmscan.c-7641-\nmm/vmscan.c:7642:\tnr_reclaimed = do_try_to_free_pages(zonelist, \u0026sc);\nmm/vmscan.c-7643-\n"
} |
| 9/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12983 output: 29 thoughts: 73 Thoughts: **Focusing on Memory Management Functions** Okay, let's zero in on some key areas here. I'm thinking `try_to_free_pages` is a solid function to start with; it seems like a core element in this context. But, hmm, what about `try_to_free_mem_cgroup_pages`? Yes, that's another important one, especially if we're dealing with memory control groups. That's definitely worth investigating further. Now, thinking more broadly, I should probably also consider something that interacts with swapping. Let's think, `balance_pgdat` is called by `kswapd`. Okay, so `kswapd` is critical. It sounds like a central piece, so let's check that out. |
| 10/2 | 2026/07/28 08:49 | tool | grepper |
0mArgs:{
"Expression": "kswapd",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 524 lines.\nUse more precise expression if possible.\n\nmm/vmscan.c-5- * Swap reorganised 29.12.95, Stephen Tweedie.\nmm/vmscan.c:6: * kswapd added: 7.1.96 sct\nmm/vmscan.c:7: * Removed kswapd_ctl limits, and swap out as many pages as needed\nmm/vmscan.c-8- * to bring the system back to freepages.high: 2.4.97, Rik van Riel.\nmm/vmscan.c:9: * Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).\nmm/vmscan.c-10- * Multiqueue VM started 5.8.00, Rik van Riel.\n--\nmm/vmscan.c=444=static int reclaimer_offset(struct scan_control *sc)\n--\nmm/vmscan.c-449-\nmm/vmscan.c:450:\tif (current_is_kswapd())\nmm/vmscan.c-451-\t\treturn 0;\n--\nmm/vmscan.c=480=static bool skip_throttle_noprogress(pg_data_t *pgdat)\n--\nmm/vmscan.c-485-\t/*\nmm/vmscan.c:486:\t * If kswapd is disabled, reschedule if necessary but do not\nmm/vmscan.c-487-\t * throttle as the system is likely near OOM.\nmm/vmscan.c-488-\t */\nmm/vmscan.c:489:\tif (kswapd_test_hopeless(pgdat))\nmm/vmscan.c-490-\t\treturn true;\n--\nmm/vmscan.c=508=void reclaim_throttle(pg_data_t *pgdat, enum vmscan_throttle_state reason)\n--\nmm/vmscan.c-514-\t/*\nmm/vmscan.c:515:\t * Do not throttle user workers, kthreads other than kswapd or\nmm/vmscan.c-516-\t * workqueues. They may be required for reclaim to make\n--\nmm/vmscan.c-518-\t */\nmm/vmscan.c:519:\tif (!current_is_kswapd() \u0026\u0026\nmm/vmscan.c-520-\t current-\u003eflags \u0026 (PF_USER_WORKER|PF_KTHREAD)) {\n--\nmm/vmscan.c=962=static struct folio *alloc_demote_folio(struct folio *src,\n--\nmm/vmscan.c-971-\t * make sure we allocate from the target node first also trying to\nmm/vmscan.c:972:\t * demote or reclaim pages from the target node via kswapd if we are\nmm/vmscan.c-973-\t * low on free memory on target node. If we don't do this and if\n--\nmm/vmscan.c=1055=static unsigned int shrink_folio_list(struct list_head *folio_list,\n--\nmm/vmscan.c-1118-\t\t * The number of dirty pages determines if a node is marked\nmm/vmscan.c:1119:\t\t * reclaim_congested. kswapd will stall and start writing\nmm/vmscan.c-1120-\t\t * folios if the tail of the LRU is all dirty unqueued folios.\n--\nmm/vmscan.c-1187-\t\t\t/* Case 1 above */\nmm/vmscan.c:1188:\t\t\tif (current_is_kswapd() \u0026\u0026\nmm/vmscan.c-1189-\t\t\t folio_test_reclaim(folio) \u0026\u0026\n--\nmm/vmscan.c=1590=unsigned int reclaim_clean_pages_from_list(struct zone *zone,\n--\nmm/vmscan.c-1615-\t * We should be safe here since we are only dealing with file pages and\nmm/vmscan.c:1616:\t * we are not kswapd and therefore cannot write dirty file pages. But\nmm/vmscan.c-1617-\t * call memalloc_noreclaim_save() anyway, just in case these conditions\n--\nmm/vmscan.c=1823=static bool too_many_isolated(struct pglist_data *pgdat, int file,\n--\nmm/vmscan.c-1828-\nmm/vmscan.c:1829:\tif (current_is_kswapd())\nmm/vmscan.c-1830-\t\treturn false;\n--\nmm/vmscan.c=3849=static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat, bool force_alloc)\n--\nmm/vmscan.c-3852-\nmm/vmscan.c:3853:\tif (pgdat \u0026\u0026 current_is_kswapd()) {\nmm/vmscan.c-3854-\t\tVM_WARN_ON_ONCE(walk);\n--\nmm/vmscan.c-3857-\t} else if (!walk \u0026\u0026 force_alloc) {\nmm/vmscan.c:3858:\t\tVM_WARN_ON_ONCE(current_is_kswapd());\nmm/vmscan.c-3859-\n--\nmm/vmscan.c=3869=static void clear_mm_walk(void)\n--\nmm/vmscan.c-3877-\nmm/vmscan.c:3878:\tif (!current_is_kswapd())\nmm/vmscan.c-3879-\t\tkfree(walk);\n--\nmm/vmscan.c=4191=static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-4196-\nmm/vmscan.c:4197:\tVM_WARN_ON_ONCE(!current_is_kswapd());\nmm/vmscan.c-4198-\n--\nmm/vmscan.c=4989=static bool should_abort_scan(struct lruvec *lruvec, struct scan_control *sc)\n--\nmm/vmscan.c-4997-\t/* check the order to exclude compaction-induced reclaim */\nmm/vmscan.c:4998:\tif (!current_is_kswapd() || sc-\u003eorder)\nmm/vmscan.c-4999-\t\treturn false;\n--\nmm/vmscan.c-5011-\nmm/vmscan.c:5012:\t/* kswapd should abort if all eligible zones are safe */\nmm/vmscan.c-5013-\treturn true;\n--\nmm/vmscan.c=5198=static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-5220-\nmm/vmscan.c:5221:\tif (current_is_kswapd())\nmm/vmscan.c-5222-\t\tsc-\u003enr_reclaimed = 0;\n--\nmm/vmscan.c-5228-\nmm/vmscan.c:5229:\tif (current_is_kswapd())\nmm/vmscan.c-5230-\t\tsc-\u003enr_reclaimed += reclaimed;\n--\nmm/vmscan.c-5236-\tif (sc-\u003enr_reclaimed \u003e reclaimed)\nmm/vmscan.c:5237:\t\tkswapd_try_clear_hopeless(pgdat, sc-\u003eorder, sc-\u003ereclaim_idx);\nmm/vmscan.c-5238-}\n--\nmm/vmscan.c=5938=static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)\n--\nmm/vmscan.c-5967-\t * DEF_PRIORITY on the assumption that the fact we are direct\nmm/vmscan.c:5968:\t * reclaiming implies that kswapd is not keeping up and it is best to\nmm/vmscan.c-5969-\t * do a batch of work at once. For memcg reclaim one check is made to\n--\nmm/vmscan.c-5972-\t */\nmm/vmscan.c:5973:\tproportional_reclaim = (!cgroup_reclaim(sc) \u0026\u0026 !current_is_kswapd() \u0026\u0026\nmm/vmscan.c-5974-\t\t\t\tsc-\u003epriority == DEF_PRIORITY);\n--\nmm/vmscan.c-5997-\t\t/*\nmm/vmscan.c:5998:\t\t * For kswapd and memcg, reclaim at least the number of pages\nmm/vmscan.c-5999-\t\t * requested. Ensure that the anon and file LRUs are scanned\n--\nmm/vmscan.c=6129=static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-6143-\t *\nmm/vmscan.c:6144:\t * For kswapd, reliable forward progress is more important\nmm/vmscan.c-6145-\t * than a quick return to idle. Always do full walks.\nmm/vmscan.c-6146-\t */\nmm/vmscan.c:6147:\tif (current_is_kswapd() || sc-\u003ememcg_full_walk)\nmm/vmscan.c-6148-\t\tpartial = NULL;\n--\nmm/vmscan.c=6208=static void shrink_node(pg_data_t *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-6246-\nmm/vmscan.c:6247:\tif (current_is_kswapd()) {\nmm/vmscan.c-6248-\t\t/*\n--\nmm/vmscan.c-6259-\t\t *\nmm/vmscan.c:6260:\t\t * Once a node is flagged PGDAT_WRITEBACK, kswapd will\nmm/vmscan.c-6261-\t\t * count the number of pages under pages flagged for\n--\nmm/vmscan.c-6268-\t\t/*\nmm/vmscan.c:6269:\t\t * If kswapd scans pages marked for immediate\nmm/vmscan.c-6270-\t\t * reclaim and under writeback (nr_immediate), it\n--\nmm/vmscan.c-6289-\nmm/vmscan.c:6290:\t\tif (current_is_kswapd())\nmm/vmscan.c-6291-\t\t\tset_bit(LRUVEC_NODE_CONGESTED, \u0026target_lruvec-\u003eflags);\n--\nmm/vmscan.c-6295-\t * Stall direct reclaim for IO completions if the lruvec is\nmm/vmscan.c:6296:\t * node is congested. Allow kswapd to continue until it\nmm/vmscan.c-6297-\t * starts encountering unqueued dirty pages or cycling through\n--\nmm/vmscan.c-6299-\t */\nmm/vmscan.c:6300:\tif (!current_is_kswapd() \u0026\u0026 current_may_throttle() \u0026\u0026\nmm/vmscan.c-6301-\t !sc-\u003ehibernation_mode \u0026\u0026\n--\nmm/vmscan.c-6312-\t * sleep. On reclaim progress, reset the failure counter. A\nmm/vmscan.c:6313:\t * successful direct reclaim run will revive a dormant kswapd.\nmm/vmscan.c-6314-\t */\nmm/vmscan.c-6315-\tif (reclaimable)\nmm/vmscan.c:6316:\t\tkswapd_try_clear_hopeless(pgdat, sc-\u003eorder, sc-\u003ereclaim_idx);\nmm/vmscan.c-6317-\telse if (sc-\u003ecache_trim_mode)\n--\nmm/vmscan.c=6355=static void consider_reclaim_throttle(pg_data_t *pgdat, struct scan_control *sc)\n--\nmm/vmscan.c-6371-\t/*\nmm/vmscan.c:6372:\t * Do not throttle kswapd or cgroup reclaim on NOPROGRESS as it will\nmm/vmscan.c-6373-\t * throttle on VMSCAN_THROTTLE_WRITEBACK if there are too many pages\n--\nmm/vmscan.c-6376-\t */\nmm/vmscan.c:6377:\tif (current_is_kswapd() || cgroup_reclaim(sc))\nmm/vmscan.c-6378-\t\treturn;\n--\nmm/vmscan.c=6613=static bool allow_direct_reclaim(pg_data_t *pgdat)\n--\nmm/vmscan.c-6620-\nmm/vmscan.c:6621:\tif (kswapd_test_hopeless(pgdat))\nmm/vmscan.c-6622-\t\treturn true;\n--\nmm/vmscan.c-6637-\nmm/vmscan.c:6638:\t/* kswapd must be awake if processes are being throttled */\nmm/vmscan.c:6639:\tif (!wmark_ok \u0026\u0026 waitqueue_active(\u0026pgdat-\u003ekswapd_wait)) {\nmm/vmscan.c:6640:\t\tif (READ_ONCE(pgdat-\u003ekswapd_highest_zoneidx) \u003e ZONE_NORMAL)\nmm/vmscan.c:6641:\t\t\tWRITE_ONCE(pgdat-\u003ekswapd_highest_zoneidx, ZONE_NORMAL);\nmm/vmscan.c-6642-\nmm/vmscan.c:6643:\t\twake_up_interruptible(\u0026pgdat-\u003ekswapd_wait);\nmm/vmscan.c-6644-\t}\n--\nmm/vmscan.c-6651- * and the PFMEMALLOC reserve for the preferred node is getting dangerously\nmm/vmscan.c:6652: * depleted. kswapd will continue to make progress and wake the processes\nmm/vmscan.c-6653- * when the low watermark is reached.\n--\nmm/vmscan.c=6658=static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist,\n--\nmm/vmscan.c-6688-\t * Throttling is based on the first usable node and throttled processes\nmm/vmscan.c:6689:\t * wait on a queue until kswapd makes progress and wakes them. There\nmm/vmscan.c-6690-\t * is an affinity then between processes waking up and where reclaim\n--\nmm/vmscan.c-6718-\t * transaction in the case of a filesystem like ext[3|4]. In this case,\nmm/vmscan.c:6719:\t * it is not safe to block on pfmemalloc_wait as kswapd could be\nmm/vmscan.c-6720-\t * blocked waiting on the same lock. Instead, throttle for up to a\n--\nmm/vmscan.c-6726-\telse\nmm/vmscan.c:6727:\t\t/* Throttle until kswapd wakes the process */\nmm/vmscan.c-6728-\t\twait_event_killable(zone-\u003ezone_pgdat-\u003epfmemalloc_wait,\n--\nmm/vmscan.c=6865=unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,\n--\nmm/vmscan.c-6874-\nmm/vmscan.c:6875:static void kswapd_age_node(struct pglist_data *pgdat, struct scan_control *sc)\nmm/vmscan.c-6876-{\n--\nmm/vmscan.c=6932=static bool pgdat_balanced(pg_data_t *pgdat, int order, int highest_zoneidx)\n--\nmm/vmscan.c-6954-\t\t *\nmm/vmscan.c:6955:\t\t * However, kswapd usually cannot accomplish this on\nmm/vmscan.c-6956-\t\t * its own and needs kcompactd support. Once it's\nmm/vmscan.c:6957:\t\t * reclaimed a compaction gap, and kswapd_shrink_node\nmm/vmscan.c-6958-\t\t * has dropped order, simply ensure there are enough\n--\nmm/vmscan.c-6989-\t * need balancing by definition. This can happen if a zone-restricted\nmm/vmscan.c:6990:\t * allocation tries to wake a remote kswapd.\nmm/vmscan.c-6991-\t */\n--\nmm/vmscan.c=6999=static void clear_pgdat_congested(pg_data_t *pgdat)\n--\nmm/vmscan.c-7008-/*\nmm/vmscan.c:7009: * Prepare kswapd for sleeping. This verifies that there are no processes\nmm/vmscan.c-7010- * waiting in throttle_direct_reclaim() and that watermarks have been met.\nmm/vmscan.c-7011- *\nmm/vmscan.c:7012: * Returns true if kswapd is ready to sleep\nmm/vmscan.c-7013- */\nmm/vmscan.c:7014:static bool prepare_kswapd_sleep(pg_data_t *pgdat, int order,\nmm/vmscan.c-7015-\t\t\t\tint highest_zoneidx)\n--\nmm/vmscan.c-7019-\t * soon as allow_direct_reclaim() is true. But there is a potential\nmm/vmscan.c:7020:\t * race between when kswapd checks the watermarks and a process gets\nmm/vmscan.c-7021-\t * throttled. There is also a potential race if processes get\nmm/vmscan.c:7022:\t * throttled, kswapd wakes, a large process exits thereby balancing the\nmm/vmscan.c:7023:\t * zones, which causes kswapd to exit balance_pgdat() before reaching\nmm/vmscan.c:7024:\t * the wake up checks. If kswapd is going to sleep, no process should\nmm/vmscan.c-7025-\t * be sleeping on pfmemalloc_wait, so wake them now if necessary. If\nmm/vmscan.c:7026:\t * the wake up is premature, processes will wake kswapd and get\nmm/vmscan.c-7027-\t * throttled again. The difference from wake ups in balance_pgdat() is\n--\nmm/vmscan.c-7033-\t/* Hopeless node, leave it to direct reclaim */\nmm/vmscan.c:7034:\tif (kswapd_test_hopeless(pgdat))\nmm/vmscan.c-7035-\t\treturn true;\n--\nmm/vmscan.c-7045-/*\nmm/vmscan.c:7046: * kswapd shrinks a node of pages that are at or below the highest usable\nmm/vmscan.c-7047- * zone that is currently unbalanced.\nmm/vmscan.c-7048- *\nmm/vmscan.c:7049: * Returns true if kswapd scanned at least the requested number of pages to\nmm/vmscan.c-7050- * reclaim or if the lack of progress was due to pages under writeback.\n--\nmm/vmscan.c-7052- */\nmm/vmscan.c:7053:static bool kswapd_shrink_node(pg_data_t *pgdat,\nmm/vmscan.c-7054-\t\t\t struct scan_control *sc)\n--\nmm/vmscan.c=7108=clear_reclaim_active(pg_data_t *pgdat, int highest_zoneidx)\n--\nmm/vmscan.c-7113-/*\nmm/vmscan.c:7114: * For kswapd, balance_pgdat() will reclaim pages across a node from zones\nmm/vmscan.c-7115- * that are eligible for use by the caller until at least one zone is\n--\nmm/vmscan.c-7117- *\nmm/vmscan.c:7118: * Returns the order kswapd finished reclaiming at.\nmm/vmscan.c-7119- *\nmm/vmscan.c:7120: * kswapd scans the zones in the highmem-\u003enormal-\u003edma direction. It skips\nmm/vmscan.c-7121- * zones which have free_pages \u003e high_wmark_pages(zone), but once a zone is\n--\nmm/vmscan.c=7126=static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx)\n--\nmm/vmscan.c-7152-\t * place so that parallel allocations that are near the watermark will\nmm/vmscan.c:7153:\t * stall or direct reclaim until kswapd is finished.\nmm/vmscan.c-7154-\t */\n--\nmm/vmscan.c-7181-\t\t * go ahead if all eligible zones for the original allocation\nmm/vmscan.c:7182:\t\t * request are balanced to avoid excessive reclaim from kswapd.\nmm/vmscan.c-7183-\t\t */\n--\nmm/vmscan.c-7199-\t\t * on the grounds that the normal reclaim should be enough to\nmm/vmscan.c:7200:\t\t * re-evaluate if boosting is required when kswapd next wakes.\nmm/vmscan.c-7201-\t\t */\n--\nmm/vmscan.c-7233-\t\t */\nmm/vmscan.c:7234:\t\tkswapd_age_node(pgdat, \u0026sc);\nmm/vmscan.c-7235-\n--\nmm/vmscan.c-7247-\t\t */\nmm/vmscan.c:7248:\t\tif (kswapd_shrink_node(pgdat, \u0026sc))\nmm/vmscan.c-7249-\t\t\traise_priority = false;\n--\nmm/vmscan.c-7259-\nmm/vmscan.c:7260:\t\t/* Check if kswapd should be suspending */\nmm/vmscan.c-7261-\t\t__fs_reclaim_release(_THIS_IP_);\n--\nmm/vmscan.c-7298-\t * watermark_high at this point. We need to avoid increasing the\nmm/vmscan.c:7299:\t * failure count to prevent the kswapd thread from stopping.\nmm/vmscan.c-7300-\t */\nmm/vmscan.c-7301-\tif (!sc.nr_reclaimed \u0026\u0026 !boosted) {\nmm/vmscan.c:7302:\t\tint fail_cnt = atomic_inc_return(\u0026pgdat-\u003ekswapd_failures);\nmm/vmscan.c:7303:\t\t/* kswapd context, low overhead to trace every failure */\nmm/vmscan.c:7304:\t\ttrace_mm_vmscan_kswapd_reclaim_fail(pgdat-\u003enode_id, fail_cnt);\nmm/vmscan.c-7305-\t}\n--\nmm/vmscan.c-7340-\t/*\nmm/vmscan.c:7341:\t * Return the order kswapd stopped reclaiming at as\nmm/vmscan.c:7342:\t * prepare_kswapd_sleep() takes it into account. If another caller\nmm/vmscan.c:7343:\t * entered the allocator slow path while kswapd was awake, order will\nmm/vmscan.c-7344-\t * remain at the higher level.\n--\nmm/vmscan.c-7349-/*\nmm/vmscan.c:7350: * The pgdat-\u003ekswapd_highest_zoneidx is used to pass the highest zone index to\nmm/vmscan.c:7351: * be reclaimed by kswapd from the waker. If the value is MAX_NR_ZONES which is\nmm/vmscan.c:7352: * not a valid index then either kswapd runs for first time or kswapd couldn't\nmm/vmscan.c-7353- * sleep after previous reclaim attempt (node is still unbalanced). In that\nmm/vmscan.c:7354: * case return the zone index of the previous kswapd reclaim cycle.\nmm/vmscan.c-7355- */\nmm/vmscan.c:7356:static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat,\nmm/vmscan.c-7357-\t\t\t\t\t enum zone_type prev_highest_zoneidx)\nmm/vmscan.c-7358-{\nmm/vmscan.c:7359:\tenum zone_type curr_idx = READ_ONCE(pgdat-\u003ekswapd_highest_zoneidx);\nmm/vmscan.c-7360-\n--\nmm/vmscan.c-7363-\nmm/vmscan.c:7364:static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order,\nmm/vmscan.c-7365-\t\t\t\tunsigned int highest_zoneidx)\n--\nmm/vmscan.c-7372-\nmm/vmscan.c:7373:\tprepare_to_wait(\u0026pgdat-\u003ekswapd_wait, \u0026wait, TASK_INTERRUPTIBLE);\nmm/vmscan.c-7374-\n--\nmm/vmscan.c-7381-\t */\nmm/vmscan.c:7382:\tif (prepare_kswapd_sleep(pgdat, reclaim_order, highest_zoneidx)) {\nmm/vmscan.c-7383-\t\t/*\n--\nmm/vmscan.c-7385-\t\t * isolate pages from and skips them in the future scanning.\nmm/vmscan.c:7386:\t\t * When kswapd is going to sleep, it is reasonable to assume\nmm/vmscan.c-7387-\t\t * that pages and compaction may succeed so reset the cache.\n--\nmm/vmscan.c-7399-\t\t/*\nmm/vmscan.c:7400:\t\t * If woken prematurely then reset kswapd_highest_zoneidx and\nmm/vmscan.c-7401-\t\t * order. The values will either be from a wakeup request or\n--\nmm/vmscan.c-7404-\t\tif (remaining) {\nmm/vmscan.c:7405:\t\t\tWRITE_ONCE(pgdat-\u003ekswapd_highest_zoneidx,\nmm/vmscan.c:7406:\t\t\t\t\tkswapd_highest_zoneidx(pgdat,\nmm/vmscan.c-7407-\t\t\t\t\t\t\thighest_zoneidx));\nmm/vmscan.c-7408-\nmm/vmscan.c:7409:\t\t\tif (READ_ONCE(pgdat-\u003ekswapd_order) \u003c reclaim_order)\nmm/vmscan.c:7410:\t\t\t\tWRITE_ONCE(pgdat-\u003ekswapd_order, reclaim_order);\nmm/vmscan.c-7411-\t\t}\nmm/vmscan.c-7412-\nmm/vmscan.c:7413:\t\tfinish_wait(\u0026pgdat-\u003ekswapd_wait, \u0026wait);\nmm/vmscan.c:7414:\t\tprepare_to_wait(\u0026pgdat-\u003ekswapd_wait, \u0026wait, TASK_INTERRUPTIBLE);\nmm/vmscan.c-7415-\t}\n--\nmm/vmscan.c-7421-\tif (!remaining \u0026\u0026\nmm/vmscan.c:7422:\t prepare_kswapd_sleep(pgdat, reclaim_order, highest_zoneidx)) {\nmm/vmscan.c:7423:\t\ttrace_mm_vmscan_kswapd_sleep(pgdat-\u003enode_id);\nmm/vmscan.c-7424-\n--\nmm/vmscan.c-7429-\t\t * watermarks being breached while under pressure, we reduce the\nmm/vmscan.c:7430:\t\t * per-cpu vmstat threshold while kswapd is awake and restore\nmm/vmscan.c-7431-\t\t * them before going back to sleep.\n--\nmm/vmscan.c-7444-\t}\nmm/vmscan.c:7445:\tfinish_wait(\u0026pgdat-\u003ekswapd_wait, \u0026wait);\nmm/vmscan.c-7446-}\n--\nmm/vmscan.c-7460- */\nmm/vmscan.c:7461:static int kswapd(void *p)\nmm/vmscan.c-7462-{\n--\nmm/vmscan.c-7470-\t * and that if we need more memory we should get access to it\nmm/vmscan.c:7471:\t * regardless (see \"__alloc_pages()\"). \"kswapd\" should\nmm/vmscan.c-7472-\t * never get caught in the normal page freeing logic.\n--\nmm/vmscan.c-7482-\nmm/vmscan.c:7483:\tWRITE_ONCE(pgdat-\u003ekswapd_order, 0);\nmm/vmscan.c:7484:\tWRITE_ONCE(pgdat-\u003ekswapd_highest_zoneidx, MAX_NR_ZONES);\nmm/vmscan.c-7485-\tatomic_set(\u0026pgdat-\u003enr_writeback_throttled, 0);\n--\nmm/vmscan.c-7488-\nmm/vmscan.c:7489:\t\talloc_order = reclaim_order = READ_ONCE(pgdat-\u003ekswapd_order);\nmm/vmscan.c:7490:\t\thighest_zoneidx = kswapd_highest_zoneidx(pgdat,\nmm/vmscan.c-7491-\t\t\t\t\t\t\thighest_zoneidx);\nmm/vmscan.c-7492-\nmm/vmscan.c:7493:kswapd_try_sleep:\nmm/vmscan.c:7494:\t\tkswapd_try_to_sleep(pgdat, alloc_order, reclaim_order,\nmm/vmscan.c-7495-\t\t\t\t\thighest_zoneidx);\n--\nmm/vmscan.c-7497-\t\t/* Read the new order and highest_zoneidx */\nmm/vmscan.c:7498:\t\talloc_order = READ_ONCE(pgdat-\u003ekswapd_order);\nmm/vmscan.c:7499:\t\thighest_zoneidx = kswapd_highest_zoneidx(pgdat,\nmm/vmscan.c-7500-\t\t\t\t\t\t\thighest_zoneidx);\nmm/vmscan.c:7501:\t\tWRITE_ONCE(pgdat-\u003ekswapd_order, 0);\nmm/vmscan.c:7502:\t\tWRITE_ONCE(pgdat-\u003ekswapd_highest_zoneidx, MAX_NR_ZONES);\nmm/vmscan.c-7503-\n--\nmm/vmscan.c-7515-\t\t * Reclaim begins at the requested order but if a high-order\nmm/vmscan.c:7516:\t\t * reclaim fails then kswapd falls back to reclaiming for\nmm/vmscan.c:7517:\t\t * order-0. If that happens, kswapd will consider sleeping\nmm/vmscan.c-7518-\t\t * for the order it finished reclaiming at (reclaim_order)\n--\nmm/vmscan.c-7521-\t\t */\nmm/vmscan.c:7522:\t\ttrace_mm_vmscan_kswapd_wake(pgdat-\u003enode_id, highest_zoneidx,\nmm/vmscan.c-7523-\t\t\t\t\t\talloc_order);\n--\nmm/vmscan.c-7526-\t\tif (reclaim_order \u003c alloc_order)\nmm/vmscan.c:7527:\t\t\tgoto kswapd_try_sleep;\nmm/vmscan.c-7528-\t}\n--\nmm/vmscan.c-7536- * A zone is low on free memory or too fragmented for high-order memory. If\nmm/vmscan.c:7537: * kswapd should reclaim (direct reclaim is deferred), wake it up for the zone's\nmm/vmscan.c:7538: * pgdat. It will wake up kcompactd after reclaiming memory. If kswapd reclaim\nmm/vmscan.c-7539- * has failed or is not needed, still wake up kcompactd if only compaction is\n--\nmm/vmscan.c-7541- */\nmm/vmscan.c:7542:void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order,\nmm/vmscan.c-7543-\t\t enum zone_type highest_zoneidx)\n--\nmm/vmscan.c-7554-\tpgdat = zone-\u003ezone_pgdat;\nmm/vmscan.c:7555:\tcurr_idx = READ_ONCE(pgdat-\u003ekswapd_highest_zoneidx);\nmm/vmscan.c-7556-\nmm/vmscan.c-7557-\tif (curr_idx == MAX_NR_ZONES || curr_idx \u003c highest_zoneidx)\nmm/vmscan.c:7558:\t\tWRITE_ONCE(pgdat-\u003ekswapd_highest_zoneidx, highest_zoneidx);\nmm/vmscan.c-7559-\nmm/vmscan.c:7560:\tif (READ_ONCE(pgdat-\u003ekswapd_order) \u003c order)\nmm/vmscan.c:7561:\t\tWRITE_ONCE(pgdat-\u003ekswapd_order, order);\nmm/vmscan.c-7562-\nmm/vmscan.c:7563:\tif (!waitqueue_active(\u0026pgdat-\u003ekswapd_wait))\nmm/vmscan.c-7564-\t\treturn;\n--\nmm/vmscan.c-7566-\t/* Hopeless node, leave it to direct reclaim if possible */\nmm/vmscan.c:7567:\tif (kswapd_test_hopeless(pgdat) ||\nmm/vmscan.c-7568-\t (pgdat_balanced(pgdat, order, highest_zoneidx) \u0026\u0026\n--\nmm/vmscan.c-7581-\nmm/vmscan.c:7582:\ttrace_mm_vmscan_wakeup_kswapd(pgdat-\u003enode_id, highest_zoneidx, order,\nmm/vmscan.c-7583-\t\t\t\t gfp_flags);\nmm/vmscan.c:7584:\twake_up_interruptible(\u0026pgdat-\u003ekswapd_wait);\nmm/vmscan.c-7585-}\nmm/vmscan.c-7586-\nmm/vmscan.c:7587:void kswapd_clear_hopeless(pg_data_t *pgdat, enum kswapd_clear_hopeless_reason reason)\nmm/vmscan.c-7588-{\nmm/vmscan.c-7589-\t/* Only trace actual resets, not redundant zero-to-zero */\nmm/vmscan.c:7590:\tif (atomic_xchg(\u0026pgdat-\u003ekswapd_failures, 0))\nmm/vmscan.c:7591:\t\ttrace_mm_vmscan_kswapd_clear_hopeless(pgdat-\u003enode_id, reason);\nmm/vmscan.c-7592-}\n--\nmm/vmscan.c-7594-/*\nmm/vmscan.c:7595: * Reset kswapd_failures only when the node is balanced. Without this\nmm/vmscan.c-7596- * check, successful direct reclaim (e.g., from cgroup memory.high\nmm/vmscan.c:7597: * throttling) can keep resetting kswapd_failures even when the node\nmm/vmscan.c:7598: * cannot be balanced, causing kswapd to run endlessly.\nmm/vmscan.c-7599- */\nmm/vmscan.c:7600:void kswapd_try_clear_hopeless(struct pglist_data *pgdat,\nmm/vmscan.c-7601-\t\t\t unsigned int order, int highest_zoneidx)\n--\nmm/vmscan.c-7603-\tif (pgdat_balanced(pgdat, order, highest_zoneidx))\nmm/vmscan.c:7604:\t\tkswapd_clear_hopeless(pgdat, current_is_kswapd() ?\nmm/vmscan.c-7605-\t\t\tKSWAPD_CLEAR_HOPELESS_KSWAPD : KSWAPD_CLEAR_HOPELESS_DIRECT);\n--\nmm/vmscan.c-7607-\nmm/vmscan.c:7608:bool kswapd_test_hopeless(pg_data_t *pgdat)\nmm/vmscan.c-7609-{\nmm/vmscan.c:7610:\treturn atomic_read(\u0026pgdat-\u003ekswapd_failures) \u003e= MAX_RECLAIM_RETRIES;\nmm/vmscan.c-7611-}\n--\nmm/vmscan.c=7622=unsigned long shrink_all_memory(unsigned long nr_to_reclaim)\n--\nmm/vmscan.c-7652-/*\nmm/vmscan.c:7653: * This kswapd start function will be called by init and node-hot-add.\nmm/vmscan.c-7654- */\nmm/vmscan.c:7655:void __meminit kswapd_run(int nid)\nmm/vmscan.c-7656-{\n--\nmm/vmscan.c-7658-\nmm/vmscan.c:7659:\tpgdat_kswapd_lock(pgdat);\nmm/vmscan.c:7660:\tif (!pgdat-\u003ekswapd) {\nmm/vmscan.c:7661:\t\tpgdat-\u003ekswapd = kthread_create_on_node(kswapd, pgdat, nid, \"kswapd%d\", nid);\nmm/vmscan.c:7662:\t\tif (IS_ERR(pgdat-\u003ekswapd)) {\nmm/vmscan.c-7663-\t\t\t/* failure at boot is fatal */\nmm/vmscan.c:7664:\t\t\tpr_err(\"Failed to start kswapd on node %d, ret=%pe\\n\",\nmm/vmscan.c:7665:\t\t\t\t nid, pgdat-\u003ekswapd);\nmm/vmscan.c-7666-\t\t\tBUG_ON(system_state \u003c SYSTEM_RUNNING);\nmm/vmscan.c:7667:\t\t\tpgdat-\u003ekswapd = NULL;\nmm/vmscan.c-7668-\t\t} else {\nmm/vmscan.c:7669:\t\t\twake_up_process(pgdat-\u003ekswapd);\nmm/vmscan.c-7670-\t\t}\nmm/vmscan.c-7671-\t}\nmm/vmscan.c:7672:\tpgdat_kswapd_unlock(pgdat);\nmm/vmscan.c-7673-}\n--\nmm/vmscan.c-7678- */\nmm/vmscan.c:7679:void __meminit kswapd_stop(int nid)\nmm/vmscan.c-7680-{\nmm/vmscan.c-7681-\tpg_data_t *pgdat = NODE_DATA(nid);\n\n"
} |
| 11/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23298 output: 37 thoughts: 169 Thoughts: **My Initial Thoughts on Page Reclaim** Okay, so I'm diving into this page reclaim business. My first thought is to zero in on `wakeup_kswapd` – it's a kthread, so that's a natural starting point. But, based on the function names I know, `try_to_free_pages` and `try_to_free_mem_cgroup_pages` seem to be the primary entry points for direct reclaim, right? Let's verify that quickly, are there any other functions that get us into the reclaim process? Hmm, I see `shrink_node` is the core workhorse. That makes sense. It looks like `shrink_lruvec` is called by `shrink_node`, which is then calling `evict_folios`. I should look into this whole call chain to understand the whole process. And I see `prepare_scan_control` called by `shrink_node` as well. Now, let's go back and examine `try_to_free_pages` and `try_to_free_mem_cgroup_pages` more closely. I should first check if these functions are exported. If not, then I will have to look at the surrounding code to determine how to call them. |
| 12/2 | 2026/07/28 08:49 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*try_to_free_pages",
"PathPrefix": "mm/vmscan.c"
}Results: {
"error": "no matches"
} |
| 13/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23790 output: 33 thoughts: 22 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Diving into `try_to_free_pages`** Okay, time to get my hands dirty and dive into `mm/vmscan.c`. Specifically, I want to take a look at the `try_to_free_pages` function. I know this is where the core of the page reclaim mechanism kicks in. It's the point where the kernel tries to physically free up memory, the rubber meets the road, so to speak. I need to refresh my memory of how this function interacts with the LRU lists, zones, and reclaim targets. What are the key heuristics and thresholds it uses? Are there any obvious bottlenecks or potential areas for optimization that jump out? Specifically, I'm thinking about the interactions with NUMA nodes and how it handles different page types. Any recent changes to memory pressure calculations would be worth reviewing here as well. Let's see what the code has to tell us. |
| 14/2 | 2026/07/28 08:49 | tool | grepper |
0mArgs:{
"Expression": "try_to_free_pages",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=6485=static void snapshot_refaults(struct mem_cgroup *target_memcg, pg_data_t *pgdat)\n--\nmm/vmscan.c-6515- */\nmm/vmscan.c:6516:static unsigned long do_try_to_free_pages(struct zonelist *zonelist,\nmm/vmscan.c-6517-\t\t\t\t\t struct scan_control *sc)\n--\nmm/vmscan.c=6658=static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist,\n--\nmm/vmscan.c-6737-\nmm/vmscan.c:6738:unsigned long try_to_free_pages(struct zonelist *zonelist, int order,\nmm/vmscan.c-6739-\t\t\t\tgfp_t gfp_mask, nodemask_t *nodemask)\n--\nmm/vmscan.c-6772-\nmm/vmscan.c:6773:\tnr_reclaimed = do_try_to_free_pages(zonelist, \u0026sc);\nmm/vmscan.c-6774-\n--\nmm/vmscan.c=6824=unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,\n--\nmm/vmscan.c-6855-\nmm/vmscan.c:6856:\tnr_reclaimed = do_try_to_free_pages(zonelist, \u0026sc);\nmm/vmscan.c-6857-\n--\nmm/vmscan.c=7622=unsigned long shrink_all_memory(unsigned long nr_to_reclaim)\n--\nmm/vmscan.c-7641-\nmm/vmscan.c:7642:\tnr_reclaimed = do_try_to_free_pages(zonelist, \u0026sc);\nmm/vmscan.c-7643-\n"
} |
| 15/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24557 output: 25 thoughts: 205 Thoughts: **Identifying Key Function Symbols and Relevant Configuration Dependencies** Okay, so I'm looking at this patch and trying to understand its scope. The non-static functions jump out immediately as good points of focus: `try_to_free_pages`, `try_to_free_mem_cgroup_pages`, `wakeup_kswapd`, and `shrink_all_memory`. These seem to be the primary entry points or control points this patch is touching. I need to understand how they interact, and what's changing *around* them, to fully grasp the implications. Now, let's see about the context. This patch touches a slew of core memory management files: `mm/vmscan.c`, `mm/swap.c`, `mm/workingset.c`, `mm/memcontrol.c`, `mm/mmzone.c`, and `mm/vmstat.c`. That's basically the whole shebang when it comes to the VM subsystem. It's a broad range and confirms a pretty significant change. Since it modifies `mm/memcontrol.c`, I need to immediately consider `CONFIG_MEMCG`. If this patch's changes depend on memory control groups, that's crucial. I can confirm, from experience, that `mm/memcontrol.c` is only compiled if `CONFIG_MEMCG` is enabled. Time to check `mm/Makefile` to confirm the details around how and where this config will come into play. |
| 16/2 | 2026/07/28 08:49 | tool | grepper |
0mArgs:{
"Expression": "memcontrol",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=27=KCOV_INSTRUMENT_kmemleak.o := n\nmm/Makefile:28:KCOV_INSTRUMENT_memcontrol.o := n\nmm/Makefile:29:KCOV_INSTRUMENT_memcontrol-v1.o := n\nmm/Makefile-30-KCOV_INSTRUMENT_mmzone.o := n\n--\nmm/Makefile=103=obj-$(CONFIG_LIVEUPDATE_MEMFD) += memfd_luo.o\nmm/Makefile:104:obj-$(CONFIG_MEMCG_V1) += memcontrol-v1.o\nmm/Makefile:105:obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o\nmm/Makefile-106-ifdef CONFIG_BPF_SYSCALL\nmm/Makefile:107:obj-$(CONFIG_MEMCG) += bpf_memcontrol.o\nmm/Makefile-108-endif\n"
} |
| 17/2 | 2026/07/28 08:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25339 output: 30 thoughts: 369 Thoughts: **Analyzing the Patch's Dependencies** Okay, so this patch touches `mm/memcontrol.c` and `mm/memcontrol-v1.c`, which immediately flags my radar as memcg-related. But, it also hits `mm/vmscan.c` and `mm/swap.c`, which are core, always-compiled components. My first instinct is to check for conditional compilation – are we introducing any new `#ifdef` blocks? I dove into `include/linux/mmzone.h`, checking the `enum node_stat_item` for any clues. Nope, no new `#ifdef`s there. Next, I jumped into `mm/vmscan.c` and specifically checked `prepare_scan_control()`, just to be certain. Again, no new `#ifdef`s. So, the core changes in `mm/vmscan.c` don't strictly *require* `CONFIG_MEMCG`. However, because `mm/memcontrol.c` and `mm/memcontrol-v1.c` are being modified, it's pretty clear that enabling both `MEMCG` and `MEMCG_V1` is the right move to ensure we fully cover the impact of this patch. Now, let's just make sure `MEMCG_V1` is actually defined... |
| 18/2 | 2026/07/28 08:50 | tool | grepper |
0mError:no matches Args: {
"Expression": "config MEMCG_V1",
"PathPrefix": "mm/Kconfig"
}Results: {
"error": "no matches"
} |
| 19/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26033 output: 23 thoughts: 25 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Kernel Configuration Hunting** Alright, so I need to find where the `MEMCG_V1` Kconfig option is defined. That's usually the first step when I'm dealing with these sorts of kernel features. My initial instinct is to check the most likely places for memory management related options. `init/Kconfig` is a good place to start, as it often handles core initialization and system-wide configurations. But, given that `MEMCG_V1` screams memory management, I'm leaning more towards `mm/Kconfig` right away. It's the more logical home for options directly related to memory control groups, and it's where I'd expect to find the dependencies and help text. I'll start with those two files, and go from there. |
| 20/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "config MEMCG",
"PathPrefix": ""
}Results: {
"Output": "init/Kconfig=1066=config CGROUP_FAVOR_DYNMODS\n--\ninit/Kconfig-1075-\ninit/Kconfig:1076:config MEMCG\ninit/Kconfig-1077-\tbool \"Memory controller\"\n--\ninit/Kconfig-1084-\ninit/Kconfig:1085:config MEMCG_NMI_UNSAFE\ninit/Kconfig-1086-\tbool\n--\ninit/Kconfig-1091-\ninit/Kconfig:1092:config MEMCG_NMI_SAFETY_REQUIRES_ATOMIC\ninit/Kconfig-1093-\tbool\n--\ninit/Kconfig-1098-\ninit/Kconfig:1099:config MEMCG_V1\ninit/Kconfig-1100-\tbool \"Legacy cgroup v1 memory controller\"\n"
} |
| 21/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26493 output: 33 thoughts: 205 Thoughts: **Analyzing Memory Management Entry Points** Okay, so I'm thinking about memory management, specifically how to best approach a deeper dive. Enabling `MEMCG` and `MEMCG_V1` seems like a solid foundation, I usually start there to be able to get good observability. I'm going to want to understand what's happening at key entry points for memory reclaim. I'm trying to identify areas where the workingset code interacts with the rest of the system. I'm particularly interested in looking at the functions: `try_to_free_pages`, `try_to_free_mem_cgroup_pages`, and `wakeup_kswapd`. I should probably also consider `shrink_all_memory`, as that's used for hibernation, though it may not be directly relevant to the specific workingset focus right now. Let me see... I see `folio_rotate_reclaimable` is mentioned in `mm/swap.c`, but it just appears to be context here, not something to focus on. I also see that `lru_note_cost_unlock_irq` and `lru_note_cost_refault` were removed. Interesting. I need to understand why and where that functionality has gone. Ah, here it is: `workingset_refault` has been modified to remove the old refault cost function. That function is called when a page is refaulted, which makes it a critical point to examine. So, *is* `workingset_refault` the correct symbol to focus on? Yep, absolutely. That seems like an ideal entry point for tracing the workingset code and seeing how it interacts with the rest of the memory management system. It's a key spot to understand how the system is behaving when memory is actively being used and re-used. |
| 22/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "workingset_refault",
"PathPrefix": "mm/workingset.c"
}Results: {
"Output": "mm/workingset.c=430=bool workingset_test_recent(void *shadow, bool file, bool *workingset,\n--\nmm/workingset.c-539-/**\nmm/workingset.c:540: * workingset_refault - Evaluate the refault of a previously evicted folio.\nmm/workingset.c-541- * @folio: The freshly allocated replacement folio.\n--\nmm/workingset.c-547- */\nmm/workingset.c:548:void workingset_refault(struct folio *folio, void *shadow)\nmm/workingset.c-549-{\n"
} |
| 23/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27293 output: 37 thoughts: 11 Thoughts: Okay, here's my summary, in the first person, as if these are my thoughts: **Reflecting on `workingset_refault`** Alright, let me think. `workingset_refault`... is that an exported function or variable? I need to know if it's meant to be accessed from outside its defining module or if it's an internal detail. If it's exported, that has implications for the overall design and, more specifically, the maintainability and potential for breaking changes in future revisions of the software. I need to understand the intent behind its exposure. Is it for performance profiling, debugging, a specific optimization strategy, or something else entirely? Knowing the reason is key to assessing its impact and any potential alternatives. This impacts how I should proceed with whatever task is at hand. |
| 24/2 | 2026/07/28 08:50 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*workingset_refault",
"PathPrefix": "mm/workingset.c"
}Results: {
"error": "no matches"
} |
| 25/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27528 output: 33 thoughts: 18 Thoughts: Okay, let's see. My first thought is: **I need to pinpoint where the relevant definitions for swap management are located in the Linux kernel source.** And given my experience, `include/linux/swap.h` seems like the most likely starting point. I'll check there first; that's where I'd expect to find the core data structures and function prototypes related to swap space, page allocation/deallocation in that context, and the fundamental mechanics of swapping pages to and from disk. It's a solid, logical place to begin the investigation. Let's start there. |
| 26/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "workingset_refault",
"PathPrefix": "include/linux/swap.h"
}Results: {
"Output": "include/linux/swap.h=300=void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg);\ninclude/linux/swap.h:301:void workingset_refault(struct folio *folio, void *shadow);\ninclude/linux/swap.h-302-void workingset_activation(struct folio *folio);\n"
} |
| 27/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27806 output: 35 thoughts: 185 Thoughts: **Analyzing the Dynamic Nature of This API Function** Okay, so this is definitely a non-static API function, which means it's designed to be called from various parts of the kernel, not just a localized module. That narrows down the potential focus symbols we need to consider. I'm thinking, based on context, we should look closely at: * `try_to_free_pages` * `try_to_free_mem_cgroup_pages` * `wakeup_kswapd` * `workingset_refault` Let's quickly verify my initial hunch. It seems `try_to_free_pages` is indeed the primary entry point for *direct* reclaim, making it a very strong candidate. `wakeup_kswapd` is the trigger for waking up the kswapd kernel thread, and `workingset_refault` is directly related to the workingset refault mechanism. Wait, are there any other functions that are applicable? Looking at the code, `lruvec_page_state_monotonic` is also a potential, but it looks like it's inline or exported? Oh, wait, it's actually in `mm/memcontrol.c`. This needs to be considered as a potential point for interaction. |
| 28/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "lruvec_page_state_monotonic",
"PathPrefix": "mm/memcontrol.c"
}Results: {
"Output": "mm/memcontrol.c=486=unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx)\n--\nmm/memcontrol.c-508-/**\nmm/memcontrol.c:509: * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling\nmm/memcontrol.c-510- * @lruvec: the LRU vector to read from\n--\nmm/memcontrol.c-526- */\nmm/memcontrol.c:527:unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec,\nmm/memcontrol.c-528-\t\t\t\t\t enum node_stat_item idx)\n"
} |
| 29/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28499 output: 30 thoughts: 209 Thoughts: Okay, let's break this down. **Analyzing the Memory Management Flow** This isn't a simple, direct read; it's a dynamic process tied to getters, with the real meat being in `prepare_scan_control` and `evict_folios`. Because they are static, that's where I need to focus my attention. It's the calling functions that are going to be key. `shrink_node` is calling `prepare_scan_control`, which is itself being called by a series of functions: `kswapd_shrink_node`, `shrink_zones`, `__node_reclaim`, and `mem_cgroup_shrink_node`. `shrink_zones` then calls `do_try_to_free_pages`, and this is triggered by `try_to_free_pages`, `try_to_free_mem_cgroup_pages`, and `shrink_all_memory`. Therefore, the places I want to investigate further are `try_to_free_pages` and `try_to_free_mem_cgroup_pages` as the points where this flow is kicked off. Now, let's shift focus to `evict_folios`. |
| 30/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "evict_folios",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=4825=static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n--\nmm/vmscan.c-4858-\nmm/vmscan.c:4859:static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\nmm/vmscan.c-4860-\t\t\tstruct scan_control *sc, int swappiness)\n--\nmm/vmscan.c=5021=static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)\n--\nmm/vmscan.c-5044-\t\tnr_batch = min(nr_to_scan, MIN_LRU_BATCH);\nmm/vmscan.c:5045:\t\tdelta = evict_folios(nr_batch, lruvec, sc, swappiness);\nmm/vmscan.c-5046-\t\tif (!delta)\n--\nmm/vmscan.c=5648=static int run_eviction(struct lruvec *lruvec, unsigned long seq, struct scan_control *sc,\n--\nmm/vmscan.c-5668-\t\tnr_batch = min(nr_to_reclaim - sc-\u003enr_reclaimed, MAX_LRU_BATCH);\nmm/vmscan.c:5669:\t\tif (!evict_folios(nr_batch, lruvec, sc, swappiness))\nmm/vmscan.c-5670-\t\t\treturn 0;\n"
} |
| 31/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29393 output: 30 thoughts: 79 Thoughts: **Delving into the MGLRU Eviction Mechanism** Okay, so `evict_folios`. I know that function is triggered by `try_to_shrink_lruvec` and `run_eviction`, and those are core components of `lru_gen`, the Multiple-Generational LRU (MGLRU) mechanism. But hang on, is `evict_folios` actually *part* of MGLRU? My memory's a bit fuzzy on that specific relationship. Time to verify. Let's go straight to the source. I need to check the `evict_folios` implementation itself. Specifically, I should look at `mm/vmscan.c` to clarify its exact role within the broader MGLRU framework and confirm how it interacts with the LRU generation management. It's important to have a precise understanding of the flow here. |
| 32/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "evict_folios",
"PathPrefix": "mm/vmscan.c"
}Results: {
"Output": "mm/vmscan.c=4825=static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\n--\nmm/vmscan.c-4858-\nmm/vmscan.c:4859:static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,\nmm/vmscan.c-4860-\t\t\tstruct scan_control *sc, int swappiness)\n--\nmm/vmscan.c=5021=static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)\n--\nmm/vmscan.c-5044-\t\tnr_batch = min(nr_to_scan, MIN_LRU_BATCH);\nmm/vmscan.c:5045:\t\tdelta = evict_folios(nr_batch, lruvec, sc, swappiness);\nmm/vmscan.c-5046-\t\tif (!delta)\n--\nmm/vmscan.c=5648=static int run_eviction(struct lruvec *lruvec, unsigned long seq, struct scan_control *sc,\n--\nmm/vmscan.c-5668-\t\tnr_batch = min(nr_to_reclaim - sc-\u003enr_reclaimed, MAX_LRU_BATCH);\nmm/vmscan.c:5669:\t\tif (!evict_folios(nr_batch, lruvec, sc, swappiness))\nmm/vmscan.c-5670-\t\t\treturn 0;\n"
} |
| 33/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30082 output: 29 thoughts: 154 Thoughts: **MGLRU Consideration for Testing** Okay, MGLRU. Right. Now, a key question pops up: is MGLRU actually enabled by default? I need to confirm that. Ah, `CONFIG_LRU_GEN` is the config option for MGLRU, so that's the one to check. Let's see if that specific configuration is a requirement in this scenario. The patch I'm looking at modifies `evict_folios`, and that is definitely part of the MGLRU codebase. Interesting, it *also* touches `shrink_inactive_list` and `shrink_active_list`. Those are part of the active/inactive LRU, so the legacy LRU. So, this patch clearly has implications for both the legacy LRU and MGLRU. Therefore, to thoroughly evaluate this patch, I think we *really* need to enable `CONFIG_LRU_GEN` during testing to ensure we're exercising the MGLRU path as well. I'll need to double check the `mm/Kconfig` file to be sure. Let's see what `LRU_GEN` brings to the table. |
| 34/2 | 2026/07/28 08:50 | tool | grepper |
0mArgs:{
"Expression": "config LRU_GEN",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=1406=endif # USERFAULTFD\n--\nmm/Kconfig-1408-# multi-gen LRU {\nmm/Kconfig:1409:config LRU_GEN\nmm/Kconfig-1410-\tbool \"Multi-Gen LRU\"\n--\nmm/Kconfig-1417-\nmm/Kconfig:1418:config LRU_GEN_ENABLED\nmm/Kconfig-1419-\tbool \"Enable by default\"\n--\nmm/Kconfig-1423-\nmm/Kconfig:1424:config LRU_GEN_STATS\nmm/Kconfig-1425-\tbool \"Full stats for debugging\"\n--\nmm/Kconfig-1432-\nmm/Kconfig:1433:config LRU_GEN_WALKS_MMU\nmm/Kconfig-1434-\tdef_bool y\n"
} |
| 35/2 | 2026/07/28 08:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30743 output: 151 thoughts: 938 Thoughts: **Analysis of the Memory Management Patch for Fuzzing** Okay, so I'm thinking about how to effectively fuzz this memory management patch. First, `LRU_GEN` and `LRU_GEN_ENABLED` look like good flags to enable – they're key to the new LRU implementation. Now, let's examine the code. `prepare_scan_control` is handling the legacy LRU, the patch modifies both the legacy LRU and the MGLRU. Specifically, the legacy LRU touches `prepare_scan_control`, `shrink_inactive_list`, and `shrink_active_list`, while MGLRU uses `evict_folios`. I should be testing both. We can just specify `LRU_GEN`, and the system will handle `LRU_GEN_ENABLED`. The initial plan is to enable `LRU_GEN` so that the feature is compiled in. `LRU_GEN_ENABLED` might be fine to enable too. It might be better to have legacy LRU tested more. But it's fine, syzkaller can toggle `LRU_GEN_ENABLED` if it knows how, or we can just provide `LRU_GEN` and let the default be whatever. If we don't specify `LRU_GEN_ENABLED`, it might be disabled by default, which tests legacy LRU. If we specify it, it tests MGLRU. So it's best to enable `LRU_GEN` in the config. Therefore, the configurations I'll use are: * `MEMCG` * `MEMCG_V1` * `LRU_GEN` I've already verified that `MEMCG_V1` is present in `init/Kconfig`. Now, let's review the critical symbols for fuzzing. I've got to cover the entry points and the changed functions, so I'm looking at: * `try_to_free_pages` * `try_to_free_mem_cgroup_pages` * `wakeup_kswapd` * `workingset_refault` Are there any other entry points for MGLRU? `lru_gen_shrink_node` is called by `shrink_node`. `try_to_free_pages` covers it. I need to make sure I get there. I'm looking at `pageout`. It's a static function called by `shrink_folio_list`. `shrink_folio_list` is called by `shrink_inactive_list` and `evict_folios`, so I think that `try_to_free_pages` will cover it. I also need to verify these. * `lruvec_page_state_monotonic` is called by `prepare_scan_control`. * `node_page_state_monotonic` is called by `lruvec_page_state_monotonic` when memcg is disabled. * `global_node_page_state_monotonic` is defined in `include/linux/vmstat.h`. The patch is a functional change to memory management, which I categorize as `WorthFuzzing: true`. I've determined the final list of Focus Symbols: * `try_to_free_pages` * `try_to_free_mem_cgroup_pages` * `wakeup_kswapd` * `workingset_refault` Final Configs: * `MEMCG` * `MEMCG_V1` * `LRU_GEN` I've double-checked that `MEMCG_V1` is correct. I've double-checked that `LRU_GEN` is correct. I've double-checked that `try_to_free_pages` is correct. I've double-checked that `try_to_free_mem_cgroup_pages` is correct. I've double-checked that `wakeup_kswapd` is correct. I've double-checked that `workingset_refault` is correct. All checks are green. Now I'll call `set-results`. |
| 36/2 | 2026/07/28 08:50 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MEMCG",
"MEMCG_V1",
"LRU_GEN"
],
"FocusSymbols": [
"try_to_free_pages",
"try_to_free_mem_cgroup_pages",
"wakeup_kswapd",
"workingset_refault"
],
"Reasoning": "The patch modifies the core memory management subsystem, specifically the LRU reclaim logic and cost calculation for both legacy LRU and MGLRU. It introduces monotonic counters for LRU stats and changes how scan balance is determined. These are functional changes to reachable core kernel code that should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MEMCG",
"MEMCG_V1",
"LRU_GEN"
],
"FocusSymbols": [
"try_to_free_pages",
"try_to_free_mem_cgroup_pages",
"wakeup_kswapd",
"workingset_refault"
],
"Reasoning": "The patch modifies the core memory management subsystem, specifically the LRU reclaim logic and cost calculation for both legacy LRU and MGLRU. It introduces monotonic counters for LRU stats and changes how scan balance is determined. These are functional changes to reachable core kernel code that should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|