[PATCH -mm 4/6] list_lru: add per-memcg lists

From: Vladimir Davydov
Date: Mon Jul 28 2014 - 05:32:49 EST


There are several FS shrinkers, including super_block::s_shrink, that
keep reclaimable objects in the list_lru structure. Hence to turn them
to memcg-aware shrinkers, it is enough to make list_lru per-memcg.

This patch does the trick. It adds an array of LRU lists to the list_lru
structure, one for each kmem-active memcg, and dispatches every item
addition or removal operation to the list corresponding to the memcg the
item is accounted to.

To make a list_lru user memcg-aware, it's enough to pass
memcg_aware=true to list_lru_init, everything else is done
automatically.

Note, this patch removes VM_BUG_ON(!current->mm) from
memcg_{stop,resume}_kmem_account. This is, because these functions may
be invoked by memcg_register_list_lru while mounting filesystems on
early init, where we don't have ->mm yet. Calling them from kernel
threads won't hurt anyway.

Signed-off-by: Vladimir Davydov <vdavydov@xxxxxxxxxxxxx>
---
fs/gfs2/main.c | 2 +-
fs/super.c | 4 +-
fs/xfs/xfs_buf.c | 2 +-
fs/xfs/xfs_qm.c | 2 +-
include/linux/list_lru.h | 86 ++++++++++--------
include/linux/memcontrol.h | 42 +++++++++
mm/list_lru.c | 132 +++++++++++++++++++++++-----
mm/memcontrol.c | 208 ++++++++++++++++++++++++++++++++++++++++----
mm/workingset.c | 3 +-
9 files changed, 402 insertions(+), 79 deletions(-)

diff --git a/fs/gfs2/main.c b/fs/gfs2/main.c
index 82b6ac829656..fb51e99a0281 100644
--- a/fs/gfs2/main.c
+++ b/fs/gfs2/main.c
@@ -84,7 +84,7 @@ static int __init init_gfs2_fs(void)
if (error)
return error;

- error = list_lru_init(&gfs2_qd_lru);
+ error = list_lru_init(&gfs2_qd_lru, false);
if (error)
goto fail_lru;

diff --git a/fs/super.c b/fs/super.c
index 1f34321e15b4..477102d59c7e 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -187,9 +187,9 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags)
INIT_HLIST_BL_HEAD(&s->s_anon);
INIT_LIST_HEAD(&s->s_inodes);

- if (list_lru_init(&s->s_dentry_lru))
+ if (list_lru_init(&s->s_dentry_lru, false))
goto fail;
- if (list_lru_init(&s->s_inode_lru))
+ if (list_lru_init(&s->s_inode_lru, false))
goto fail;

init_rwsem(&s->s_umount);
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 1a5e178fd8d0..405ff6044a60 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -1669,7 +1669,7 @@ xfs_alloc_buftarg(
if (xfs_setsize_buftarg_early(btp, bdev))
goto error;

- if (list_lru_init(&btp->bt_lru))
+ if (list_lru_init(&btp->bt_lru, false))
goto error;

btp->bt_shrinker.count_objects = xfs_buftarg_shrink_count;
diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c
index 76640cd73a23..cb7267297783 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -670,7 +670,7 @@ xfs_qm_init_quotainfo(

qinf = mp->m_quotainfo = kmem_zalloc(sizeof(xfs_quotainfo_t), KM_SLEEP);

- error = list_lru_init(&qinf->qi_lru);
+ error = list_lru_init(&qinf->qi_lru, false);
if (error)
goto out_free_qinf;

diff --git a/include/linux/list_lru.h b/include/linux/list_lru.h
index f500a2e39b13..cf1e73825431 100644
--- a/include/linux/list_lru.h
+++ b/include/linux/list_lru.h
@@ -11,6 +11,8 @@
#include <linux/nodemask.h>
#include <linux/shrinker.h>

+struct list_lru;
+
/* list_lru_walk_cb has to always return one of those */
enum lru_status {
LRU_REMOVED, /* item removed from list */
@@ -29,16 +31,50 @@ struct list_lru_node {
long nr_items;
} ____cacheline_aligned_in_smp;

+struct memcg_list_lru_params {
+ /* list_lru which this struct is for */
+ struct list_lru *owner;
+
+ /* list node for connecting to the list of all memcg-aware lrus */
+ struct list_head list;
+
+ struct rcu_head rcu_head;
+
+ /* array of per-memcg lrus, indexed by mem_cgroup->kmemcg_id */
+ struct list_lru_node *node[0];
+};
+
struct list_lru {
struct list_lru_node *node;
nodemask_t active_nodes;
+#ifdef CONFIG_MEMCG_KMEM
+ struct memcg_list_lru_params *memcg_params;
+#endif
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+ struct lock_class_key *key;
+#endif
};

+#ifdef CONFIG_MEMCG_KMEM
+static inline bool list_lru_memcg_aware(struct list_lru *lru)
+{
+ return !!lru->memcg_params;
+}
+#else
+static inline bool list_lru_memcg_aware(struct list_lru *lru)
+{
+ return false;
+}
+#endif
+
+void list_lru_node_init(struct list_lru_node *n, struct list_lru *lru);
+
void list_lru_destroy(struct list_lru *lru);
-int list_lru_init_key(struct list_lru *lru, struct lock_class_key *key);
-static inline int list_lru_init(struct list_lru *lru)
+int list_lru_init_key(struct list_lru *lru, bool memcg_aware,
+ struct lock_class_key *key);
+static inline int list_lru_init(struct list_lru *lru, bool memcg_aware)
{
- return list_lru_init_key(lru, NULL);
+ return list_lru_init_key(lru, memcg_aware, NULL);
}

/**
@@ -76,28 +112,20 @@ bool list_lru_del(struct list_lru *lru, struct list_head *item);
* list_lru_count_node: return the number of objects currently held by @lru
* @lru: the lru pointer.
* @nid: the node id to count from.
+ * @memcg: the memcg to count from
*
* Always return a non-negative number, 0 for empty lists. There is no
* guarantee that the list is not updated while the count is being computed.
* Callers that want such a guarantee need to provide an outer lock.
*/
-unsigned long list_lru_count_node(struct list_lru *lru, int nid);
+unsigned long list_lru_count_node(struct list_lru *lru,
+ int nid, struct mem_cgroup *memcg);
+unsigned long list_lru_count(struct list_lru *lru);

static inline unsigned long list_lru_shrink_count(struct list_lru *lru,
struct shrink_control *sc)
{
- return list_lru_count_node(lru, sc->nid);
-}
-
-static inline unsigned long list_lru_count(struct list_lru *lru)
-{
- long count = 0;
- int nid;
-
- for_each_node_mask(nid, lru->active_nodes)
- count += list_lru_count_node(lru, nid);
-
- return count;
+ return list_lru_count_node(lru, sc->nid, sc->memcg);
}

typedef enum lru_status
@@ -106,6 +134,7 @@ typedef enum lru_status
* list_lru_walk_node: walk a list_lru, isolating and disposing freeable items.
* @lru: the lru pointer.
* @nid: the node id to scan from.
+ * @memcg: the memcg to scan from.
* @isolate: callback function that is resposible for deciding what to do with
* the item currently being scanned
* @cb_arg: opaque type that will be passed to @isolate
@@ -123,31 +152,18 @@ typedef enum lru_status
*
* Return value: the number of objects effectively removed from the LRU.
*/
-unsigned long list_lru_walk_node(struct list_lru *lru, int nid,
+unsigned long list_lru_walk_node(struct list_lru *lru,
+ int nid, struct mem_cgroup *memcg,
list_lru_walk_cb isolate, void *cb_arg,
unsigned long *nr_to_walk);
+unsigned long list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate,
+ void *cb_arg, unsigned long nr_to_walk);

static inline unsigned long
list_lru_shrink_walk(struct list_lru *lru, struct shrink_control *sc,
list_lru_walk_cb isolate, void *cb_arg)
{
- return list_lru_walk_node(lru, sc->nid, isolate, cb_arg,
- &sc->nr_to_scan);
-}
-
-static inline unsigned long
-list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate,
- void *cb_arg, unsigned long nr_to_walk)
-{
- long isolated = 0;
- int nid;
-
- for_each_node_mask(nid, lru->active_nodes) {
- isolated += list_lru_walk_node(lru, nid, isolate,
- cb_arg, &nr_to_walk);
- if (nr_to_walk <= 0)
- break;
- }
- return isolated;
+ return list_lru_walk_node(lru, sc->nid, sc->memcg,
+ isolate, cb_arg, &sc->nr_to_scan);
}
#endif /* _LRU_LIST_H */
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index d0f3d8f0990c..962e36cb95ae 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -53,6 +53,21 @@ struct mem_cgroup_reclaim_cookie {
unsigned int generation;
};

+/*
+ * Iteration constructs for visiting all cgroups (under a tree). If
+ * loops are exited prematurely (break), mem_cgroup_iter_break() must
+ * be used for reference counting.
+ */
+#define for_each_mem_cgroup_tree(iter, root) \
+ for (iter = mem_cgroup_iter(root, NULL, NULL); \
+ iter != NULL; \
+ iter = mem_cgroup_iter(root, iter, NULL))
+
+#define for_each_mem_cgroup(iter) \
+ for (iter = mem_cgroup_iter(NULL, NULL, NULL); \
+ iter != NULL; \
+ iter = mem_cgroup_iter(NULL, iter, NULL))
+
#ifdef CONFIG_MEMCG
int mem_cgroup_try_charge(struct page *page, struct mm_struct *mm,
gfp_t gfp_mask, struct mem_cgroup **memcgp);
@@ -459,6 +474,12 @@ void memcg_free_cache_params(struct kmem_cache *s);
int memcg_update_cache_size(struct kmem_cache *s, int num_groups);
void memcg_update_array_size(int num_groups);

+int memcg_register_list_lru(struct list_lru *lru);
+void memcg_unregister_list_lru(struct list_lru *lru);
+struct list_lru_node *memcg_list_lru(struct list_lru *lru,
+ struct mem_cgroup *memcg);
+struct list_lru_node *memcg_list_lru_from_obj(struct list_lru *lru, void *obj);
+
struct kmem_cache *
__memcg_kmem_get_cache(struct kmem_cache *cachep, gfp_t gfp);

@@ -611,6 +632,27 @@ memcg_kmem_get_cache(struct kmem_cache *cachep, gfp_t gfp)
{
return cachep;
}
+
+static inline int memcg_register_list_lru(struct list_lru *lru)
+{
+ return 0;
+}
+
+static inline void memcg_unregister_list_lru(struct list_lru *lru)
+{
+}
+
+static inline struct list_lru_node *memcg_list_lru(struct list_lru *lru,
+ struct mem_cgroup *memcg)
+{
+ return NULL;
+}
+
+static inline struct list_lru_node *memcg_list_lru_from_obj(struct list_lru *lru,
+ void *obj)
+{
+ return NULL;
+}
#endif /* CONFIG_MEMCG_KMEM */
#endif /* _LINUX_MEMCONTROL_H */

diff --git a/mm/list_lru.c b/mm/list_lru.c
index f1a0db194173..b914f0930c67 100644
--- a/mm/list_lru.c
+++ b/mm/list_lru.c
@@ -9,17 +9,24 @@
#include <linux/mm.h>
#include <linux/list_lru.h>
#include <linux/slab.h>
+#include <linux/memcontrol.h>

bool list_lru_add(struct list_lru *lru, struct list_head *item)
{
- int nid = page_to_nid(virt_to_page(item));
- struct list_lru_node *nlru = &lru->node[nid];
+ int nid = -1;
+ struct list_lru_node *nlru;
+
+ nlru = memcg_list_lru_from_obj(lru, item);
+ if (!nlru) {
+ nid = page_to_nid(virt_to_page(item));
+ nlru = &lru->node[nid];
+ }

spin_lock(&nlru->lock);
WARN_ON_ONCE(nlru->nr_items < 0);
if (list_empty(item)) {
list_add_tail(item, &nlru->list);
- if (nlru->nr_items++ == 0)
+ if (nlru->nr_items++ == 0 && nid >= 0)
node_set(nid, lru->active_nodes);
spin_unlock(&nlru->lock);
return true;
@@ -31,13 +38,19 @@ EXPORT_SYMBOL_GPL(list_lru_add);

bool list_lru_del(struct list_lru *lru, struct list_head *item)
{
- int nid = page_to_nid(virt_to_page(item));
- struct list_lru_node *nlru = &lru->node[nid];
+ int nid = -1;
+ struct list_lru_node *nlru;
+
+ nlru = memcg_list_lru_from_obj(lru, item);
+ if (!nlru) {
+ nid = page_to_nid(virt_to_page(item));
+ nlru = &lru->node[nid];
+ }

spin_lock(&nlru->lock);
if (!list_empty(item)) {
list_del_init(item);
- if (--nlru->nr_items == 0)
+ if (--nlru->nr_items == 0 && nid >= 0)
node_clear(nid, lru->active_nodes);
WARN_ON_ONCE(nlru->nr_items < 0);
spin_unlock(&nlru->lock);
@@ -48,12 +61,18 @@ bool list_lru_del(struct list_lru *lru, struct list_head *item)
}
EXPORT_SYMBOL_GPL(list_lru_del);

-unsigned long
-list_lru_count_node(struct list_lru *lru, int nid)
+unsigned long list_lru_count_node(struct list_lru *lru,
+ int nid, struct mem_cgroup *memcg)
{
unsigned long count = 0;
struct list_lru_node *nlru = &lru->node[nid];

+ if (memcg) {
+ nlru = memcg_list_lru(lru, memcg);
+ if (!nlru)
+ return 0;
+ }
+
spin_lock(&nlru->lock);
WARN_ON_ONCE(nlru->nr_items < 0);
count += nlru->nr_items;
@@ -63,15 +82,41 @@ list_lru_count_node(struct list_lru *lru, int nid)
}
EXPORT_SYMBOL_GPL(list_lru_count_node);

-unsigned long
-list_lru_walk_node(struct list_lru *lru, int nid, list_lru_walk_cb isolate,
- void *cb_arg, unsigned long *nr_to_walk)
+unsigned long list_lru_count(struct list_lru *lru)
+{
+ long count = 0;
+ int nid;
+
+ for_each_node_mask(nid, lru->active_nodes)
+ count += list_lru_count_node(lru, nid, NULL);
+
+ if (list_lru_memcg_aware(lru)) {
+ struct mem_cgroup *memcg;
+
+ for_each_mem_cgroup(memcg)
+ count += list_lru_count_node(lru, 0, memcg);
+ }
+ return count;
+}
+EXPORT_SYMBOL_GPL(list_lru_count);
+
+unsigned long list_lru_walk_node(struct list_lru *lru,
+ int nid, struct mem_cgroup *memcg,
+ list_lru_walk_cb isolate, void *cb_arg,
+ unsigned long *nr_to_walk)
{

- struct list_lru_node *nlru = &lru->node[nid];
+ struct list_lru_node *nlru = &lru->node[nid];
struct list_head *item, *n;
unsigned long isolated = 0;

+ if (memcg) {
+ nlru = memcg_list_lru(lru, memcg);
+ if (!nlru)
+ return 0;
+ nid = -1;
+ }
+
spin_lock(&nlru->lock);
restart:
list_for_each_safe(item, n, &nlru->list) {
@@ -90,7 +135,7 @@ restart:
case LRU_REMOVED_RETRY:
assert_spin_locked(&nlru->lock);
case LRU_REMOVED:
- if (--nlru->nr_items == 0)
+ if (--nlru->nr_items == 0 && nid >= 0)
node_clear(nid, lru->active_nodes);
WARN_ON_ONCE(nlru->nr_items < 0);
isolated++;
@@ -124,7 +169,47 @@ restart:
}
EXPORT_SYMBOL_GPL(list_lru_walk_node);

-int list_lru_init_key(struct list_lru *lru, struct lock_class_key *key)
+unsigned long list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate,
+ void *cb_arg, unsigned long nr_to_walk)
+{
+ long isolated = 0;
+ int nid;
+
+ for_each_node_mask(nid, lru->active_nodes) {
+ isolated += list_lru_walk_node(lru, nid, NULL,
+ isolate, cb_arg, &nr_to_walk);
+ if (nr_to_walk <= 0)
+ break;
+ }
+ if (list_lru_memcg_aware(lru)) {
+ struct mem_cgroup *memcg;
+
+ for_each_mem_cgroup(memcg) {
+ isolated += list_lru_walk_node(lru, 0, memcg,
+ isolate, cb_arg, &nr_to_walk);
+ if (nr_to_walk <= 0) {
+ mem_cgroup_iter_break(NULL, memcg);
+ break;
+ }
+ }
+ }
+ return isolated;
+}
+EXPORT_SYMBOL_GPL(list_lru_walk);
+
+void list_lru_node_init(struct list_lru_node *n, struct list_lru *lru)
+{
+ spin_lock_init(&n->lock);
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+ if (lru->key)
+ lockdep_set_class(&n->lock, lru->key);
+#endif
+ INIT_LIST_HEAD(&n->list);
+ n->nr_items = 0;
+}
+
+int list_lru_init_key(struct list_lru *lru, bool memcg_aware,
+ struct lock_class_key *key)
{
int i;
size_t size = sizeof(*lru->node) * nr_node_ids;
@@ -133,13 +218,19 @@ int list_lru_init_key(struct list_lru *lru, struct lock_class_key *key)
if (!lru->node)
return -ENOMEM;

+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+ lru->key = key;
+#endif
nodes_clear(lru->active_nodes);
- for (i = 0; i < nr_node_ids; i++) {
- spin_lock_init(&lru->node[i].lock);
- if (key)
- lockdep_set_class(&lru->node[i].lock, key);
- INIT_LIST_HEAD(&lru->node[i].list);
- lru->node[i].nr_items = 0;
+ for (i = 0; i < nr_node_ids; i++)
+ list_lru_node_init(&lru->node[i], lru);
+
+#ifdef CONFIG_MEMCG_KMEM
+ lru->memcg_params = NULL;
+#endif
+ if (memcg_aware && memcg_register_list_lru(lru)) {
+ list_lru_destroy(lru);
+ return -ENOMEM;
}
return 0;
}
@@ -147,6 +238,7 @@ EXPORT_SYMBOL_GPL(list_lru_init_key);

void list_lru_destroy(struct list_lru *lru)
{
+ memcg_unregister_list_lru(lru);
kfree(lru->node);
}
EXPORT_SYMBOL_GPL(list_lru_destroy);
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 6a96a3994692..1030bba4b94f 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1268,21 +1268,6 @@ void mem_cgroup_iter_break(struct mem_cgroup *root,
css_put(&prev->css);
}

-/*
- * Iteration constructs for visiting all cgroups (under a tree). If
- * loops are exited prematurely (break), mem_cgroup_iter_break() must
- * be used for reference counting.
- */
-#define for_each_mem_cgroup_tree(iter, root) \
- for (iter = mem_cgroup_iter(root, NULL, NULL); \
- iter != NULL; \
- iter = mem_cgroup_iter(root, iter, NULL))
-
-#define for_each_mem_cgroup(iter) \
- for (iter = mem_cgroup_iter(NULL, NULL, NULL); \
- iter != NULL; \
- iter = mem_cgroup_iter(NULL, iter, NULL))
-
void __mem_cgroup_count_vm_event(struct mm_struct *mm, enum vm_event_item idx)
{
struct mem_cgroup *memcg;
@@ -3140,13 +3125,11 @@ static void memcg_unregister_cache(struct kmem_cache *cachep)
*/
static inline void memcg_stop_kmem_account(void)
{
- VM_BUG_ON(!current->mm);
current->memcg_kmem_skip_account++;
}

static inline void memcg_resume_kmem_account(void)
{
- VM_BUG_ON(!current->mm);
current->memcg_kmem_skip_account--;
}

@@ -3436,6 +3419,193 @@ void __memcg_kmem_uncharge_pages(struct page *page, int order)
VM_BUG_ON_PAGE(mem_cgroup_is_root(memcg), page);
memcg_uncharge_kmem(memcg, PAGE_SIZE << order);
}
+
+/*
+ * List of all memcg-aware list_lrus, linked through
+ * memcg_list_lru_params->list, protected by memcg_slab_mutex.
+ */
+static LIST_HEAD(memcg_list_lrus);
+
+static void memcg_free_list_lru_params(struct memcg_list_lru_params *params,
+ int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ kfree(params->node[i]);
+ kfree(params);
+}
+
+static struct memcg_list_lru_params *
+memcg_alloc_list_lru_params(struct list_lru *lru, int size)
+{
+ struct memcg_list_lru_params *params, *old_params;
+ int i, old_size = 0;
+
+ memcg_stop_kmem_account();
+ params = kzalloc(sizeof(*params) + size * sizeof(*params->node),
+ GFP_KERNEL);
+ if (!params)
+ goto out;
+
+ old_params = lru->memcg_params;
+ if (old_params)
+ old_size = memcg_limited_groups_array_size;
+
+ for (i = old_size; i < size; i++) {
+ struct list_lru_node *n;
+
+ n = kmalloc(sizeof(*n), GFP_KERNEL);
+ if (!n) {
+ memcg_free_list_lru_params(params, size);
+ params = NULL;
+ goto out;
+ }
+ list_lru_node_init(n, lru);
+ params->node[i] = n;
+ }
+
+ if (old_params)
+ memcpy(params->node, old_params->node,
+ old_size * sizeof(*params->node));
+out:
+ memcg_resume_kmem_account();
+ return params;
+}
+
+int memcg_register_list_lru(struct list_lru *lru)
+{
+ struct memcg_list_lru_params *params;
+
+ if (mem_cgroup_disabled())
+ return 0;
+
+ BUG_ON(lru->memcg_params);
+
+ mutex_lock(&memcg_slab_mutex);
+ params = memcg_alloc_list_lru_params(lru,
+ memcg_limited_groups_array_size);
+ if (!params) {
+ mutex_unlock(&memcg_slab_mutex);
+ return -ENOMEM;
+ }
+ params->owner = lru;
+ list_add(&params->list, &memcg_list_lrus);
+ lru->memcg_params = params;
+ mutex_unlock(&memcg_slab_mutex);
+
+ return 0;
+}
+
+void memcg_unregister_list_lru(struct list_lru *lru)
+{
+ struct memcg_list_lru_params *params = lru->memcg_params;
+
+ if (!params)
+ return;
+
+ BUG_ON(params->owner != lru);
+
+ mutex_lock(&memcg_slab_mutex);
+ list_del(&params->list);
+ memcg_free_list_lru_params(params, memcg_limited_groups_array_size);
+ mutex_unlock(&memcg_slab_mutex);
+
+ lru->memcg_params = NULL;
+}
+
+static int memcg_update_all_list_lrus(int num_groups)
+{
+ struct memcg_list_lru_params *params, *tmp, *new_params;
+ struct list_lru *lru;
+ int new_size;
+
+ lockdep_assert_held(&memcg_slab_mutex);
+
+ if (num_groups <= memcg_limited_groups_array_size)
+ return 0;
+
+ new_size = memcg_caches_array_size(num_groups);
+
+ list_for_each_entry_safe(params, tmp, &memcg_list_lrus, list) {
+ lru = params->owner;
+
+ new_params = memcg_alloc_list_lru_params(lru, new_size);
+ if (!new_params)
+ return -ENOMEM;
+
+ new_params->owner = lru;
+ list_replace(&params->list, &new_params->list);
+
+ rcu_assign_pointer(lru->memcg_params, new_params);
+ kfree_rcu(params, rcu_head);
+ }
+ return 0;
+}
+
+/**
+ * memcg_list_lru: get list_lru node corresponding to memory cgroup
+ * @lru: the list_lru
+ * @memcg: the memory cgroup
+ *
+ * Returns NULL if no node corresponds to @memcg in @lru.
+ */
+struct list_lru_node *memcg_list_lru(struct list_lru *lru,
+ struct mem_cgroup *memcg)
+{
+ struct memcg_list_lru_params *params;
+ struct list_lru_node *n;
+
+ if (!lru->memcg_params)
+ return NULL;
+ if (!memcg_kmem_is_active(memcg))
+ return NULL;
+
+ rcu_read_lock();
+ params = rcu_dereference(lru->memcg_params);
+ n = params->node[memcg_cache_id(memcg)];
+ rcu_read_unlock();
+
+ return n;
+}
+
+/**
+ * memcg_list_lru_from_obj: get list_lru node corresponding to memory cgroup
+ * which object is accounted to
+ * @lru: the list_lru
+ * @obj: the object ptr
+ *
+ * Return NULL if no node corresponds to the memory cgroup which @obj is
+ * accounted to or if @obj is not accounted to any memory cgroup.
+ *
+ * The object must be allocated from kmem.
+ */
+struct list_lru_node *memcg_list_lru_from_obj(struct list_lru *lru, void *obj)
+{
+ struct mem_cgroup *memcg = NULL;
+ struct kmem_cache *cachep;
+ struct page_cgroup *pc;
+ struct page *page;
+
+ if (!lru->memcg_params)
+ return NULL;
+
+ page = virt_to_head_page(obj);
+ if (PageSlab(page)) {
+ cachep = page->slab_cache;
+ if (!is_root_cache(cachep))
+ memcg = cachep->memcg_params->memcg;
+ } else {
+ /* page allocated with alloc_kmem_pages */
+ pc = lookup_page_cgroup(page);
+ if (PageCgroupUsed(pc))
+ memcg = pc->mem_cgroup;
+ }
+ if (!memcg)
+ return NULL;
+
+ return memcg_list_lru(lru, memcg);
+}
#else
static inline void memcg_unregister_all_caches(struct mem_cgroup *memcg)
{
@@ -4215,7 +4385,9 @@ static int __memcg_activate_kmem(struct mem_cgroup *memcg,
* memcg_params.
*/
mutex_lock(&memcg_slab_mutex);
- err = memcg_update_all_caches(memcg_id + 1);
+ err = memcg_update_all_list_lrus(memcg_id + 1);
+ if (!err)
+ err = memcg_update_all_caches(memcg_id + 1);
mutex_unlock(&memcg_slab_mutex);
if (err)
goto out_rmid;
diff --git a/mm/workingset.c b/mm/workingset.c
index d4fa7fb10a52..f8aae7497723 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -399,7 +399,8 @@ static int __init workingset_init(void)
{
int ret;

- ret = list_lru_init_key(&workingset_shadow_nodes, &shadow_nodes_key);
+ ret = list_lru_init_key(&workingset_shadow_nodes, false,
+ &shadow_nodes_key);
if (ret)
goto err;
ret = register_shrinker(&workingset_shadow_shrinker);
--
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/