]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mm: list_lru: introduce caller locking for additions and deletions
authorJohannes Weiner <hannes@cmpxchg.org>
Wed, 27 May 2026 20:45:13 +0000 (16:45 -0400)
committerAndrew Morton <akpm@linux-foundation.org>
Tue, 9 Jun 2026 01:21:24 +0000 (18:21 -0700)
Locking is currently internal to the list_lru API.  However, a caller
might want to keep auxiliary state synchronized with the LRU state.

For example, the THP shrinker uses the lock of its custom LRU to keep
PG_partially_mapped and vmstats consistent.

To allow the THP shrinker to switch to list_lru, provide normal and
irqsafe locking primitives as well as caller-locked variants of the
addition and deletion functions.

Link: https://lore.kernel.org/20260527204757.2544958-7-hannes@cmpxchg.org
Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
Acked-by: Shakeel Butt <shakeel.butt@linux.dev>
Reviewed-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
Reviewed-by: Liam R. Howlett (Oracle) <liam@infradead.org>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Dave Chinner <david@fromorbit.com>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Kairui Song <ryncsn@gmail.com>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Nico Pache <npache@redhat.com>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Usama Arif <usama.arif@linux.dev>
Cc: Vasily Gorbik <gor@linux.ibm.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/list_lru.h
mm/list_lru.c

index fe739d35a864cdcc3b5fa8c79266c8f3f455fe83..134cb3e5652a22aff398a534561cc1ec0f9daa50 100644 (file)
@@ -83,6 +83,46 @@ int memcg_list_lru_alloc(struct mem_cgroup *memcg, struct list_lru *lru,
                         gfp_t gfp);
 void memcg_reparent_list_lrus(struct mem_cgroup *memcg, struct mem_cgroup *parent);
 
+/**
+ * list_lru_lock: lock the sublist for the given node and memcg
+ * @lru: the lru pointer
+ * @nid: the node id of the sublist to lock.
+ * @memcg: pointer to the cgroup of the sublist to lock. On return,
+ *         updated to the cgroup whose sublist was actually locked,
+ *         which may be an ancestor if the original memcg was dying.
+ *
+ * Returns the locked list_lru_one sublist. The caller must call
+ * list_lru_unlock() when done.
+ *
+ * You must ensure that the memcg is not freed during this call (e.g., with
+ * rcu or by taking a css refcnt).
+ *
+ * Return: the locked list_lru_one, or NULL on failure
+ */
+struct list_lru_one *list_lru_lock(struct list_lru *lru, int nid,
+               struct mem_cgroup **memcg);
+
+/**
+ * list_lru_unlock: unlock a sublist locked by list_lru_lock()
+ * @l: the list_lru_one to unlock
+ */
+void list_lru_unlock(struct list_lru_one *l);
+
+struct list_lru_one *list_lru_lock_irq(struct list_lru *lru, int nid,
+               struct mem_cgroup **memcg);
+void list_lru_unlock_irq(struct list_lru_one *l);
+
+struct list_lru_one *list_lru_lock_irqsave(struct list_lru *lru, int nid,
+               struct mem_cgroup **memcg, unsigned long *irq_flags);
+void list_lru_unlock_irqrestore(struct list_lru_one *l,
+               unsigned long *irq_flags);
+
+/* Caller-locked variants, see list_lru_add() etc for documentation */
+bool __list_lru_add(struct list_lru *lru, struct list_lru_one *l,
+               struct list_head *item, int nid, struct mem_cgroup *memcg);
+bool __list_lru_del(struct list_lru *lru, struct list_lru_one *l,
+               struct list_head *item, int nid);
+
 /**
  * list_lru_add: add an element to the lru list's tail
  * @lru: the lru pointer
@@ -115,6 +155,9 @@ void memcg_reparent_list_lrus(struct mem_cgroup *memcg, struct mem_cgroup *paren
 bool list_lru_add(struct list_lru *lru, struct list_head *item, int nid,
                    struct mem_cgroup *memcg);
 
+bool list_lru_add_irq(struct list_lru *lru, struct list_head *item, int nid,
+                     struct mem_cgroup *memcg);
+
 /**
  * list_lru_add_obj: add an element to the lru list's tail
  * @lru: the lru pointer
index fdb3fe2ea64fb4d55f121d70758c2f13855f3d3b..402bb028114d96b72965124993c16f9278824119 100644 (file)
 #include "slab.h"
 #include "internal.h"
 
-static inline void lock_list_lru(struct list_lru_one *l, bool irq)
+static inline void lock_list_lru(struct list_lru_one *l, bool irq,
+                                unsigned long *irq_flags)
 {
-       if (irq)
+       if (irq_flags)
+               spin_lock_irqsave(&l->lock, *irq_flags);
+       else if (irq)
                spin_lock_irq(&l->lock);
        else
                spin_lock(&l->lock);
 }
 
-static inline void unlock_list_lru(struct list_lru_one *l, bool irq_off)
+static inline void unlock_list_lru(struct list_lru_one *l, bool irq_off,
+                                  unsigned long *irq_flags)
 {
-       if (irq_off)
+       if (irq_flags)
+               spin_unlock_irqrestore(&l->lock, *irq_flags);
+       else if (irq_off)
                spin_unlock_irq(&l->lock);
        else
                spin_unlock(&l->lock);
@@ -78,7 +84,8 @@ list_lru_from_memcg_idx(struct list_lru *lru, int nid, int idx)
 
 static inline struct list_lru_one *
 lock_list_lru_of_memcg(struct list_lru *lru, int nid,
-                      struct mem_cgroup **memcg, bool irq, bool skip_empty)
+                      struct mem_cgroup **memcg, bool irq,
+                      unsigned long *irq_flags, bool skip_empty)
 {
        struct list_lru_one *l;
 
@@ -86,12 +93,12 @@ lock_list_lru_of_memcg(struct list_lru *lru, int nid,
 again:
        l = list_lru_from_memcg_idx(lru, nid, memcg_kmem_id(*memcg));
        if (likely(l)) {
-               lock_list_lru(l, irq);
+               lock_list_lru(l, irq, irq_flags);
                if (likely(READ_ONCE(l->nr_items) != LONG_MIN)) {
                        rcu_read_unlock();
                        return l;
                }
-               unlock_list_lru(l, irq);
+               unlock_list_lru(l, irq, irq_flags);
        }
        /*
         * Caller may simply bail out if raced with reparenting or
@@ -132,24 +139,58 @@ list_lru_from_memcg_idx(struct list_lru *lru, int nid, int idx)
 
 static inline struct list_lru_one *
 lock_list_lru_of_memcg(struct list_lru *lru, int nid,
-                      struct mem_cgroup **memcg, bool irq, bool skip_empty)
+                      struct mem_cgroup **memcg, bool irq,
+                      unsigned long *irq_flags, bool skip_empty)
 {
        struct list_lru_one *l = &lru->node[nid].lru;
 
-       lock_list_lru(l, irq);
+       lock_list_lru(l, irq, irq_flags);
 
        return l;
 }
 #endif /* CONFIG_MEMCG */
 
-/* The caller must ensure the memcg lifetime. */
-bool list_lru_add(struct list_lru *lru, struct list_head *item, int nid,
-                 struct mem_cgroup *memcg)
+struct list_lru_one *list_lru_lock(struct list_lru *lru, int nid,
+                                  struct mem_cgroup **memcg)
 {
-       struct list_lru_node *nlru = &lru->node[nid];
-       struct list_lru_one *l;
+       return lock_list_lru_of_memcg(lru, nid, memcg, /*irq=*/false,
+                                     /*irq_flags=*/NULL, /*skip_empty=*/false);
+}
+
+void list_lru_unlock(struct list_lru_one *l)
+{
+       unlock_list_lru(l, /*irq_off=*/false, /*irq_flags=*/NULL);
+}
+
+struct list_lru_one *list_lru_lock_irq(struct list_lru *lru, int nid,
+                                      struct mem_cgroup **memcg)
+{
+       return lock_list_lru_of_memcg(lru, nid, memcg, /*irq=*/true,
+                                     /*irq_flags=*/NULL, /*skip_empty=*/false);
+}
+
+void list_lru_unlock_irq(struct list_lru_one *l)
+{
+       unlock_list_lru(l, /*irq_off=*/true, /*irq_flags=*/NULL);
+}
 
-       l = lock_list_lru_of_memcg(lru, nid, &memcg, false, false);
+struct list_lru_one *list_lru_lock_irqsave(struct list_lru *lru, int nid,
+                                          struct mem_cgroup **memcg,
+                                          unsigned long *flags)
+{
+       return lock_list_lru_of_memcg(lru, nid, memcg, /*irq=*/true,
+                                     /*irq_flags=*/flags, /*skip_empty=*/false);
+}
+
+void list_lru_unlock_irqrestore(struct list_lru_one *l, unsigned long *flags)
+{
+       unlock_list_lru(l, /*irq_off=*/true, /*irq_flags=*/flags);
+}
+
+bool __list_lru_add(struct list_lru *lru, struct list_lru_one *l,
+                   struct list_head *item, int nid,
+                   struct mem_cgroup *memcg)
+{
        if (list_empty(item)) {
                list_add_tail(item, &l->list);
                /*
@@ -159,15 +200,50 @@ bool list_lru_add(struct list_lru *lru, struct list_head *item, int nid,
                 */
                if (!l->nr_items++)
                        set_shrinker_bit(memcg, nid, lru_shrinker_id(lru));
-               unlock_list_lru(l, false);
-               atomic_long_inc(&nlru->nr_items);
+               atomic_long_inc(&lru->node[nid].nr_items);
                return true;
        }
-       unlock_list_lru(l, false);
        return false;
 }
 EXPORT_SYMBOL_GPL(list_lru_add);
 
+bool __list_lru_del(struct list_lru *lru, struct list_lru_one *l,
+                   struct list_head *item, int nid)
+{
+       if (!list_empty(item)) {
+               list_del_init(item);
+               l->nr_items--;
+               atomic_long_dec(&lru->node[nid].nr_items);
+               return true;
+       }
+       return false;
+}
+
+/* The caller must ensure the memcg lifetime. */
+bool list_lru_add(struct list_lru *lru, struct list_head *item, int nid,
+                 struct mem_cgroup *memcg)
+{
+       struct list_lru_one *l;
+       bool ret;
+
+       l = list_lru_lock(lru, nid, &memcg);
+       ret = __list_lru_add(lru, l, item, nid, memcg);
+       list_lru_unlock(l);
+       return ret;
+}
+
+bool list_lru_add_irq(struct list_lru *lru, struct list_head *item,
+                     int nid, struct mem_cgroup *memcg)
+{
+       struct list_lru_one *l;
+       bool ret;
+
+       l = list_lru_lock_irq(lru, nid, &memcg);
+       ret = __list_lru_add(lru, l, item, nid, memcg);
+       list_lru_unlock_irq(l);
+       return ret;
+}
+
 bool list_lru_add_obj(struct list_lru *lru, struct list_head *item)
 {
        bool ret;
@@ -189,19 +265,13 @@ EXPORT_SYMBOL_GPL(list_lru_add_obj);
 bool list_lru_del(struct list_lru *lru, struct list_head *item, int nid,
                  struct mem_cgroup *memcg)
 {
-       struct list_lru_node *nlru = &lru->node[nid];
        struct list_lru_one *l;
+       bool ret;
 
-       l = lock_list_lru_of_memcg(lru, nid, &memcg, false, false);
-       if (!list_empty(item)) {
-               list_del_init(item);
-               l->nr_items--;
-               unlock_list_lru(l, false);
-               atomic_long_dec(&nlru->nr_items);
-               return true;
-       }
-       unlock_list_lru(l, false);
-       return false;
+       l = list_lru_lock(lru, nid, &memcg);
+       ret = __list_lru_del(lru, l, item, nid);
+       list_lru_unlock(l);
+       return ret;
 }
 
 bool list_lru_del_obj(struct list_lru *lru, struct list_head *item)
@@ -274,7 +344,8 @@ __list_lru_walk_one(struct list_lru *lru, int nid, struct mem_cgroup *memcg,
        unsigned long isolated = 0;
 
 restart:
-       l = lock_list_lru_of_memcg(lru, nid, &memcg, irq_off, true);
+       l = lock_list_lru_of_memcg(lru, nid, &memcg, /*irq=*/irq_off,
+                                  /*irq_flags=*/NULL, /*skip_empty=*/true);
        if (!l)
                return isolated;
        list_for_each_safe(item, n, &l->list) {
@@ -315,7 +386,7 @@ restart:
                        BUG();
                }
        }
-       unlock_list_lru(l, irq_off);
+       unlock_list_lru(l, irq_off, NULL);
 out:
        return isolated;
 }