diff options
Diffstat (limited to 'mm/vmscan.c')
| -rw-r--r-- | mm/vmscan.c | 130 | 
1 files changed, 58 insertions, 72 deletions
diff --git a/mm/vmscan.c b/mm/vmscan.c index d257916f39e5..5bf98d0a22c9 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -35,7 +35,7 @@  #include <linux/cpuset.h>  #include <linux/compaction.h>  #include <linux/notifier.h> -#include <linux/mutex.h> +#include <linux/rwsem.h>  #include <linux/delay.h>  #include <linux/kthread.h>  #include <linux/freezer.h> @@ -57,7 +57,6 @@  #include <linux/khugepaged.h>  #include <linux/rculist_nulls.h>  #include <linux/random.h> -#include <linux/srcu.h>  #include <asm/tlbflush.h>  #include <asm/div64.h> @@ -190,9 +189,7 @@ struct scan_control {  int vm_swappiness = 60;  LIST_HEAD(shrinker_list); -DEFINE_MUTEX(shrinker_mutex); -DEFINE_SRCU(shrinker_srcu); -static atomic_t shrinker_srcu_generation = ATOMIC_INIT(0); +DECLARE_RWSEM(shrinker_rwsem);  #ifdef CONFIG_MEMCG  static int shrinker_nr_max; @@ -211,21 +208,8 @@ static inline int shrinker_defer_size(int nr_items)  static struct shrinker_info *shrinker_info_protected(struct mem_cgroup *memcg,  						     int nid)  { -	return srcu_dereference_check(memcg->nodeinfo[nid]->shrinker_info, -				      &shrinker_srcu, -				      lockdep_is_held(&shrinker_mutex)); -} - -static struct shrinker_info *shrinker_info_srcu(struct mem_cgroup *memcg, -						     int nid) -{ -	return srcu_dereference(memcg->nodeinfo[nid]->shrinker_info, -				&shrinker_srcu); -} - -static void free_shrinker_info_rcu(struct rcu_head *head) -{ -	kvfree(container_of(head, struct shrinker_info, rcu)); +	return rcu_dereference_protected(memcg->nodeinfo[nid]->shrinker_info, +					 lockdep_is_held(&shrinker_rwsem));  }  static int expand_one_shrinker_info(struct mem_cgroup *memcg, @@ -266,7 +250,7 @@ static int expand_one_shrinker_info(struct mem_cgroup *memcg,  		       defer_size - old_defer_size);  		rcu_assign_pointer(pn->shrinker_info, new); -		call_srcu(&shrinker_srcu, &old->rcu, free_shrinker_info_rcu); +		kvfree_rcu(old, rcu);  	}  	return 0; @@ -292,7 +276,7 @@ int alloc_shrinker_info(struct mem_cgroup *memcg)  	int nid, size, ret = 0;  	int map_size, defer_size = 0; -	mutex_lock(&shrinker_mutex); +	down_write(&shrinker_rwsem);  	map_size = shrinker_map_size(shrinker_nr_max);  	defer_size = shrinker_defer_size(shrinker_nr_max);  	size = map_size + defer_size; @@ -308,7 +292,7 @@ int alloc_shrinker_info(struct mem_cgroup *memcg)  		info->map_nr_max = shrinker_nr_max;  		rcu_assign_pointer(memcg->nodeinfo[nid]->shrinker_info, info);  	} -	mutex_unlock(&shrinker_mutex); +	up_write(&shrinker_rwsem);  	return ret;  } @@ -324,7 +308,7 @@ static int expand_shrinker_info(int new_id)  	if (!root_mem_cgroup)  		goto out; -	lockdep_assert_held(&shrinker_mutex); +	lockdep_assert_held(&shrinker_rwsem);  	map_size = shrinker_map_size(new_nr_max);  	defer_size = shrinker_defer_size(new_nr_max); @@ -352,16 +336,15 @@ void set_shrinker_bit(struct mem_cgroup *memcg, int nid, int shrinker_id)  {  	if (shrinker_id >= 0 && memcg && !mem_cgroup_is_root(memcg)) {  		struct shrinker_info *info; -		int srcu_idx; -		srcu_idx = srcu_read_lock(&shrinker_srcu); -		info = shrinker_info_srcu(memcg, nid); +		rcu_read_lock(); +		info = rcu_dereference(memcg->nodeinfo[nid]->shrinker_info);  		if (!WARN_ON_ONCE(shrinker_id >= info->map_nr_max)) {  			/* Pairs with smp mb in shrink_slab() */  			smp_mb__before_atomic();  			set_bit(shrinker_id, info->map);  		} -		srcu_read_unlock(&shrinker_srcu, srcu_idx); +		rcu_read_unlock();  	}  } @@ -374,7 +357,8 @@ static int prealloc_memcg_shrinker(struct shrinker *shrinker)  	if (mem_cgroup_disabled())  		return -ENOSYS; -	mutex_lock(&shrinker_mutex); +	down_write(&shrinker_rwsem); +	/* This may call shrinker, so it must use down_read_trylock() */  	id = idr_alloc(&shrinker_idr, shrinker, 0, 0, GFP_KERNEL);  	if (id < 0)  		goto unlock; @@ -388,7 +372,7 @@ static int prealloc_memcg_shrinker(struct shrinker *shrinker)  	shrinker->id = id;  	ret = 0;  unlock: -	mutex_unlock(&shrinker_mutex); +	up_write(&shrinker_rwsem);  	return ret;  } @@ -398,7 +382,7 @@ static void unregister_memcg_shrinker(struct shrinker *shrinker)  	BUG_ON(id < 0); -	lockdep_assert_held(&shrinker_mutex); +	lockdep_assert_held(&shrinker_rwsem);  	idr_remove(&shrinker_idr, id);  } @@ -408,7 +392,7 @@ static long xchg_nr_deferred_memcg(int nid, struct shrinker *shrinker,  {  	struct shrinker_info *info; -	info = shrinker_info_srcu(memcg, nid); +	info = shrinker_info_protected(memcg, nid);  	return atomic_long_xchg(&info->nr_deferred[shrinker->id], 0);  } @@ -417,7 +401,7 @@ static long add_nr_deferred_memcg(long nr, int nid, struct shrinker *shrinker,  {  	struct shrinker_info *info; -	info = shrinker_info_srcu(memcg, nid); +	info = shrinker_info_protected(memcg, nid);  	return atomic_long_add_return(nr, &info->nr_deferred[shrinker->id]);  } @@ -433,7 +417,7 @@ void reparent_shrinker_deferred(struct mem_cgroup *memcg)  		parent = root_mem_cgroup;  	/* Prevent from concurrent shrinker_info expand */ -	mutex_lock(&shrinker_mutex); +	down_read(&shrinker_rwsem);  	for_each_node(nid) {  		child_info = shrinker_info_protected(memcg, nid);  		parent_info = shrinker_info_protected(parent, nid); @@ -442,7 +426,7 @@ void reparent_shrinker_deferred(struct mem_cgroup *memcg)  			atomic_long_add(nr, &parent_info->nr_deferred[i]);  		}  	} -	mutex_unlock(&shrinker_mutex); +	up_read(&shrinker_rwsem);  }  static bool cgroup_reclaim(struct scan_control *sc) @@ -743,9 +727,9 @@ void free_prealloced_shrinker(struct shrinker *shrinker)  	shrinker->name = NULL;  #endif  	if (shrinker->flags & SHRINKER_MEMCG_AWARE) { -		mutex_lock(&shrinker_mutex); +		down_write(&shrinker_rwsem);  		unregister_memcg_shrinker(shrinker); -		mutex_unlock(&shrinker_mutex); +		up_write(&shrinker_rwsem);  		return;  	} @@ -755,11 +739,11 @@ void free_prealloced_shrinker(struct shrinker *shrinker)  void register_shrinker_prepared(struct shrinker *shrinker)  { -	mutex_lock(&shrinker_mutex); -	list_add_tail_rcu(&shrinker->list, &shrinker_list); +	down_write(&shrinker_rwsem); +	list_add_tail(&shrinker->list, &shrinker_list);  	shrinker->flags |= SHRINKER_REGISTERED;  	shrinker_debugfs_add(shrinker); -	mutex_unlock(&shrinker_mutex); +	up_write(&shrinker_rwsem);  }  static int __register_shrinker(struct shrinker *shrinker) @@ -805,22 +789,20 @@ EXPORT_SYMBOL(register_shrinker);  void unregister_shrinker(struct shrinker *shrinker)  {  	struct dentry *debugfs_entry; +	int debugfs_id;  	if (!(shrinker->flags & SHRINKER_REGISTERED))  		return; -	mutex_lock(&shrinker_mutex); -	list_del_rcu(&shrinker->list); +	down_write(&shrinker_rwsem); +	list_del(&shrinker->list);  	shrinker->flags &= ~SHRINKER_REGISTERED;  	if (shrinker->flags & SHRINKER_MEMCG_AWARE)  		unregister_memcg_shrinker(shrinker); -	debugfs_entry = shrinker_debugfs_remove(shrinker); -	mutex_unlock(&shrinker_mutex); +	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id); +	up_write(&shrinker_rwsem); -	atomic_inc(&shrinker_srcu_generation); -	synchronize_srcu(&shrinker_srcu); - -	debugfs_remove_recursive(debugfs_entry); +	shrinker_debugfs_remove(debugfs_entry, debugfs_id);  	kfree(shrinker->nr_deferred);  	shrinker->nr_deferred = NULL; @@ -830,13 +812,15 @@ EXPORT_SYMBOL(unregister_shrinker);  /**   * synchronize_shrinkers - Wait for all running shrinkers to complete.   * - * This is useful to guarantee that all shrinker invocations have seen an - * update, before freeing memory. + * This is equivalent to calling unregister_shrink() and register_shrinker(), + * but atomically and with less overhead. This is useful to guarantee that all + * shrinker invocations have seen an update, before freeing memory, similar to + * rcu.   */  void synchronize_shrinkers(void)  { -	atomic_inc(&shrinker_srcu_generation); -	synchronize_srcu(&shrinker_srcu); +	down_write(&shrinker_rwsem); +	up_write(&shrinker_rwsem);  }  EXPORT_SYMBOL(synchronize_shrinkers); @@ -945,20 +929,19 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,  {  	struct shrinker_info *info;  	unsigned long ret, freed = 0; -	int srcu_idx, generation; -	int i = 0; +	int i;  	if (!mem_cgroup_online(memcg))  		return 0; -again: -	srcu_idx = srcu_read_lock(&shrinker_srcu); -	info = shrinker_info_srcu(memcg, nid); +	if (!down_read_trylock(&shrinker_rwsem)) +		return 0; + +	info = shrinker_info_protected(memcg, nid);  	if (unlikely(!info))  		goto unlock; -	generation = atomic_read(&shrinker_srcu_generation); -	for_each_set_bit_from(i, info->map, info->map_nr_max) { +	for_each_set_bit(i, info->map, info->map_nr_max) {  		struct shrink_control sc = {  			.gfp_mask = gfp_mask,  			.nid = nid, @@ -1004,14 +987,14 @@ again:  				set_shrinker_bit(memcg, nid, i);  		}  		freed += ret; -		if (atomic_read(&shrinker_srcu_generation) != generation) { -			srcu_read_unlock(&shrinker_srcu, srcu_idx); -			i++; -			goto again; + +		if (rwsem_is_contended(&shrinker_rwsem)) { +			freed = freed ? : 1; +			break;  		}  	}  unlock: -	srcu_read_unlock(&shrinker_srcu, srcu_idx); +	up_read(&shrinker_rwsem);  	return freed;  }  #else /* CONFIG_MEMCG */ @@ -1048,7 +1031,6 @@ static unsigned long shrink_slab(gfp_t gfp_mask, int nid,  {  	unsigned long ret, freed = 0;  	struct shrinker *shrinker; -	int srcu_idx, generation;  	/*  	 * The root memcg might be allocated even though memcg is disabled @@ -1060,11 +1042,10 @@ static unsigned long shrink_slab(gfp_t gfp_mask, int nid,  	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))  		return shrink_slab_memcg(gfp_mask, nid, memcg, priority); -	srcu_idx = srcu_read_lock(&shrinker_srcu); +	if (!down_read_trylock(&shrinker_rwsem)) +		goto out; -	generation = atomic_read(&shrinker_srcu_generation); -	list_for_each_entry_srcu(shrinker, &shrinker_list, list, -				 srcu_read_lock_held(&shrinker_srcu)) { +	list_for_each_entry(shrinker, &shrinker_list, list) {  		struct shrink_control sc = {  			.gfp_mask = gfp_mask,  			.nid = nid, @@ -1075,14 +1056,19 @@ static unsigned long shrink_slab(gfp_t gfp_mask, int nid,  		if (ret == SHRINK_EMPTY)  			ret = 0;  		freed += ret; - -		if (atomic_read(&shrinker_srcu_generation) != generation) { +		/* +		 * Bail out if someone want to register a new shrinker to +		 * prevent the registration from being stalled for long periods +		 * by parallel ongoing shrinking. +		 */ +		if (rwsem_is_contended(&shrinker_rwsem)) {  			freed = freed ? : 1;  			break;  		}  	} -	srcu_read_unlock(&shrinker_srcu, srcu_idx); +	up_read(&shrinker_rwsem); +out:  	cond_resched();  	return freed;  }  |