diff options
author | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2014-08-25 23:25:06 -0400 |
---|---|---|
committer | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2014-09-18 19:22:27 -0400 |
commit | dd56af42bd829c6e770ed69812bd65a04eaeb1e4 (patch) | |
tree | 6d510ac693c3dfb05dcc383cb60acd3e1e8cab6c | |
parent | ec4518aad8329364af373f4bf7f4eff25a01a339 (diff) |
rcu: Eliminate deadlock between CPU hotplug and expedited grace periods
Currently, the expedited grace-period primitives do get_online_cpus().
This greatly simplifies their implementation, but means that calls
to them holding locks that are acquired by CPU-hotplug notifiers (to
say nothing of calls to these primitives from CPU-hotplug notifiers)
can deadlock. But this is starting to become inconvenient, as can be
seen here: https://lkml.org/lkml/2014/8/5/754. The problem in this
case is that some developers need to acquire a mutex from a CPU-hotplug
notifier, but also need to hold it across a synchronize_rcu_expedited().
As noted above, this currently results in deadlock.
This commit avoids the deadlock and retains the simplicity by creating
a try_get_online_cpus(), which returns false if the get_online_cpus()
reference count could not immediately be incremented. If a call to
try_get_online_cpus() returns true, the expedited primitives operate as
before. If a call returns false, the expedited primitives fall back to
normal grace-period operations. This falling back of course results in
increased grace-period latency, but only during times when CPU hotplug
operations are actually in flight. The effect should therefore be
negligible during normal operation.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: Josh Triplett <josh@joshtriplett.org>
Cc: "Rafael J. Wysocki" <rjw@rjwysocki.net>
Tested-by: Lan Tianyu <tianyu.lan@intel.com>
-rw-r--r-- | include/linux/cpu.h | 2 | ||||
-rw-r--r-- | include/linux/lockdep.h | 1 | ||||
-rw-r--r-- | kernel/cpu.c | 16 | ||||
-rw-r--r-- | kernel/rcu/tree.c | 19 | ||||
-rw-r--r-- | kernel/rcu/tree_plugin.h | 11 |
5 files changed, 35 insertions, 14 deletions
diff --git a/include/linux/cpu.h b/include/linux/cpu.h index 95978ad7fcdd..b2d9a43012b2 100644 --- a/include/linux/cpu.h +++ b/include/linux/cpu.h | |||
@@ -213,6 +213,7 @@ extern struct bus_type cpu_subsys; | |||
213 | extern void cpu_hotplug_begin(void); | 213 | extern void cpu_hotplug_begin(void); |
214 | extern void cpu_hotplug_done(void); | 214 | extern void cpu_hotplug_done(void); |
215 | extern void get_online_cpus(void); | 215 | extern void get_online_cpus(void); |
216 | extern bool try_get_online_cpus(void); | ||
216 | extern void put_online_cpus(void); | 217 | extern void put_online_cpus(void); |
217 | extern void cpu_hotplug_disable(void); | 218 | extern void cpu_hotplug_disable(void); |
218 | extern void cpu_hotplug_enable(void); | 219 | extern void cpu_hotplug_enable(void); |
@@ -230,6 +231,7 @@ int cpu_down(unsigned int cpu); | |||
230 | static inline void cpu_hotplug_begin(void) {} | 231 | static inline void cpu_hotplug_begin(void) {} |
231 | static inline void cpu_hotplug_done(void) {} | 232 | static inline void cpu_hotplug_done(void) {} |
232 | #define get_online_cpus() do { } while (0) | 233 | #define get_online_cpus() do { } while (0) |
234 | #define try_get_online_cpus() true | ||
233 | #define put_online_cpus() do { } while (0) | 235 | #define put_online_cpus() do { } while (0) |
234 | #define cpu_hotplug_disable() do { } while (0) | 236 | #define cpu_hotplug_disable() do { } while (0) |
235 | #define cpu_hotplug_enable() do { } while (0) | 237 | #define cpu_hotplug_enable() do { } while (0) |
diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h index 008388f920d7..4f86465cc317 100644 --- a/include/linux/lockdep.h +++ b/include/linux/lockdep.h | |||
@@ -505,6 +505,7 @@ static inline void print_irqtrace_events(struct task_struct *curr) | |||
505 | 505 | ||
506 | #define lock_map_acquire(l) lock_acquire_exclusive(l, 0, 0, NULL, _THIS_IP_) | 506 | #define lock_map_acquire(l) lock_acquire_exclusive(l, 0, 0, NULL, _THIS_IP_) |
507 | #define lock_map_acquire_read(l) lock_acquire_shared_recursive(l, 0, 0, NULL, _THIS_IP_) | 507 | #define lock_map_acquire_read(l) lock_acquire_shared_recursive(l, 0, 0, NULL, _THIS_IP_) |
508 | #define lock_map_acquire_tryread(l) lock_acquire_shared_recursive(l, 0, 1, NULL, _THIS_IP_) | ||
508 | #define lock_map_release(l) lock_release(l, 1, _THIS_IP_) | 509 | #define lock_map_release(l) lock_release(l, 1, _THIS_IP_) |
509 | 510 | ||
510 | #ifdef CONFIG_PROVE_LOCKING | 511 | #ifdef CONFIG_PROVE_LOCKING |
diff --git a/kernel/cpu.c b/kernel/cpu.c index 81e2a388a0f6..356450f09c1f 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c | |||
@@ -79,6 +79,8 @@ static struct { | |||
79 | 79 | ||
80 | /* Lockdep annotations for get/put_online_cpus() and cpu_hotplug_begin/end() */ | 80 | /* Lockdep annotations for get/put_online_cpus() and cpu_hotplug_begin/end() */ |
81 | #define cpuhp_lock_acquire_read() lock_map_acquire_read(&cpu_hotplug.dep_map) | 81 | #define cpuhp_lock_acquire_read() lock_map_acquire_read(&cpu_hotplug.dep_map) |
82 | #define cpuhp_lock_acquire_tryread() \ | ||
83 | lock_map_acquire_tryread(&cpu_hotplug.dep_map) | ||
82 | #define cpuhp_lock_acquire() lock_map_acquire(&cpu_hotplug.dep_map) | 84 | #define cpuhp_lock_acquire() lock_map_acquire(&cpu_hotplug.dep_map) |
83 | #define cpuhp_lock_release() lock_map_release(&cpu_hotplug.dep_map) | 85 | #define cpuhp_lock_release() lock_map_release(&cpu_hotplug.dep_map) |
84 | 86 | ||
@@ -91,10 +93,22 @@ void get_online_cpus(void) | |||
91 | mutex_lock(&cpu_hotplug.lock); | 93 | mutex_lock(&cpu_hotplug.lock); |
92 | cpu_hotplug.refcount++; | 94 | cpu_hotplug.refcount++; |
93 | mutex_unlock(&cpu_hotplug.lock); | 95 | mutex_unlock(&cpu_hotplug.lock); |
94 | |||
95 | } | 96 | } |
96 | EXPORT_SYMBOL_GPL(get_online_cpus); | 97 | EXPORT_SYMBOL_GPL(get_online_cpus); |
97 | 98 | ||
99 | bool try_get_online_cpus(void) | ||
100 | { | ||
101 | if (cpu_hotplug.active_writer == current) | ||
102 | return true; | ||
103 | if (!mutex_trylock(&cpu_hotplug.lock)) | ||
104 | return false; | ||
105 | cpuhp_lock_acquire_tryread(); | ||
106 | cpu_hotplug.refcount++; | ||
107 | mutex_unlock(&cpu_hotplug.lock); | ||
108 | return true; | ||
109 | } | ||
110 | EXPORT_SYMBOL_GPL(try_get_online_cpus); | ||
111 | |||
98 | void put_online_cpus(void) | 112 | void put_online_cpus(void) |
99 | { | 113 | { |
100 | if (cpu_hotplug.active_writer == current) | 114 | if (cpu_hotplug.active_writer == current) |
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index d7a3b13bc94c..133e47223095 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c | |||
@@ -2940,11 +2940,6 @@ static int synchronize_sched_expedited_cpu_stop(void *data) | |||
2940 | * restructure your code to batch your updates, and then use a single | 2940 | * restructure your code to batch your updates, and then use a single |
2941 | * synchronize_sched() instead. | 2941 | * synchronize_sched() instead. |
2942 | * | 2942 | * |
2943 | * Note that it is illegal to call this function while holding any lock | ||
2944 | * that is acquired by a CPU-hotplug notifier. And yes, it is also illegal | ||
2945 | * to call this function from a CPU-hotplug notifier. Failing to observe | ||
2946 | * these restriction will result in deadlock. | ||
2947 | * | ||
2948 | * This implementation can be thought of as an application of ticket | 2943 | * This implementation can be thought of as an application of ticket |
2949 | * locking to RCU, with sync_sched_expedited_started and | 2944 | * locking to RCU, with sync_sched_expedited_started and |
2950 | * sync_sched_expedited_done taking on the roles of the halves | 2945 | * sync_sched_expedited_done taking on the roles of the halves |
@@ -2994,7 +2989,12 @@ void synchronize_sched_expedited(void) | |||
2994 | */ | 2989 | */ |
2995 | snap = atomic_long_inc_return(&rsp->expedited_start); | 2990 | snap = atomic_long_inc_return(&rsp->expedited_start); |
2996 | firstsnap = snap; | 2991 | firstsnap = snap; |
2997 | get_online_cpus(); | 2992 | if (!try_get_online_cpus()) { |
2993 | /* CPU hotplug operation in flight, fall back to normal GP. */ | ||
2994 | wait_rcu_gp(call_rcu_sched); | ||
2995 | atomic_long_inc(&rsp->expedited_normal); | ||
2996 | return; | ||
2997 | } | ||
2998 | WARN_ON_ONCE(cpu_is_offline(raw_smp_processor_id())); | 2998 | WARN_ON_ONCE(cpu_is_offline(raw_smp_processor_id())); |
2999 | 2999 | ||
3000 | /* | 3000 | /* |
@@ -3041,7 +3041,12 @@ void synchronize_sched_expedited(void) | |||
3041 | * and they started after our first try, so their grace | 3041 | * and they started after our first try, so their grace |
3042 | * period works for us. | 3042 | * period works for us. |
3043 | */ | 3043 | */ |
3044 | get_online_cpus(); | 3044 | if (!try_get_online_cpus()) { |
3045 | /* CPU hotplug operation in flight, use normal GP. */ | ||
3046 | wait_rcu_gp(call_rcu_sched); | ||
3047 | atomic_long_inc(&rsp->expedited_normal); | ||
3048 | return; | ||
3049 | } | ||
3045 | snap = atomic_long_read(&rsp->expedited_start); | 3050 | snap = atomic_long_read(&rsp->expedited_start); |
3046 | smp_mb(); /* ensure read is before try_stop_cpus(). */ | 3051 | smp_mb(); /* ensure read is before try_stop_cpus(). */ |
3047 | } | 3052 | } |
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h index e2c5910546f6..387dd4599344 100644 --- a/kernel/rcu/tree_plugin.h +++ b/kernel/rcu/tree_plugin.h | |||
@@ -793,11 +793,6 @@ sync_rcu_preempt_exp_init(struct rcu_state *rsp, struct rcu_node *rnp) | |||
793 | * In fact, if you are using synchronize_rcu_expedited() in a loop, | 793 | * In fact, if you are using synchronize_rcu_expedited() in a loop, |
794 | * please restructure your code to batch your updates, and then Use a | 794 | * please restructure your code to batch your updates, and then Use a |
795 | * single synchronize_rcu() instead. | 795 | * single synchronize_rcu() instead. |
796 | * | ||
797 | * Note that it is illegal to call this function while holding any lock | ||
798 | * that is acquired by a CPU-hotplug notifier. And yes, it is also illegal | ||
799 | * to call this function from a CPU-hotplug notifier. Failing to observe | ||
800 | * these restriction will result in deadlock. | ||
801 | */ | 796 | */ |
802 | void synchronize_rcu_expedited(void) | 797 | void synchronize_rcu_expedited(void) |
803 | { | 798 | { |
@@ -819,7 +814,11 @@ void synchronize_rcu_expedited(void) | |||
819 | * being boosted. This simplifies the process of moving tasks | 814 | * being boosted. This simplifies the process of moving tasks |
820 | * from leaf to root rcu_node structures. | 815 | * from leaf to root rcu_node structures. |
821 | */ | 816 | */ |
822 | get_online_cpus(); | 817 | if (!try_get_online_cpus()) { |
818 | /* CPU-hotplug operation in flight, fall back to normal GP. */ | ||
819 | wait_rcu_gp(call_rcu); | ||
820 | return; | ||
821 | } | ||
823 | 822 | ||
824 | /* | 823 | /* |
825 | * Acquire lock, falling back to synchronize_rcu() if too many | 824 | * Acquire lock, falling back to synchronize_rcu() if too many |