slab: cleanup mem hotplug synchronization
Subsystems (8)
v2 cover slab: cleanup mem hotplug synchronization
Hi,
kmem_cache_{create,destroy,shrink} need to get a stable value of cpu/node online mask, because they init/destroy/access per-cpu/node kmem_cache parts, which can be allocated or destroyed on cpu/mem hotplug. To protect against cpu hotplug, these functions use {get,put}_online_cpus. However, they do nothing to synchronize with memory hotplug - taking the slab_mutex does not eliminate the possibility of race as described in patch 2.
What we need there is something like get_online_cpus, but for memory. We already have lock_memory_hotplug, which serves for the purpose, but it's a bit of a hammer right now, because it's backed by a mutex. As a result, it imposes some limitations to locking order, which are not desirable, and can't be used just like get_online_cpus. That's why in patch 1 I substitute it with get/put_online_mems, which work exactly like get/put_online_cpus except they block not cpu, but memory hotplug.
[ v1 can be found at https://lkml.org/lkml/2014/4/6/68. I NAK'ed it by myself, because it used an rw semaphore for get/put_online_mems, making them dead lock prune. ]
Thanks,
Vladimir Davydov (2):
mem-hotplug: implement get/put_online_mems
slab: lock_memory_hotplug for kmem_cache_{create,destroy,shrink}
include/linux/memory_hotplug.h | 14 ++-- include/linux/mmzone.h | 8 +-- mm/kmemleak.c | 4 +- mm/memory-failure.c | 8 +-- mm/memory_hotplug.c | 142 ++++++++++++++++++++++++++++------------ mm/slab.c | 26 +------- mm/slab.h | 1 + mm/slab_common.c | 35 +++++++++- mm/slob.c | 3 +- mm/slub.c | 9 ++- mm/vmscan.c | 2 +- 11 files changed, 155 insertions(+), 97 deletions(-)
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v2 1/2 mem-hotplug: implement get/put_online_mems
{un}lock_memory_hotplug, which is used to synchronize against memory hotplug, is currently backed by a mutex, which makes it a bit of a hammer - threads that only want to get a stable value of online nodes mask won't be able to proceed concurrently. Also, it imposes some strong locking ordering rules on it, which narrows down the set of its usage scenarios.
This patch introduces get/put_online_mems, which are the same as get/put_online_cpus, but for memory hotplug, i.e. executing a code inside a get/put_online_mems section will guarantee a stable value of online nodes, present pages, etc.
--- include/linux/memory_hotplug.h | 14 ++-- include/linux/mmzone.h | 8 +-- mm/kmemleak.c | 4 +- mm/memory-failure.c | 8 +-- mm/memory_hotplug.c | 142 ++++++++++++++++++++++++++++------------ mm/slub.c | 4 +- mm/vmscan.c | 2 +- 7 files changed, 116 insertions(+), 66 deletions(-)
include/linux/memory_hotplug.h +4 -10
@@ -187,14 +187,8 @@ extern void put_page_bootmem(struct page *page);extern void get_page_bootmem (unsigned long ingo,struct page *page,unsigned long type); -/* -* Lock for memory hotplug guarantees 1) all callbacks for memory hotplug -* notifier will be called under this. 2) offline/online/add/remove memory -* will not run simultaneously. -*/ - -void lock_memory_hotplug (void ); -void unlock_memory_hotplug (void ); +void get_online_mems (void ); +void put_online_mems (void );#else /* ! CONFIG_MEMORY_HOTPLUG */ /*
@@ -232,8 +226,8 @@ static inline int try_online_node(int nid) return 0; } -static inline void lock_memory_hotplug (void ) {} -static inline void unlock_memory_hotplug (void ) {} +static inline void get_online_mems (void ) {} +static inline void put_online_mems (void ) {}#endif /* ! CONFIG_MEMORY_HOTPLUG */
include/linux/mmzone.h +4 -4
@@ -481,9 +481,8 @@ struct zone {* give them a chance of being in the same cacheline. * * Write access to present_pages at runtime should be protected by -* lock_memory_hotplug()/unlock_memory_hotplug(). Any reader who can't -* tolerant drift of present_pages should hold memory hotplug lock to -* get a stable value. +* mem_hotplug_begin/end(). Any reader who can't tolerant drift of +* present_pages should get_online_mems() to get a stable value. * * Read access to managed_pages should be safe because it's unsigned * long. Write access to zone->managed_pages and totalram_pages are
@@ -766,7 +765,8 @@ typedef struct pglist_data {nodemask_t reclaim_nodes;/* Nodes allowed to reclaim from */ wait_queue_head_t kswapd_wait;wait_queue_head_t pfmemalloc_wait; -struct task_struct *kswapd;/* Protected by lock_memory_hotplug() */ +struct task_struct *kswapd;/* Protected by +mem_hotplug_begin/end() */ int kswapd_max_order;enum zone_type classzone_idx;#ifdef CONFIG_NUMA_BALANCING
mm/kmemleak.c +2 -2
@@ -1299,7 +1299,7 @@ static void kmemleak_scan(void)/* * Struct page scanning for each node. */ -lock_memory_hotplug (); +get_online_mems ();for_each_online_node (i) {unsigned long start_pfn =node_start_pfn (i);unsigned long end_pfn =node_end_pfn (i);
@@ -1317,7 +1317,7 @@ static void kmemleak_scan(void)scan_block (page, page +1 ,NULL ,1 ); } } -unlock_memory_hotplug (); +put_online_mems ();/* * Scanning the task stacks (may introduce false negatives).
mm/memory-failure.c +2 -6
@@ -1661,11 +1661,7 @@ int soft_offline_page(struct page *page, int flags) } } -/* -* The lock_memory_hotplug prevents a race with memory hotplug. -* This is a big hammer, a better would be nicer. -*/ -lock_memory_hotplug (); +get_online_mems ();/* * Isolate the page, so that it doesn't get reallocated if it
@@ -1676,7 +1672,7 @@ int soft_offline_page(struct page *page, int flags)set_migratetype_isolate (page, true); ret =get_any_page (page, pfn, flags); -unlock_memory_hotplug (); +put_online_mems ();if (ret >0 ) {/* for in-use pages */ if (PageHuge (page)) ret =soft_offline_huge_page (page, flags);
mm/memory_hotplug.c +101 -41
276 lines not on this page · raw message
mm/slub.c +2 -2
@@ -4352,7 +4352,7 @@ static ssize_t show_slab_objects(struct kmem_cache *s, } } -lock_memory_hotplug (); +get_online_mems ();#ifdef CONFIG_SLUB_DEBUG if (flags &SO_ALL ) {for_each_node_state (node,N_NORMAL_MEMORY ) {
@@ -4392,7 +4392,7 @@ static ssize_t show_slab_objects(struct kmem_cache *s, x +=sprintf (buf + x," N%d=%lu" , node, nodes[node]);#endif -unlock_memory_hotplug (); +put_online_mems ();kfree (nodes);return x +sprintf (buf + x,"\n" ); }
mm/vmscan.c +1 -1
@@ -3418,7 +3418,7 @@ int kswapd_run(int nid)/* * Called by memory hotplug when all memory in a node is offlined. Caller must -* hold lock_memory_hotplug(). +* hold mem_hotplug_begin/end(). */ void kswapd_stop (int nid) {
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Andrew Morton
maintainerRe: [PATCH -mm v2 1/2] mem-hotplug: implement get/put_online_mems
Quoted 11 lines · On Mon, 7 Apr 2014 13:45:34 +0400 Vladimir Davydov <[email protected]>…
{un}lock_memory_hotplug, which is used to synchronize against memory hotplug, is currently backed by a mutex, which makes it a bit of a hammer - threads that only want to get a stable value of online nodes mask won't be able to proceed concurrently. Also, it imposes some strong locking ordering rules on it, which narrows down the set of its usage scenarios.
This patch introduces get/put_online_mems, which are the same as get/put_online_cpus, but for memory hotplug, i.e. executing a code inside a get/put_online_mems section will guarantee a stable value of online nodes, present pages, etc.
Well that seems a nice change. I added the important paragraph
"lock_memory_hotplug()/unlock_memory_hotplug() are removed altogether."
I'll Cc a large number of people who have recently worked on the memory hotplug code. Hopefully some of them will have time to review and test these patches, thanks.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v2 2/2 slab: get_online_mems for kmem_cache_{create,destroy,shrink}
In reply to Vladimir DavydovWhen we create a sl[au]b cache, we allocate kmem_cache_node structures for each online NUMA node. To handle nodes taken online/offline, we register memory hotplug notifier and allocate/free kmem_cache_node corresponding to the node that changes its state for each kmem cache.
To synchronize between the two paths we hold the slab_mutex during both the cache creationg/destruction path and while tuning per-node parts of kmem caches in memory hotplug handler, but that's not quite right, because it does not guarantee that a newly created cache will have all kmem_cache_nodes initialized in case it races with memory hotplug. For instance, in case of slub:
CPU0 CPU1
---- ----
kmem_cache_create: online_pages: __kmem_cache_create: slab_memory_callback:
slab_mem_going_online_callback:
lock slab_mutex
for each slab_caches list entry
allocate kmem_cache node
unlock slab_mutex
lock slab_mutex
init_kmem_cache_nodes:
for_each_node_state(node, N_NORMAL_MEMORY)
allocate kmem_cache node
add kmem_cache to slab_caches list
unlock slab_mutex
online_pages (continued):
node_states_set_node
As a result we'll get a kmem cache with not all kmem_cache_nodes allocated.
To avoid issues like that we should hold get/put_online_mems() during the whole kmem cache creation/destruction/shrink paths, just like we deal with cpu hotplug. This patch does the trick.
Note, that after it's applied, there is no need in taking the slab_mutex for kmem_cache_shrink any more, so it is removed from there.
--- mm/slab.c | 26 ++------------------------ mm/slab.h | 1 + mm/slab_common.c | 35 +++++++++++++++++++++++++++++++++-- mm/slob.c | 3 +-- mm/slub.c | 5 ++--- 5 files changed, 39 insertions(+), 31 deletions(-)
mm/slab.c +2 -24
@@ -2442,8 +2442,7 @@ out:return nr_freed; } -/* Called with slab_mutex held to protect against cpu hotplug */ -static int __cache_shrink (struct kmem_cache *cachep) +int __kmem_cache_shrink (struct kmem_cache *cachep) {int ret =0 , i =0 ;struct kmem_cache_node *n;
@@ -2464,32 +2463,11 @@ static int __cache_shrink(struct kmem_cache *cachep)return (ret ?1 :0 ); } -/** -* kmem_cache_shrink - Shrink a cache. -* @cachep: The cache to shrink. -* -* Releases as many slabs as possible for a cache. -* To help debugging, a zero exit status indicates all slabs were released. -*/ -int kmem_cache_shrink (struct kmem_cache *cachep) -{ -int ret; -BUG_ON (!cachep ||in_interrupt ()); - -get_online_cpus (); -mutex_lock (&slab_mutex); - ret =__cache_shrink (cachep); -mutex_unlock (&slab_mutex); -put_online_cpus (); -return ret; -} -EXPORT_SYMBOL (kmem_cache_shrink); -int __kmem_cache_shutdown (struct kmem_cache *cachep) {int i;struct kmem_cache_node *n; -int rc =__cache_shrink (cachep); +int rc =__kmem_cache_shrink (cachep);if (rc)return rc;
mm/slab.h +1 -0
@@ -91,6 +91,7 @@ __kmem_cache_alias(const char *name, size_t size, size_t align,#define CACHE_CREATE_MASK (SLAB_CORE_FLAGS | SLAB_DEBUG_FLAGS | SLAB_CACHE_FLAGS)int __kmem_cache_shutdown (struct kmem_cache *); +int __kmem_cache_shrink (struct kmem_cache *);struct seq_file ;struct file ;
mm/slab_common.c +33 -2
@@ -205,6 +205,8 @@ kmem_cache_create(const char *name, size_t size, size_t align,int err;get_online_cpus (); +get_online_mems (); +mutex_lock (&slab_mutex); err =kmem_cache_sanity_check (name, size);
@@ -239,6 +241,8 @@ kmem_cache_create(const char *name, size_t size, size_t align, out_unlock:mutex_unlock (&slab_mutex); + +put_online_mems ();put_online_cpus ();if (err) {
@@ -272,6 +276,8 @@ void kmem_cache_create_memcg(struct mem_cgroup *memcg, struct kmem_cache *root_cchar *cache_name;get_online_cpus (); +get_online_mems (); +mutex_lock (&slab_mutex);/*
@@ -299,6 +305,8 @@ void kmem_cache_create_memcg(struct mem_cgroup *memcg, struct kmem_cache *root_c out_unlock:mutex_unlock (&slab_mutex); + +put_online_mems ();put_online_cpus (); }
@@ -326,6 +334,8 @@ static int kmem_cache_destroy_memcg_children(struct kmem_cache *s)void kmem_cache_destroy (struct kmem_cache *s) {get_online_cpus (); +get_online_mems (); +mutex_lock (&slab_mutex); s->refcount--;
@@ -354,15 +364,36 @@ void kmem_cache_destroy(struct kmem_cache *s)memcg_free_cache_params (s);kfree (s->name);kmem_cache_free (kmem_cache, s); - goto out_put_cpus; + goto out; out_unlock:mutex_unlock (&slab_mutex); -out_put_cpus: +out: +put_online_mems ();put_online_cpus (); }EXPORT_SYMBOL (kmem_cache_destroy); +/** +* kmem_cache_shrink - Shrink a cache. +* @cachep: The cache to shrink. +* +* Releases as many slabs as possible for a cache. +* To help debugging, a zero exit status indicates all slabs were released. +*/ +int kmem_cache_shrink (struct kmem_cache *cachep) +{ +int ret; + +get_online_cpus (); +get_online_mems (); + ret =__kmem_cache_shrink (cachep); +put_online_mems (); +put_online_cpus (); +return ret; +} +EXPORT_SYMBOL (kmem_cache_shrink); +int slab_is_available (void ) {return slab_state >=UP ;
mm/slob.c +1 -2
@@ -620,11 +620,10 @@ int __kmem_cache_shutdown(struct kmem_cache *c)return 0 ; } -int kmem_cache_shrink (struct kmem_cache *d) +int __kmem_cache_shrink (struct kmem_cache *d) {return 0 ; } -EXPORT_SYMBOL (kmem_cache_shrink);struct kmem_cache kmem_cache_boot = { .name ="kmem_cache" ,
mm/slub.c +2 -3
@@ -3412,7 +3412,7 @@ EXPORT_SYMBOL(kfree);* being allocated from last increasing the chance that the last objects * are freed in them. */ -int kmem_cache_shrink (struct kmem_cache *s) +int __kmem_cache_shrink (struct kmem_cache *s) {int node;int i;
@@ -3468,7 +3468,6 @@ int kmem_cache_shrink(struct kmem_cache *s)kfree (slabs_by_inuse);return 0 ; } -EXPORT_SYMBOL (kmem_cache_shrink);static int slab_mem_going_offline_callback (void *arg) {
@@ -3476,7 +3475,7 @@ static int slab_mem_going_offline_callback(void *arg)mutex_lock (&slab_mutex);list_for_each_entry (s, &slab_caches, list) -kmem_cache_shrink (s); +__kmem_cache_shrink (s);mutex_unlock (&slab_mutex);return 0 ;
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>