diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-20 20:05:59 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-20 20:05:59 -0700 |
| commit | 8dbbc7e188949b6e1f6be4e82a44dd1d8b1d6c8b (patch) | |
| tree | efc33f6ebb6b7081c54f2a7dc4c0efea50e5516a | |
| parent | ed3b875bea55a3ec4837113356df2ead11115af9 (diff) | |
| parent | aedf2efd18977e0cef7eb963166e2b1fcc0aa321 (diff) | |
Merge tag 'mm-hotfixes-stable-2026-08-19-21-33' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull hotfixes from Andrew Morton:
"8 hotfixes. 5 are cc:stable. 5 are for MM.
All are singletons, please see their changelogs for details"
* tag 'mm-hotfixes-stable-2026-08-19-21-33' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm:
mm/pagewalk: fix stale walk->action escaping walk_pmd_range()
mm, swap: don't free a hibernation slot that is in the swap cache
mm: memcg-v1: fix memsw and TCP failcnt accounting
mm/vmscan: report RCU-tasks quiescent states in shrink_lruvec()
mailmap: add entries for Guodong Xu
MAINTAINERS, mailmap: update email address for JP Kobryn
MAINTAINERS: remove git URL for Squashfs
memcg: keep folio's objcg same as its node
| -rw-r--r-- | .mailmap | 3 | ||||
| -rw-r--r-- | MAINTAINERS | 3 | ||||
| -rw-r--r-- | mm/memcontrol.c | 102 | ||||
| -rw-r--r-- | mm/pagewalk.c | 6 | ||||
| -rw-r--r-- | mm/swapfile.c | 9 | ||||
| -rw-r--r-- | mm/vmscan.c | 2 |
6 files changed, 100 insertions, 25 deletions
@@ -331,6 +331,8 @@ Gokul Sriram Palanisamy <quic_gokulsri@quicinc.com> <gokulsri@codeaurora.org> Govindaraj Saminathan <quic_gsamin@quicinc.com> <gsamin@codeaurora.org> Guo Ren <guoren@kernel.org> <guoren@linux.alibaba.com> Guo Ren <guoren@kernel.org> <ren_guo@c-sky.com> +Guodong Xu <docular.xu@gmail.com> <guodong.xu@linaro.org> +Guodong Xu <docular.xu@gmail.com> <guodong@riscstar.com> Guru Das Srinagesh <linux@gurudas.dev> Guru Das Srinagesh <linux@gurudas.dev> <quic_gurus@quicinc.com> Guru Das Srinagesh <linux@gurudas.dev> <gurus@codeaurora.org> @@ -465,6 +467,7 @@ Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com> <jorge.ramirez-ortiz@linaro Josh Poimboeuf <jpoimboe@kernel.org> <jpoimboe@redhat.com> Josh Poimboeuf <jpoimboe@kernel.org> <jpoimboe@us.ibm.com> Jouni Malinen <quic_jouni@quicinc.com> <jouni@codeaurora.org> +JP Kobryn <jp.kobryn@linux.dev> <inwardvessel@gmail.com> Juha Yrjola <at solidboot.com> Juha Yrjola <juha.yrjola@nokia.com> Juha Yrjola <juha.yrjola@solidboot.com> diff --git a/MAINTAINERS b/MAINTAINERS index af6228ec9eb5..77159f279621 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4985,7 +4985,7 @@ F: tools/lib/bpf/ BPF [MEMORY MANAGEMENT EXTENSIONS] M: Roman Gushchin <roman.gushchin@linux.dev> -M: JP Kobryn <inwardvessel@gmail.com> +M: JP Kobryn <jp.kobryn@linux.dev> M: Shakeel Butt <shakeel.butt@linux.dev> L: bpf@vger.kernel.org L: linux-mm@kvack.org @@ -25672,7 +25672,6 @@ M: Phillip Lougher <phillip@squashfs.org.uk> L: squashfs-devel@lists.sourceforge.net (subscribers-only) S: Maintained W: http://squashfs.org.uk -T: git git://git.kernel.org/pub/scm/linux/kernel/git/pkl/squashfs-next.git F: Documentation/filesystems/squashfs.rst F: fs/squashfs/ diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 8319ad8c5c23..69b37f63a307 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -2912,10 +2912,9 @@ struct mem_cgroup *mem_cgroup_from_virt(void *p) return folio_memcg_check(virt_to_folio(p)); } -static struct obj_cgroup *__get_obj_cgroup_from_memcg(struct mem_cgroup *memcg) +static struct obj_cgroup *__get_obj_cgroup_from_memcg(struct mem_cgroup *memcg, + int nid) { - int nid = numa_node_id(); - for (; memcg; memcg = parent_mem_cgroup(memcg)) { struct obj_cgroup *objcg = rcu_dereference(memcg->nodeinfo[nid]->objcg); @@ -2926,12 +2925,13 @@ static struct obj_cgroup *__get_obj_cgroup_from_memcg(struct mem_cgroup *memcg) return NULL; } -static inline struct obj_cgroup *get_obj_cgroup_from_memcg(struct mem_cgroup *memcg) +static inline struct obj_cgroup *get_obj_cgroup_from_memcg(struct mem_cgroup *memcg, + int nid) { struct obj_cgroup *objcg; rcu_read_lock(); - objcg = __get_obj_cgroup_from_memcg(memcg); + objcg = __get_obj_cgroup_from_memcg(memcg, nid); rcu_read_unlock(); return objcg; @@ -2975,7 +2975,7 @@ static struct obj_cgroup *current_objcg_update(void) rcu_read_lock(); memcg = mem_cgroup_from_task(current); - objcg = __get_obj_cgroup_from_memcg(memcg); + objcg = __get_obj_cgroup_from_memcg(memcg, numa_node_id()); rcu_read_unlock(); /* @@ -4182,9 +4182,11 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css) page_counter_init(&memcg->swap, &parent->swap, false); #ifdef CONFIG_MEMCG_V1 memcg->memory.track_failcnt = !memcg_on_dfl; + memcg->memsw.track_failcnt = !memcg_on_dfl; WRITE_ONCE(memcg->oom_kill_disable, READ_ONCE(parent->oom_kill_disable)); page_counter_init(&memcg->kmem, &parent->kmem, false); page_counter_init(&memcg->tcpmem, &parent->tcpmem, false); + memcg->tcpmem.track_failcnt = !memcg_on_dfl; #endif } else { init_memcg_stats(); @@ -5138,7 +5140,7 @@ static int charge_memcg(struct folio *folio, struct mem_cgroup *memcg, int ret = 0; struct obj_cgroup *objcg; - objcg = get_obj_cgroup_from_memcg(memcg); + objcg = get_obj_cgroup_from_memcg(memcg, folio_nid(folio)); /* Do not account at the root objcg level. */ if (!obj_cgroup_is_root(objcg)) ret = try_charge_memcg(memcg, gfp, folio_nr_pages(folio)); @@ -5337,6 +5339,46 @@ void __mem_cgroup_uncharge_folios(struct folio_batch *folios) uncharge_batch(&ug); } +/* + * An LRU folio must hold the objcg belonging to its own node. + * + * memcg_reparent_objcgs() reparents a dying cgroup one node at a time: the + * folios on that node's LRU lists move to the parent and that node's objcg is + * redirected to the parent, atomically under the node's lru_lock. + * folio_lruvec_lock() relies on this to provide a stable folio<->lruvec + * binding. If a folio holds another node's objcg, its list membership and its + * lruvec resolution change in separate lock sections, and an LRU operation in + * between can re-add the folio to, and strand it on, the LRU list of a dead + * memcg. + * + * So when migration transfers the memcg state to a folio on another node, + * re-derive the objcg for the destination node. If the memcg is dying and the + * destination node has already been reparented, the lookup walks up to the + * nearest live ancestor - which is also where that node's LRU lists went. + * + * Returns the objcg to commit to @new, with a reference for the caller. + */ +static struct obj_cgroup *get_migration_objcg(struct folio *old, + struct folio *new) +{ + struct obj_cgroup *old_objcg, *new_objcg; + int new_nid = folio_nid(new); + + old_objcg = get_obj_cgroup_from_folio(old); + + if (folio_nid(old) == new_nid) + return old_objcg; + + rcu_read_lock(); + new_objcg = __get_obj_cgroup_from_memcg(obj_cgroup_memcg(old_objcg), + new_nid); + rcu_read_unlock(); + + obj_cgroup_put(old_objcg); + + return new_objcg; +} + /** * mem_cgroup_replace_folio - Charge a folio's replacement. * @old: Currently circulating folio. @@ -5365,21 +5407,28 @@ void mem_cgroup_replace_folio(struct folio *old, struct folio *new) if (folio_memcg_charged(new)) return; - objcg = folio_objcg(old); - VM_WARN_ON_ONCE_FOLIO(!objcg, old); - if (!objcg) + VM_WARN_ON_ONCE_FOLIO(!folio_objcg(old), old); + if (!folio_objcg(old)) return; + objcg = get_migration_objcg(old, new); + rcu_read_lock(); memcg = obj_cgroup_memcg(objcg); - /* Force-charge the new page. The old one will be freed soon */ + + /* + * Force-charge the new page. The old one will be freed soon. + * + * The rootness of the committed objcg decides whether the final + * uncharge of @new goes through the page counters (see + * uncharge_folio()); charge them only if the uncharge will. + */ if (!obj_cgroup_is_root(objcg)) { page_counter_charge(&memcg->memory, nr_pages); if (do_memsw_account()) page_counter_charge(&memcg->memsw, nr_pages); } - obj_cgroup_get(objcg); commit_charge(new, objcg); memcg1_commit_charge(new, memcg); rcu_read_unlock(); @@ -5391,14 +5440,15 @@ void mem_cgroup_replace_folio(struct folio *old, struct folio *new) * @new: Replacement folio. * * Transfer the memcg data from the old folio to the new folio for migration. - * The old folio's data info will be cleared. Note that the memory counters - * will remain unchanged throughout the process. + * The old folio's data info will be cleared. The memory counters remain + * unchanged, unless the charge moves out of a fully reparented ancestry + * and has to be settled (see below). * * Both folios must be locked, @new->mapping must be set up. */ void mem_cgroup_migrate(struct folio *old, struct folio *new) { - struct obj_cgroup *objcg; + struct obj_cgroup *objcg, *new_objcg; VM_BUG_ON_FOLIO(!folio_test_locked(old), old); VM_BUG_ON_FOLIO(!folio_test_locked(new), new); @@ -5419,12 +5469,30 @@ void mem_cgroup_migrate(struct folio *old, struct folio *new) if (!objcg) return; - /* Transfer the charge and the objcg ref */ - commit_charge(new, objcg); + new_objcg = get_migration_objcg(old, new); + + /* + * @old was charged through a non-root objcg, so its charge is in the + * page counters. If the re-derivation walked up to the root objcg - + * @old's entire ancestry is dying and already reparented - the final + * uncharge of @new will skip the page counters (see uncharge_folio()). + * Settle them now: this is @old's eventual uncharge, moved up to the + * point where its charge record ends. + */ + if (obj_cgroup_is_root(new_objcg) && !obj_cgroup_is_root(objcg)) { + rcu_read_lock(); + memcg_uncharge(obj_cgroup_memcg(objcg), folio_nr_pages(old)); + rcu_read_unlock(); + } + + commit_charge(new, new_objcg); /* Warning should never happen, so don't worry about refcount non-0 */ WARN_ON_ONCE(folio_unqueue_deferred_split(old)); old->memcg_data = 0; + + /* @new holds its own reference now, drop @old's */ + obj_cgroup_put(objcg); } DEFINE_STATIC_KEY_FALSE(memcg_sockets_enabled_key); diff --git a/mm/pagewalk.c b/mm/pagewalk.c index ed4860c01936..cc07fcf50e87 100644 --- a/mm/pagewalk.c +++ b/mm/pagewalk.c @@ -126,6 +126,7 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end, pmd = pmd_offset(pud, addr); do { again: + walk->action = ACTION_SUBTREE; next = pmd_addr_end(addr, end); if (pmd_none(*pmd)) { if (has_install) @@ -138,8 +139,6 @@ again: continue; } - walk->action = ACTION_SUBTREE; - /* * This implies that each ->pmd_entry() handler * needs to know about pmd_trans_huge() pmds @@ -196,6 +195,7 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end, pud = pud_offset(p4d, addr); do { again: + walk->action = ACTION_SUBTREE; next = pud_addr_end(addr, end); if (pud_none(*pud)) { if (has_install) @@ -208,8 +208,6 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end, continue; } - walk->action = ACTION_SUBTREE; - if (ops->pud_entry) err = ops->pud_entry(pud, addr, next, walk); if (err) diff --git a/mm/swapfile.c b/mm/swapfile.c index 095d43838d4e..4e07d457e261 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -2202,7 +2202,14 @@ void swap_free_hibernation_slot(swp_entry_t entry) ci = swap_cluster_lock(si, offset); __swap_cluster_put_entry(ci, offset % SWAPFILE_CLUSTER); - __swap_cluster_free_entries(si, ci, offset % SWAPFILE_CLUSTER, 1); + /* + * A slot with a folio in the swap cache is freed when the folio + * leaves the cache, the same rule swap_put_entries_cluster() follows. + * Readahead can put a folio here, and freeing the slot now would + * leave that folio with no entry behind it. + */ + if (!swp_tb_is_folio(__swap_table_get(ci, offset % SWAPFILE_CLUSTER))) + __swap_cluster_free_entries(si, ci, offset % SWAPFILE_CLUSTER, 1); swap_cluster_unlock(ci); /* In theory readahead might add it to the swap cache by accident */ diff --git a/mm/vmscan.c b/mm/vmscan.c index 4243f72a9740..4ca9775ceee8 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -5948,7 +5948,7 @@ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc) } } - cond_resched(); + cond_resched_tasks_rcu_qs(); if (nr_reclaimed < nr_to_reclaim || proportional_reclaim) continue; |
