| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/19 19:41 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"ASYNC_MM_TEARDOWN"
],
"FocusSymbols": [
"mm_reaper",
"async_mm_teardown_queue",
"async_mm_teardown_reserve",
"async_mm_teardown_eligible"
],
"Reasoning": "The patch introduces a new feature to asynchronously tear down the address space of exiting processes via a dedicated kernel thread (mm_reaper). This modifies core process exit and memory management logic, which is highly functional and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/19 19:41 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 11e5f51972471277c32b40ff94aabbd86a0461ea\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Jul 19 19:41:38 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst\nindex 5b318d17aa4b44..ccda6c75fe1fb7 100644\n--- a/Documentation/admin-guide/sysctl/vm.rst\n+++ b/Documentation/admin-guide/sysctl/vm.rst\n@@ -22,6 +22,9 @@ the writeout of dirty data to disk.\n Currently, these files are in /proc/sys/vm:\n \n - admin_reserve_kbytes\n+- async_mm_teardown\n+- async_mm_teardown_max_pending_pages\n+- async_mm_teardown_thresh_pages\n - compact_memory\n - compaction_proactiveness\n - compact_unevictable_allowed\n@@ -108,6 +111,41 @@ On x86_64 this is about 128MB.\n Changing this takes effect whenever an application requests memory.\n \n \n+async_mm_teardown\n+=================\n+\n+Available only when CONFIG_ASYNC_MM_TEARDOWN is set. Controls whether the\n+address-space teardown (exit_mmap()) of large exiting processes is deferred\n+to the mm_reaper kernel thread instead of running on the exiting CPU.\n+\n+Writing 1 enables deferral, 0 disables it. Disabling makes mmput_exit() tear\n+down inline again; it does not drain mms already queued. Defaults to 0.\n+\n+Deferred teardown consumes CPU in the mm_reaper kernel thread, which runs in\n+the root cgroup: the teardown work of a task in a CPU-limited cgroup is not\n+charged to that cgroup, similar to kswapd and the oom_reaper. Consider this\n+before enabling the feature on systems that rely on strict per-cgroup CPU\n+accounting.\n+\n+\n+async_mm_teardown_max_pending_pages\n+===================================\n+\n+Available only when CONFIG_ASYNC_MM_TEARDOWN is set. Backpressure cap, in\n+pages, on the total RSS of mms queued for the mm_reaper but not yet torn\n+down. An exit that would push the total over this cap tears down\n+synchronously instead of queuing. Defaults to totalram_pages() / 4.\n+\n+\n+async_mm_teardown_thresh_pages\n+==============================\n+\n+Available only when CONFIG_ASYNC_MM_TEARDOWN is set. Minimum RSS, in pages,\n+for an exiting mm to be eligible for deferral to the mm_reaper. Exiting mms\n+below this threshold are always torn down synchronously. Defaults to 64MB\n+worth of pages.\n+\n+\n compact_memory\n ==============\n \ndiff --git a/include/linux/mm_types.h b/include/linux/mm_types.h\nindex 939b5ea8c9e0be..ee6c0ac84b3d59 100644\n--- a/include/linux/mm_types.h\n+++ b/include/linux/mm_types.h\n@@ -7,6 +7,7 @@\n #include \u003clinux/auxvec.h\u003e\n #include \u003clinux/kref.h\u003e\n #include \u003clinux/list.h\u003e\n+#include \u003clinux/llist.h\u003e\n #include \u003clinux/spinlock.h\u003e\n #include \u003clinux/rbtree.h\u003e\n #include \u003clinux/maple_tree.h\u003e\n@@ -1367,6 +1368,10 @@ struct mm_struct {\n \t\tatomic_long_t hugetlb_usage;\n #endif\n \t\tstruct work_struct async_put_work;\n+#ifdef CONFIG_ASYNC_MM_TEARDOWN\n+\t\tstruct llist_node async_reap_node;\n+\t\tunsigned long async_reap_rss;\n+#endif /* CONFIG_ASYNC_MM_TEARDOWN */\n \n #ifdef CONFIG_IOMMU_MM_DATA\n \t\tstruct iommu_mm_data *iommu_mm;\n@@ -1985,6 +1990,16 @@ enum {\n #define MMF_TOPDOWN\t\t31\t/* mm searches top down by default */\n #define MMF_TOPDOWN_MASK\tBIT(MMF_TOPDOWN)\n \n+/*\n+ * mm is the target of an in-flight OOM kill. Only written under\n+ * CONFIG_ASYNC_MM_TEARDOWN today. Sticky: never cleared -- the mm is\n+ * dying (__oom_kill_process() kills every process sharing it), and a\n+ * hypothetical surviving sharer would merely keep tearing down\n+ * synchronously. Above bit 31, so it is outside MMF_INIT_LEGACY_MASK's\n+ * domain entirely and is never inherited on fork.\n+ */\n+#define MMF_OOM_TARGETED\t32\n+\n #define MMF_INIT_LEGACY_MASK\t(MMF_DUMP_FILTER_MASK |\\\n \t\t\t\t MMF_DISABLE_THP_MASK | MMF_HAS_MDWE_MASK |\\\n \t\t\t\t MMF_VM_MERGE_ANY_MASK | MMF_TOPDOWN_MASK)\ndiff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h\nindex 10be8a54b416f9..20aeb1d7ed31b0 100644\n--- a/include/linux/sched/mm.h\n+++ b/include/linux/sched/mm.h\n@@ -147,6 +147,12 @@ extern void mmput(struct mm_struct *);\n void mmput_async(struct mm_struct *);\n #endif\n \n+#ifdef CONFIG_ASYNC_MM_TEARDOWN\n+void mmput_exit(struct mm_struct *);\n+#else\n+static inline void mmput_exit(struct mm_struct *mm) { mmput(mm); }\n+#endif\n+\n /* Grab a reference to a task's mm, if it is not already going away */\n extern struct mm_struct *get_task_mm(struct task_struct *task);\n /*\ndiff --git a/include/linux/vm_event_item.h b/include/linux/vm_event_item.h\nindex 2628ccda076a07..de4dc20b7f87de 100644\n--- a/include/linux/vm_event_item.h\n+++ b/include/linux/vm_event_item.h\n@@ -179,6 +179,11 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT,\n \t\tNRSWPIN,\n \t\tNRSWPOUT,\n #endif /* CONFIG_SWAP */\n+#ifdef CONFIG_ASYNC_MM_TEARDOWN\n+\t\tASYNC_MM_TEARDOWN_QUEUED,\n+\t\tASYNC_MM_TEARDOWN_SYNC,\n+\t\tASYNC_MM_TEARDOWN_REJECTED,\n+#endif\n \t\tNR_VM_EVENT_ITEMS\n };\n \ndiff --git a/include/trace/events/mm_reaper.h b/include/trace/events/mm_reaper.h\nnew file mode 100644\nindex 00000000000000..315bced7c4253d\n--- /dev/null\n+++ b/include/trace/events/mm_reaper.h\n@@ -0,0 +1,73 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#undef TRACE_SYSTEM\n+#define TRACE_SYSTEM mm_reaper\n+\n+#if !defined(_TRACE_MM_REAPER_H) || defined(TRACE_HEADER_MULTI_READ)\n+#define _TRACE_MM_REAPER_H\n+\n+#include \u003clinux/tracepoint.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/topology.h\u003e\n+\n+TRACE_EVENT(mm_async_teardown_queue,\n+\n+\tTP_PROTO(struct mm_struct *mm, unsigned long rss),\n+\n+\tTP_ARGS(mm, rss),\n+\n+\tTP_STRUCT__entry(\n+\t\t__field(struct mm_struct *, mm)\n+\t\t__field(int, pid)\n+\t\t__array(char, comm, TASK_COMM_LEN)\n+\t\t__field(unsigned long, rss)\n+\t\t__field(int, node)\n+\t),\n+\n+\tTP_fast_assign(\n+\t\t__entry-\u003emm = mm;\n+\t\t__entry-\u003epid = current-\u003epid;\n+\t\tmemcpy(__entry-\u003ecomm, current-\u003ecomm, TASK_COMM_LEN);\n+\t\t__entry-\u003erss = rss;\n+\t\t__entry-\u003enode = numa_node_id();\n+\t),\n+\n+\tTP_printk(\"mm=%p pid=%d comm=%s rss=%lukB node=%d\",\n+\t\t__entry-\u003emm,\n+\t\t__entry-\u003epid,\n+\t\t__entry-\u003ecomm,\n+\t\t__entry-\u003erss \u003c\u003c (PAGE_SHIFT - 10),\n+\t\t__entry-\u003enode\n+\t)\n+);\n+\n+TRACE_EVENT(mm_async_teardown_reap,\n+\n+\tTP_PROTO(struct mm_struct *mm, unsigned long charged_rss, unsigned long live_rss),\n+\n+\tTP_ARGS(mm, charged_rss, live_rss),\n+\n+\tTP_STRUCT__entry(\n+\t\t__field(struct mm_struct *, mm)\n+\t\t__field(unsigned long, charged_rss)\n+\t\t__field(unsigned long, live_rss)\n+\t\t__field(int, node)\n+\t),\n+\n+\tTP_fast_assign(\n+\t\t__entry-\u003emm = mm;\n+\t\t__entry-\u003echarged_rss = charged_rss;\n+\t\t__entry-\u003elive_rss = live_rss;\n+\t\t__entry-\u003enode = numa_node_id();\n+\t),\n+\n+\tTP_printk(\"mm=%p charged_rss=%lukB live_rss=%lukB node=%d\",\n+\t\t__entry-\u003emm,\n+\t\t__entry-\u003echarged_rss \u003c\u003c (PAGE_SHIFT - 10),\n+\t\t__entry-\u003elive_rss \u003c\u003c (PAGE_SHIFT - 10),\n+\t\t__entry-\u003enode\n+\t)\n+);\n+\n+#endif /* _TRACE_MM_REAPER_H */\n+\n+#include \u003ctrace/define_trace.h\u003e\ndiff --git a/kernel/exit.c b/kernel/exit.c\nindex 1056422bc1013e..6f97d204f9fcac 100644\n--- a/kernel/exit.c\n+++ b/kernel/exit.c\n@@ -607,7 +607,7 @@ static void exit_mm(void)\n \ttask_unlock(current);\n \tmmap_read_unlock(mm);\n \tmm_update_next_owner(mm);\n-\tmmput(mm);\n+\tmmput_exit(mm);\n \tif (test_thread_flag(TIF_MEMDIE))\n \t\texit_oom_victim();\n }\ndiff --git a/kernel/fork.c b/kernel/fork.c\nindex f0e2e131a9a5af..4bdffa188f5224 100644\n--- a/kernel/fork.c\n+++ b/kernel/fork.c\n@@ -112,6 +112,8 @@\n #include \u003clinux/unwind_deferred.h\u003e\n #include \u003clinux/pgalloc.h\u003e\n #include \u003clinux/uaccess.h\u003e\n+#include \u003clinux/sched/isolation.h\u003e\n+#include \u003clinux/sizes.h\u003e\n \n #include \u003casm/mmu_context.h\u003e\n #include \u003casm/cacheflush.h\u003e\n@@ -124,6 +126,9 @@\n \n #define CREATE_TRACE_POINTS\n #include \u003ctrace/events/task.h\u003e\n+#ifdef CONFIG_ASYNC_MM_TEARDOWN\n+#include \u003ctrace/events/mm_reaper.h\u003e\n+#endif\n \n #include \u003ckunit/visibility.h\u003e\n \n@@ -3407,3 +3412,197 @@ static int __init init_fork_sysctl(void)\n }\n \n subsys_initcall(init_fork_sysctl);\n+\n+#ifdef CONFIG_ASYNC_MM_TEARDOWN\n+static LLIST_HEAD(mm_reaper_list);\n+static DECLARE_WAIT_QUEUE_HEAD(mm_reaper_wait);\n+static atomic_long_t mm_reaper_pending_pages;\n+static DEFINE_STATIC_KEY_FALSE(async_mm_teardown_key);\n+static unsigned long sysctl_async_mm_teardown_thresh_pages;\n+static unsigned long sysctl_async_mm_teardown_max_pending_pages;\n+#ifdef CONFIG_SYSCTL\n+static u8 sysctl_async_mm_teardown_enabled;\n+static DEFINE_MUTEX(async_mm_teardown_lock);\n+#endif\n+\n+static bool async_mm_teardown_eligible(struct mm_struct *mm, unsigned long rss)\n+{\n+\tif (rss \u003c READ_ONCE(sysctl_async_mm_teardown_thresh_pages))\n+\t\treturn false;\n+\tif (mm_flags_test(MMF_OOM_SKIP, mm))\t/* reaped, or hidden from the reaper */\n+\t\treturn false;\n+\t/*\n+\t * MMF_OOM_TARGETED is set by the OOM killer while some task still\n+\t * holds a live reference to this mm (see mark_oom_victim() and\n+\t * __oom_kill_process()), and this eligibility check is reached only\n+\t * from mmput_exit(), after mm_users has dropped to 0 -- so marking\n+\t * and this check can never overlap in time. Whichever thread\n+\t * performs the final decrement is therefore guaranteed to observe\n+\t * the flag already set, regardless of whether that thread was\n+\t * itself ever passed to mark_oom_victim() (a CLONE_VM-sharing\n+\t * process in another thread group never is, but still observes\n+\t * this flag on the shared mm).\n+\t */\n+\tif (mm_flags_test(MMF_OOM_TARGETED, mm))\n+\t\treturn false;\n+\treturn true;\n+}\n+\n+/*\n+ * Charge rss against the pending-teardown budget. Returns true if it fits\n+ * under the cap, in which case the caller enqueues and the reaper releases\n+ * the same rss after __mmput(). On overshoot nothing stays charged and the\n+ * caller must tear down synchronously.\n+ */\n+static bool async_mm_teardown_reserve(unsigned long rss)\n+{\n+\tif (atomic_long_add_return(rss, \u0026mm_reaper_pending_pages) \u003e\n+\t READ_ONCE(sysctl_async_mm_teardown_max_pending_pages)) {\n+\t\tatomic_long_sub(rss, \u0026mm_reaper_pending_pages);\n+\t\treturn false;\n+\t}\n+\treturn true;\n+}\n+\n+static void async_mm_teardown_queue(struct mm_struct *mm, unsigned long rss)\n+{\n+\tcount_vm_event(ASYNC_MM_TEARDOWN_QUEUED);\n+\ttrace_mm_async_teardown_queue(mm, rss);\n+\tif (llist_add(\u0026mm-\u003easync_reap_node, \u0026mm_reaper_list))\n+\t\twake_up(\u0026mm_reaper_wait);\n+}\n+\n+static int mm_reaper(void *unused)\n+{\n+\tset_freezable();\n+\twhile (true) {\n+\t\tstruct llist_node *batch;\n+\t\tstruct mm_struct *mm, *n;\n+\n+\t\twait_event_freezable(mm_reaper_wait, !llist_empty(\u0026mm_reaper_list));\n+\t\tbatch = llist_del_all(\u0026mm_reaper_list);\n+\t\tllist_for_each_entry_safe(mm, n, batch, async_reap_node) {\n+\t\t\tunsigned long pages = mm-\u003easync_reap_rss;\n+\t\t\tunsigned long live = get_mm_rss(mm);\n+\n+\t\t\ttrace_mm_async_teardown_reap(mm, pages, live);\n+\n+\t\t\t__mmput(mm); /* may free mm via mmdrop */\n+\t\t\tatomic_long_sub(pages, \u0026mm_reaper_pending_pages);\n+\t\t\tcond_resched();\n+\t\t\ttry_to_freeze();\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+void mmput_exit(struct mm_struct *mm)\n+{\n+\tmight_sleep();\n+\t/*\n+\t * Fully ordered RMW: combined with exit_mm() (or exec_mmap(), the\n+\t * other path that sheds -\u003emm) clearing -\u003emm under task_lock() before\n+\t * this call, it guarantees MMF_OOM_TARGETED set at any mark site is\n+\t * visible here -- see mark_oom_victim(). Any new caller of\n+\t * mmput_exit() outside exit_mm() must re-verify that argument.\n+\t */\n+\tif (!atomic_dec_and_test(\u0026mm-\u003emm_users))\n+\t\treturn;\n+\n+\tif (static_branch_unlikely(\u0026async_mm_teardown_key)) {\n+\t\tunsigned long rss = get_mm_rss(mm);\n+\n+\t\tif (async_mm_teardown_eligible(mm, rss)) {\n+\t\t\t/*\n+\t\t\t * exit_aio() can block indefinitely. Run it here so a stuck\n+\t\t\t * AIO only hangs this task (same as how it would happen in\n+\t\t\t * case of synchronous mmput()) instead of stranding every\n+\t\t\t * teardown queued behind this mm\n+\t\t\t */\n+\t\t\texit_aio(mm);\n+\n+\t\t\tif (async_mm_teardown_reserve(rss)) {\n+\t\t\t\tmm-\u003easync_reap_rss = rss;\n+\t\t\t\tasync_mm_teardown_queue(mm, rss);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\tcount_vm_event(ASYNC_MM_TEARDOWN_REJECTED);\n+\t\t}\n+\t}\n+\n+\tcount_vm_event(ASYNC_MM_TEARDOWN_SYNC);\n+\t__mmput(mm);\n+}\n+\n+#ifdef CONFIG_SYSCTL\n+static int async_mm_teardown_enabled_handler(const struct ctl_table *table,\n+\t\t\t\t\t int write, void *buffer,\n+\t\t\t\t\t size_t *lenp, loff_t *ppos)\n+{\n+\tint ret;\n+\n+\t/*\n+\t * Serialize concurrent writers so the static key state always matches\n+\t * the last value written to the variable.\n+\t */\n+\tguard(mutex)(\u0026async_mm_teardown_lock);\n+\n+\tret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos);\n+\tif (ret || !write)\n+\t\treturn ret;\n+\n+\tif (sysctl_async_mm_teardown_enabled)\n+\t\tstatic_branch_enable(\u0026async_mm_teardown_key);\n+\telse\n+\t\tstatic_branch_disable(\u0026async_mm_teardown_key);\n+\treturn 0;\n+}\n+\n+static const struct ctl_table async_mm_teardown_table[] = {\n+\t{\n+\t\t.procname\t= \"async_mm_teardown\",\n+\t\t.data\t\t= \u0026sysctl_async_mm_teardown_enabled,\n+\t\t.maxlen\t\t= sizeof(u8),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= async_mm_teardown_enabled_handler,\n+\t\t.extra1\t\t= SYSCTL_ZERO,\n+\t\t.extra2\t\t= SYSCTL_ONE,\n+\t},\n+\t{\n+\t\t.procname\t= \"async_mm_teardown_thresh_pages\",\n+\t\t.data\t\t= \u0026sysctl_async_mm_teardown_thresh_pages,\n+\t\t.maxlen\t\t= sizeof(unsigned long),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= proc_doulongvec_minmax,\n+\t},\n+\t{\n+\t\t.procname\t= \"async_mm_teardown_max_pending_pages\",\n+\t\t.data\t\t= \u0026sysctl_async_mm_teardown_max_pending_pages,\n+\t\t.maxlen\t\t= sizeof(unsigned long),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= proc_doulongvec_minmax,\n+\t},\n+};\n+#endif /* CONFIG_SYSCTL */\n+\n+static int __init mm_reaper_init(void)\n+{\n+\tstruct task_struct *th;\n+\n+\tth = kthread_create(mm_reaper, NULL, \"mm_reaper\");\n+\tif (IS_ERR(th)) {\n+\t\tpr_err(\"mm_reaper: failed to start kthread: %ld\\n\", PTR_ERR(th));\n+\t\treturn PTR_ERR(th);\n+\t}\n+\tset_user_nice(th, 19); /* minimize competition with other fair class tasks */\n+\tkthread_affine_preferred(th, housekeeping_cpumask(HK_TYPE_KTHREAD));\n+\tsysctl_async_mm_teardown_thresh_pages = SZ_64M \u003e\u003e PAGE_SHIFT;\n+\tsysctl_async_mm_teardown_max_pending_pages = totalram_pages() / 4; /* TODO: placeholder */\n+\twake_up_process(th);\n+#ifdef CONFIG_SYSCTL\n+\tregister_sysctl_init(\"vm\", async_mm_teardown_table);\n+#endif\n+\treturn 0;\n+}\n+subsys_initcall(mm_reaper_init);\n+#endif\ndiff --git a/mm/Kconfig b/mm/Kconfig\nindex c52ab6afcb1596..a9d925406653f8 100644\n--- a/mm/Kconfig\n+++ b/mm/Kconfig\n@@ -1500,6 +1500,18 @@ config LAZY_MMU_MODE_KUNIT_TEST\n \n \t If unsure, say N.\n \n+config ASYNC_MM_TEARDOWN\n+\tbool \"Async off-CPU address space teardown on exit\"\n+\tdepends on MMU\n+\tdefault n\n+\thelp\n+\t Defer the address space teardown (exit_mmap()) of large exiting processes\n+\t to a kernel thread instead of running it on the exiting CPU. The feature\n+\t is off by default and is enabled at runtime via the vm.async_mm_teardown\n+\t sysctl.\n+\n+\t If unsure, say N.\n+\n source \"mm/damon/Kconfig\"\n \n endmenu\ndiff --git a/mm/oom_kill.c b/mm/oom_kill.c\nindex 5f372f6e26fa32..b5490d1b1bd261 100644\n--- a/mm/oom_kill.c\n+++ b/mm/oom_kill.c\n@@ -754,6 +754,40 @@ static void mark_oom_victim(struct task_struct *tsk)\n \tstruct mm_struct *mm = tsk-\u003emm;\n \n \tWARN_ON(oom_killer_disabled);\n+\n+\t/*\n+\t * Mark the mm itself, not just this task/signal, as an OOM target,\n+\t * so the deferred-teardown path can test it from the mm alone. Set\n+\t * ahead of the TIF_MEMDIE test-and-set below so that every call\n+\t * marks the mm, including a repeat mark on a task that is already\n+\t * TIF_MEMDIE.\n+\t *\n+\t * This is reliably visible to whichever thread ends up dropping the\n+\t * last reference in mmput_exit(): marking and queuing for async\n+\t * teardown can never overlap in time. Every caller reaches this with\n+\t * tsk-\u003emm still set -- either tsk is current, marking itself in\n+\t * program order, or tsk is task_lock()ed by the caller (the\n+\t * task_will_free_mem() fast path in oom_kill_process()) -- while\n+\t * mmput_exit(), the only path that queues for teardown, is reached\n+\t * only after mm_users hits 0, which requires tsk's own exit_mm() (or\n+\t * exec_mmap(), the other path that sheds -\u003emm) to have already\n+\t * cleared -\u003emm under that same task_lock(). Program order or\n+\t * task_lock() release/acquire therefore orders this store before\n+\t * tsk's own eventual mmput_exit().\n+\t *\n+\t * If a different CLONE_VM sharer performs the actual final decrement\n+\t * instead, the ordering does not come from this store:\n+\t * mm_flags_set() is set_bit(), a non-value-returning RMW, so it is\n+\t * unordered and contributes nothing on its own. It comes from the\n+\t * marking task's own atomic_dec_and_test(), a value-returning RMW\n+\t * and therefore fully ordered (an smp_mb() before and after).\n+\t * Decrements are totally ordered in mm_users' modification order\n+\t * with the zeroing one last, so the general barriers on both sides\n+\t * make this store visible to whichever thread performs it.\n+\t */\n+\tif (IS_ENABLED(CONFIG_ASYNC_MM_TEARDOWN))\n+\t\tmm_flags_set(MMF_OOM_TARGETED, mm);\n+\n \t/* OOM killer might race with memcg OOM */\n \tif (test_and_set_tsk_thread_flag(tsk, TIF_MEMDIE))\n \t\treturn;\n@@ -931,6 +965,33 @@ static void __oom_kill_process(struct task_struct *victim, const char *message)\n \tmm = victim-\u003emm;\n \tmmgrab(mm);\n \n+\t/*\n+\t * Set this before any SIGKILL for this kill event goes out below,\n+\t * while task_lock(victim) (held since find_lock_task_mm() above) is\n+\t * still held. This is reliably visible to whichever thread ends up\n+\t * running mmput_exit() and dropping the last reference to this mm --\n+\t * victim itself, a sibling, or a CLONE_VM sharer in another thread\n+\t * group (which mark_oom_victim() never marks, but which still\n+\t * observes this store on the shared mm). Marking and queuing for\n+\t * async teardown can never overlap: mmput_exit() is reached only\n+\t * after mm_users hits 0, and that requires victim's own exit_mm()\n+\t * (or exec_mmap(), the other path that sheds -\u003emm) to have cleared\n+\t * -\u003emm under this same task_lock() first, which release/acquire\n+\t * orders after the store above.\n+\t *\n+\t * If some other sharer performs the actual final decrement instead,\n+\t * the ordering does not come from this store: mm_flags_set() is\n+\t * set_bit(), a non-value-returning RMW, so it is unordered and\n+\t * contributes nothing on its own. It comes from the marking task's\n+\t * own atomic_dec_and_test(), a value-returning RMW and therefore\n+\t * fully ordered (an smp_mb() before and after). Decrements are\n+\t * totally ordered in mm_users' modification order with the zeroing\n+\t * one last, so the general barriers on both sides make this store\n+\t * visible to whichever thread performs it.\n+\t */\n+\tif (IS_ENABLED(CONFIG_ASYNC_MM_TEARDOWN))\n+\t\tmm_flags_set(MMF_OOM_TARGETED, mm);\n+\n \t/* Raise event before sending signal: task reaper must see this */\n \tcount_vm_event(OOM_KILL);\n \tmemcg_memory_event_mm(mm, MEMCG_OOM_KILL);\ndiff --git a/mm/vmstat.c b/mm/vmstat.c\nindex 4e26e5fd666626..e3ff30027c24ed 100644\n--- a/mm/vmstat.c\n+++ b/mm/vmstat.c\n@@ -1494,6 +1494,11 @@ const char * const vmstat_text[] = {\n \t[I(NRSWPIN)]\t\t\t\t= \"nrswpin\",\n \t[I(NRSWPOUT)]\t\t\t\t= \"nrswpout\",\n #endif /* CONFIG_SWAP */\n+#ifdef CONFIG_ASYNC_MM_TEARDOWN\n+\t[I(ASYNC_MM_TEARDOWN_QUEUED)]\t= \"async_mm_teardown_queued\",\n+\t[I(ASYNC_MM_TEARDOWN_SYNC)]\t\t= \"async_mm_teardown_sync\",\n+\t[I(ASYNC_MM_TEARDOWN_REJECTED)]\t= \"async_mm_teardown_rejected\",\n+#endif\n #undef I\n #endif /* CONFIG_VM_EVENT_COUNTERS */\n };\n"
} |
| 2/1 | 2026/07/19 19:41 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"ASYNC_MM_TEARDOWN"
],
"FocusSymbols": [
"mm_reaper",
"async_mm_teardown_queue",
"async_mm_teardown_reserve",
"async_mm_teardown_eligible"
],
"Reasoning": "The patch introduces a new feature to asynchronously tear down the address space of exiting processes via a dedicated kernel thread (mm_reaper). This modifies core process exit and memory management logic, which is highly functional and 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). 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. 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. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. 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 11e5f51972471277c32b40ff94aabbd86a0461ea
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Jul 19 19:41:38 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst
index 5b318d17aa4b44..ccda6c75fe1fb7 100644
--- a/Documentation/admin-guide/sysctl/vm.rst
+++ b/Documentation/admin-guide/sysctl/vm.rst
@@ -22,6 +22,9 @@ the writeout of dirty data to disk.
Currently, these files are in /proc/sys/vm:
- admin_reserve_kbytes
+- async_mm_teardown
+- async_mm_teardown_max_pending_pages
+- async_mm_teardown_thresh_pages
- compact_memory
- compaction_proactiveness
- compact_unevictable_allowed
@@ -108,6 +111,41 @@ On x86_64 this is about 128MB.
Changing this takes effect whenever an application requests memory.
+async_mm_teardown
+=================
+
+Available only when CONFIG_ASYNC_MM_TEARDOWN is set. Controls whether the
+address-space teardown (exit_mmap()) of large exiting processes is deferred
+to the mm_reaper kernel thread instead of running on the exiting CPU.
+
+Writing 1 enables deferral, 0 disables it. Disabling makes mmput_exit() tear
+down inline again; it does not drain mms already queued. Defaults to 0.
+
+Deferred teardown consumes CPU in the mm_reaper kernel thread, which runs in
+the root cgroup: the teardown work of a task in a CPU-limited cgroup is not
+charged to that cgroup, similar to kswapd and the oom_reaper. Consider this
+before enabling the feature on systems that rely on strict per-cgroup CPU
+accounting.
+
+
+async_mm_teardown_max_pending_pages
+===================================
+
+Available only when CONFIG_ASYNC_MM_TEARDOWN is set. Backpressure cap, in
+pages, on the total RSS of mms queued for the mm_reaper but not yet torn
+down. An exit that would push the total over this cap tears down
+synchronously instead of queuing. Defaults to totalram_pages() / 4.
+
+
+async_mm_teardown_thresh_pages
+==============================
+
+Available only when CONFIG_ASYNC_MM_TEARDOWN is set. Minimum RSS, in pages,
+for an exiting mm to be eligible for deferral to the mm_reaper. Exiting mms
+below this threshold are always torn down synchronously. Defaults to 64MB
+worth of pages.
+
+
compact_memory
==============
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 939b5ea8c9e0be..ee6c0ac84b3d59 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -7,6 +7,7 @@
#include <linux/auxvec.h>
#include <linux/kref.h>
#include <linux/list.h>
+#include <linux/llist.h>
#include <linux/spinlock.h>
#include <linux/rbtree.h>
#include <linux/maple_tree.h>
@@ -1367,6 +1368,10 @@ struct mm_struct {
atomic_long_t hugetlb_usage;
#endif
struct work_struct async_put_work;
+#ifdef CONFIG_ASYNC_MM_TEARDOWN
+ struct llist_node async_reap_node;
+ unsigned long async_reap_rss;
+#endif /* CONFIG_ASYNC_MM_TEARDOWN */
#ifdef CONFIG_IOMMU_MM_DATA
struct iommu_mm_data *iommu_mm;
@@ -1985,6 +1990,16 @@ enum {
#define MMF_TOPDOWN 31 /* mm searches top down by default */
#define MMF_TOPDOWN_MASK BIT(MMF_TOPDOWN)
+/*
+ * mm is the target of an in-flight OOM kill. Only written under
+ * CONFIG_ASYNC_MM_TEARDOWN today. Sticky: never cleared -- the mm is
+ * dying (__oom_kill_process() kills every process sharing it), and a
+ * hypothetical surviving sharer would merely keep tearing down
+ * synchronously. Above bit 31, so it is outside MMF_INIT_LEGACY_MASK's
+ * domain entirely and is never inherited on fork.
+ */
+#define MMF_OOM_TARGETED 32
+
#define MMF_INIT_LEGACY_MASK (MMF_DUMP_FILTER_MASK |\
MMF_DISABLE_THP_MASK | MMF_HAS_MDWE_MASK |\
MMF_VM_MERGE_ANY_MASK | MMF_TOPDOWN_MASK)
diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h
index 10be8a54b416f9..20aeb1d7ed31b0 100644
--- a/include/linux/sched/mm.h
+++ b/include/linux/sched/mm.h
@@ -147,6 +147,12 @@ extern void mmput(struct mm_struct *);
void mmput_async(struct mm_struct *);
#endif
+#ifdef CONFIG_ASYNC_MM_TEARDOWN
+void mmput_exit(struct mm_struct *);
+#else
+static inline void mmput_exit(struct mm_struct *mm) { mmput(mm); }
+#endif
+
/* Grab a reference to a task's mm, if it is not already going away */
extern struct mm_struct *get_task_mm(struct task_struct *task);
/*
diff --git a/include/linux/vm_event_item.h b/include/linux/vm_event_item.h
index 2628ccda076a07..de4dc20b7f87de 100644
--- a/include/linux/vm_event_item.h
+++ b/include/linux/vm_event_item.h
@@ -179,6 +179,11 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT,
NRSWPIN,
NRSWPOUT,
#endif /* CONFIG_SWAP */
+#ifdef CONFIG_ASYNC_MM_TEARDOWN
+ ASYNC_MM_TEARDOWN_QUEUED,
+ ASYNC_MM_TEARDOWN_SYNC,
+ ASYNC_MM_TEARDOWN_REJECTED,
+#endif
NR_VM_EVENT_ITEMS
};
diff --git a/include/trace/events/mm_reaper.h b/include/trace/events/mm_reaper.h
new file mode 100644
index 00000000000000..315bced7c4253d
--- /dev/null
+++ b/include/trace/events/mm_reaper.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mm_reaper
+
+#if !defined(_TRACE_MM_REAPER_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_MM_REAPER_H
+
+#include <linux/tracepoint.h>
+#include <linux/sched.h>
+#include <linux/topology.h>
+
+TRACE_EVENT(mm_async_teardown_queue,
+
+ TP_PROTO(struct mm_struct *mm, unsigned long rss),
+
+ TP_ARGS(mm, rss),
+
+ TP_STRUCT__entry(
+ __field(struct mm_struct *, mm)
+ __field(int, pid)
+ __array(char, comm, TASK_COMM_LEN)
+ __field(unsigned long, rss)
+ __field(int, node)
+ ),
+
+ TP_fast_assign(
+ __entry->mm = mm;
+ __entry->pid = current->pid;
+ memcpy(__entry->comm, current->comm, TASK_COMM_LEN);
+ __entry->rss = rss;
+ __entry->node = numa_node_id();
+ ),
+
+ TP_printk("mm=%p pid=%d comm=%s rss=%lukB node=%d",
+ __entry->mm,
+ __entry->pid,
+ __entry->comm,
+ __entry->rss << (PAGE_SHIFT - 10),
+ __entry->node
+ )
+);
+
+TRACE_EVENT(mm_async_teardown_reap,
+
+ TP_PROTO(struct mm_struct *mm, unsigned long charged_rss, unsigned long live_rss),
+
+ TP_ARGS(mm, charged_rss, live_rss),
+
+ TP_STRUCT__entry(
+ __field(struct mm_struct *, mm)
+ __field(unsigned long, charged_rss)
+ __field(unsigned long, live_rss)
+ __field(int, node)
+ ),
+
+ TP_fast_assign(
+ __entry->mm = mm;
+ __entry->charged_rss = charged_rss;
+ __entry->live_rss = live_rss;
+ __entry->node = numa_node_id();
+ ),
+
+ TP_printk("mm=%p charged_rss=%lukB live_rss=%lukB node=%d",
+ __entry->mm,
+ __entry->charged_rss << (PAGE_SHIFT - 10),
+ __entry->live_rss << (PAGE_SHIFT - 10),
+ __entry->node
+ )
+);
+
+#endif /* _TRACE_MM_REAPER_H */
+
+#include <trace/define_trace.h>
diff --git a/kernel/exit.c b/kernel/exit.c
index 1056422bc1013e..6f97d204f9fcac 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -607,7 +607,7 @@ static void exit_mm(void)
task_unlock(current);
mmap_read_unlock(mm);
mm_update_next_owner(mm);
- mmput(mm);
+ mmput_exit(mm);
if (test_thread_flag(TIF_MEMDIE))
exit_oom_victim();
}
diff --git a/kernel/fork.c b/kernel/fork.c
index f0e2e131a9a5af..4bdffa188f5224 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -112,6 +112,8 @@
#include <linux/unwind_deferred.h>
#include <linux/pgalloc.h>
#include <linux/uaccess.h>
+#include <linux/sched/isolation.h>
+#include <linux/sizes.h>
#include <asm/mmu_context.h>
#include <asm/cacheflush.h>
@@ -124,6 +126,9 @@
#define CREATE_TRACE_POINTS
#include <trace/events/task.h>
+#ifdef CONFIG_ASYNC_MM_TEARDOWN
+#include <trace/events/mm_reaper.h>
+#endif
#include <kunit/visibility.h>
@@ -3407,3 +3412,197 @@ static int __init init_fork_sysctl(void)
}
subsys_initcall(init_fork_sysctl);
+
+#ifdef CONFIG_ASYNC_MM_TEARDOWN
+static LLIST_HEAD(mm_reaper_list);
+static DECLARE_WAIT_QUEUE_HEAD(mm_reaper_wait);
+static atomic_long_t mm_reaper_pending_pages;
+static DEFINE_STATIC_KEY_FALSE(async_mm_teardown_key);
+static unsigned long sysctl_async_mm_teardown_thresh_pages;
+static unsigned long sysctl_async_mm_teardown_max_pending_pages;
+#ifdef CONFIG_SYSCTL
+static u8 sysctl_async_mm_teardown_enabled;
+static DEFINE_MUTEX(async_mm_teardown_lock);
+#endif
+
+static bool async_mm_teardown_eligible(struct mm_struct *mm, unsigned long rss)
+{
+ if (rss < READ_ONCE(sysctl_async_mm_teardown_thresh_pages))
+ return false;
+ if (mm_flags_test(MMF_OOM_SKIP, mm)) /* reaped, or hidden from the reaper */
+ return false;
+ /*
+ * MMF_OOM_TARGETED is set by the OOM killer while some task still
+ * holds a live reference to this mm (see mark_oom_victim() and
+ * __oom_kill_process()), and this eligibility check is reached only
+ * from mmput_exit(), after mm_users has dropped to 0 -- so marking
+ * and this check can never overlap in time. Whichever thread
+ * performs the final decrement is therefore guaranteed to observe
+ * the flag already set, regardless of whether that thread was
+ * itself ever passed to mark_oom_victim() (a CLONE_VM-sharing
+ * process in another thread group never is, but still observes
+ * this flag on the shared mm).
+ */
+ if (mm_flags_test(MMF_OOM_TARGETED, mm))
+ return false;
+ return true;
+}
+
+/*
+ * Charge rss against the pending-teardown budget. Returns true if it fits
+ * under the cap, in which case the caller enqueues and the reaper releases
+ * the same rss after __mmput(). On overshoot nothing stays charged and the
+ * caller must tear down synchronously.
+ */
+static bool async_mm_teardown_reserve(unsigned long rss)
+{
+ if (atomic_long_add_return(rss, &mm_reaper_pending_pages) >
+ READ_ONCE(sysctl_async_mm_teardown_max_pending_pages)) {
+ atomic_long_sub(rss, &mm_reaper_pending_pages);
+ return false;
+ }
+ return true;
+}
+
+static void async_mm_teardown_queue(struct mm_struct *mm, unsigned long rss)
+{
+ count_vm_event(ASYNC_MM_TEARDOWN_QUEUED);
+ trace_mm_async_teardown_queue(mm, rss);
+ if (llist_add(&mm->async_reap_node, &mm_reaper_list))
+ wake_up(&mm_reaper_wait);
+}
+
+static int mm_reaper(void *unused)
+{
+ set_freezable();
+ while (true) {
+ struct llist_node *batch;
+ struct mm_struct *mm, *n;
+
+ wait_event_freezable(mm_reaper_wait, !llist_empty(&mm_reaper_list));
+ batch = llist_del_all(&mm_reaper_list);
+ llist_for_each_entry_safe(mm, n, batch, async_reap_node) {
+ unsigned long pages = mm->async_reap_rss;
+ unsigned long live = get_mm_rss(mm);
+
+ trace_mm_async_teardown_reap(mm, pages, live);
+
+ __mmput(mm); /* may free mm via mmdrop */
+ atomic_long_sub(pages, &mm_reaper_pending_pages);
+ cond_resched();
+ try_to_freeze();
+ }
+ }
+ return 0;
+}
+
+void mmput_exit(struct mm_struct *mm)
+{
+ might_sleep();
+ /*
+ * Fully ordered RMW: combined with exit_mm() (or exec_mmap(), the
+ * other path that sheds ->mm) clearing ->mm under task_lock() before
+ * this call, it guarantees MMF_OOM_TARGETED set at any mark site is
+ * visible here -- see mark_oom_victim(). Any new caller of
+ * mmput_exit() outside exit_mm() must re-verify that argument.
+ */
+ if (!atomic_dec_and_test(&mm->mm_users))
+ return;
+
+ if (static_branch_unlikely(&async_mm_teardown_key)) {
+ unsigned long rss = get_mm_rss(mm);
+
+ if (async_mm_teardown_eligible(mm, rss)) {
+ /*
+ * exit_aio() can block indefinitely. Run it here so a stuck
+ * AIO only hangs this task (same as how it would happen in
+ * case of synchronous mmput()) instead of stranding every
+ * teardown queued behind this mm
+ */
+ exit_aio(mm);
+
+ if (async_mm_teardown_reserve(rss)) {
+ mm->async_reap_rss = rss;
+ async_mm_teardown_queue(mm, rss);
+ return;
+ }
+ count_vm_event(ASYNC_MM_TEARDOWN_REJECTED);
+ }
+ }
+
+ count_vm_event(ASYNC_MM_TEARDOWN_SYNC);
+ __mmput(mm);
+}
+
+#ifdef CONFIG_SYSCTL
+static int async_mm_teardown_enabled_handler(const struct ctl_table *table,
+ int write, void *buffer,
+ size_t *lenp, loff_t *ppos)
+{
+ int ret;
+
+ /*
+ * Serialize concurrent writers so the static key state always matches
+ * the last value written to the variable.
+ */
+ guard(mutex)(&async_mm_teardown_lock);
+
+ ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos);
+ if (ret || !write)
+ return ret;
+
+ if (sysctl_async_mm_teardown_enabled)
+ static_branch_enable(&async_mm_teardown_key);
+ else
+ static_branch_disable(&async_mm_teardown_key);
+ return 0;
+}
+
+static const struct ctl_table async_mm_teardown_table[] = {
+ {
+ .procname = "async_mm_teardown",
+ .data = &sysctl_async_mm_teardown_enabled,
+ .maxlen = sizeof(u8),
+ .mode = 0644,
+ .proc_handler = async_mm_teardown_enabled_handler,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
+ },
+ {
+ .procname = "async_mm_teardown_thresh_pages",
+ .data = &sysctl_async_mm_teardown_thresh_pages,
+ .maxlen = sizeof(unsigned long),
+ .mode = 0644,
+ .proc_handler = proc_doulongvec_minmax,
+ },
+ {
+ .procname = "async_mm_teardown_max_pending_pages",
+ .data = &sysctl_async_mm_teardown_max_pending_pages,
+ .maxlen = sizeof(unsigned long),
+ .mode = 0644,
+ .proc_handler = proc_doulongvec_minmax,
+ },
+};
+#endif /* CONFIG_SYSCTL */
+
+static int __init mm_reaper_init(void)
+{
+ struct task_struct *th;
+
+ th = kthread_create(mm_reaper, NULL, "mm_reaper");
+ if (IS_ERR(th)) {
+ pr_err("mm_reaper: failed to start kthread: %ld\n", PTR_ERR(th));
+ return PTR_ERR(th);
+ }
+ set_user_nice(th, 19); /* minimize competition with other fair class tasks */
+ kthread_affine_preferred(th, housekeeping_cpumask(HK_TYPE_KTHREAD));
+ sysctl_async_mm_teardown_thresh_pages = SZ_64M >> PAGE_SHIFT;
+ sysctl_async_mm_teardown_max_pending_pages = totalram_pages() / 4; /* TODO: placeholder */
+ wake_up_process(th);
+#ifdef CONFIG_SYSCTL
+ register_sysctl_init("vm", async_mm_teardown_table);
+#endif
+ return 0;
+}
+subsys_initcall(mm_reaper_init);
+#endif
diff --git a/mm/Kconfig b/mm/Kconfig
index c52ab6afcb1596..a9d925406653f8 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1500,6 +1500,18 @@ config LAZY_MMU_MODE_KUNIT_TEST
If unsure, say N.
+config ASYNC_MM_TEARDOWN
+ bool "Async off-CPU address space teardown on exit"
+ depends on MMU
+ default n
+ help
+ Defer the address space teardown (exit_mmap()) of large exiting processes
+ to a kernel thread instead of running it on the exiting CPU. The feature
+ is off by default and is enabled at runtime via the vm.async_mm_teardown
+ sysctl.
+
+ If unsure, say N.
+
source "mm/damon/Kconfig"
endmenu
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index 5f372f6e26fa32..b5490d1b1bd261 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -754,6 +754,40 @@ static void mark_oom_victim(struct task_struct *tsk)
struct mm_struct *mm = tsk->mm;
WARN_ON(oom_killer_disabled);
+
+ /*
+ * Mark the mm itself, not just this task/signal, as an OOM target,
+ * so the deferred-teardown path can test it from the mm alone. Set
+ * ahead of the TIF_MEMDIE test-and-set below so that every call
+ * marks the mm, including a repeat mark on a task that is already
+ * TIF_MEMDIE.
+ *
+ * This is reliably visible to whichever thread ends up dropping the
+ * last reference in mmput_exit(): marking and queuing for async
+ * teardown can never overlap in time. Every caller reaches this with
+ * tsk->mm still set -- either tsk is current, marking itself in
+ * program order, or tsk is task_lock()ed by the caller (the
+ * task_will_free_mem() fast path in oom_kill_process()) -- while
+ * mmput_exit(), the only path that queues for teardown, is reached
+ * only after mm_users hits 0, which requires tsk's own exit_mm() (or
+ * exec_mmap(), the other path that sheds ->mm) to have already
+ * cleared ->mm under that same task_lock(). Program order or
+ * task_lock() release/acquire therefore orders this store before
+ * tsk's own eventual mmput_exit().
+ *
+ * If a different CLONE_VM sharer performs the actual final decrement
+ * instead, the ordering does not come from this store:
+ * mm_flags_set() is set_bit(), a non-value-returning RMW, so it is
+ * unordered and contributes nothing on its own. It comes from the
+ * marking task's own atomic_dec_and_test(), a value-returning RMW
+ * and therefore fully ordered (an smp_mb() before and after).
+ * Decrements are totally ordered in mm_users' modification order
+ * with the zeroing one last, so the general barriers on both sides
+ * make this store visible to whichever thread performs it.
+ */
+ if (IS_ENABLED(CONFIG_ASYNC_MM_TEARDOWN))
+ mm_flags_set(MMF_OOM_TARGETED, mm);
+
/* OOM killer might race with memcg OOM */
if (test_and_set_tsk_thread_flag(tsk, TIF_MEMDIE))
return;
@@ -931,6 +965,33 @@ static void __oom_kill_process(struct task_struct *victim, const char *message)
mm = victim->mm;
mmgrab(mm);
+ /*
+ * Set this before any SIGKILL for this kill event goes out below,
+ * while task_lock(victim) (held since find_lock_task_mm() above) is
+ * still held. This is reliably visible to whichever thread ends up
+ * running mmput_exit() and dropping the last reference to this mm --
+ * victim itself, a sibling, or a CLONE_VM sharer in another thread
+ * group (which mark_oom_victim() never marks, but which still
+ * observes this store on the shared mm). Marking and queuing for
+ * async teardown can never overlap: mmput_exit() is reached only
+ * after mm_users hits 0, and that requires victim's own exit_mm()
+ * (or exec_mmap(), the other path that sheds ->mm) to have cleared
+ * ->mm under this same task_lock() first, which release/acquire
+ * orders after the store above.
+ *
+ * If some other sharer performs the actual final decrement instead,
+ * the ordering does not come from this store: mm_flags_set() is
+ * set_bit(), a non-value-returning RMW, so it is unordered and
+ * contributes nothing on its own. It comes from the marking task's
+ * own atomic_dec_and_test(), a value-returning RMW and therefore
+ * fully ordered (an smp_mb() before and after). Decrements are
+ * totally ordered in mm_users' modification order with the zeroing
+ * one last, so the general barriers on both sides make this store
+ * visible to whichever thread performs it.
+ */
+ if (IS_ENABLED(CONFIG_ASYNC_MM_TEARDOWN))
+ mm_flags_set(MMF_OOM_TARGETED, mm);
+
/* Raise event before sending signal: task reaper must see this */
count_vm_event(OOM_KILL);
memcg_memory_event_mm(mm, MEMCG_OOM_KILL);
diff --git a/mm/vmstat.c b/mm/vmstat.c
index 4e26e5fd666626..e3ff30027c24ed 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1494,6 +1494,11 @@ const char * const vmstat_text[] = {
[I(NRSWPIN)] = "nrswpin",
[I(NRSWPOUT)] = "nrswpout",
#endif /* CONFIG_SWAP */
+#ifdef CONFIG_ASYNC_MM_TEARDOWN
+ [I(ASYNC_MM_TEARDOWN_QUEUED)] = "async_mm_teardown_queued",
+ [I(ASYNC_MM_TEARDOWN_SYNC)] = "async_mm_teardown_sync",
+ [I(ASYNC_MM_TEARDOWN_REJECTED)] = "async_mm_teardown_rejected",
+#endif
#undef I
#endif /* CONFIG_VM_EVENT_COUNTERS */
};
|
| 3/2 | 2026/07/19 19:41 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8813 output: 131 thoughts: 1125 Thoughts: **Fuzzing Evaluation: Async MM Teardown** Okay, let's break this down. We have a patch series introducing `CONFIG_ASYNC_MM_TEARDOWN`. This is a pretty significant change. It's deferring the address space teardown (`exit_mmap()`) of exiting processes to a kernel thread, `mm_reaper`. This fundamentally alters how the kernel handles process exit and memory management. This is definitely a functional change that *needs* fuzzing. My initial thought is to zero in on specific kernel functions affected. `mmput_exit`, `mm_reaper`, `async_mm_teardown_queue`, `async_mm_teardown_reserve`, and `async_mm_teardown_eligible` immediately stand out. Those are the key pieces of the new logic. The patch also introduces a sysctl, `vm.async_mm_teardown`, which is important for enabling the feature at runtime, and of course, the `CONFIG_ASYNC_MM_TEARDOWN` config option. The code modifications are in `kernel/fork.c`, `kernel/exit.c`, and `mm/oom_kill.c`. Let's ensure the relevant code paths can be executed; that includes verifying the sysctl and config option. A quick review… the config is `ASYNC_MM_TEARDOWN`. Okay, good. Now, about those functions. I need to be careful. While `mmput_exit` is directly involved, it's called from `exit_mm`, which is a frequently accessed path. The new logic is in `mmput_exit` itself though, so let's include it. `mm_reaper` is the kthread, it's definitely on the list. `async_mm_teardown_queue`, `async_mm_teardown_reserve`, and `async_mm_teardown_eligible` seem to be the core of the async logic. Alright, the plan is to call `set-results` with: * **WorthFuzzing**: `true` (Absolutely. This is important.) * **FocusSymbols**: `["mm_reaper", "async_mm_teardown_queue", "async_mm_teardown_reserve", "async_mm_teardown_eligible", "mmput_exit"]` (I'm leaning towards `mmput_exit` as well because it's where the new logic intersects with the hot path) * **EnableConfigs**: `["ASYNC_MM_TEARDOWN"]` (This is the critical config to enable the feature.) * **Reasoning**: "The patch introduces a new feature to asynchronously tear down the address space of exiting processes via a dedicated kernel thread (`mm_reaper`). This modifies core process exit and memory management logic, which is highly functional and should be fuzzed." I'm double-checking the code again to be thorough. It's easy to miss something when you're focusing on the critical path. Wait a second… `mmput_exit` is invoked on every task exit. I want to avoid hot paths, but `mmput_exit` is the entry point for the new logic. `mm_reaper`, `async_mm_teardown_queue`, `async_mm_teardown_reserve`, and `async_mm_teardown_eligible` seems like a better focus, let's keep it tight on the new additions. One more review. No other crucial config dependencies. Okay, it looks like `ASYNC_MM_TEARDOWN` is the main one. Let's call `set-results`. |
| 4/2 | 2026/07/19 19:41 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"ASYNC_MM_TEARDOWN"
],
"FocusSymbols": [
"mm_reaper",
"async_mm_teardown_queue",
"async_mm_teardown_reserve",
"async_mm_teardown_eligible"
],
"Reasoning": "The patch introduces a new feature to asynchronously tear down the address space of exiting processes via a dedicated kernel thread (mm_reaper). This modifies core process exit and memory management logic, which is highly functional and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"ASYNC_MM_TEARDOWN"
],
"FocusSymbols": [
"mm_reaper",
"async_mm_teardown_queue",
"async_mm_teardown_reserve",
"async_mm_teardown_eligible"
],
"Reasoning": "The patch introduces a new feature to asynchronously tear down the address space of exiting processes via a dedicated kernel thread (mm_reaper). This modifies core process exit and memory management logic, which is highly functional and 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) |
|---|