diff options
author | Peter Zijlstra <peterz@infradead.org> | 2013-10-10 14:17:22 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2013-10-16 08:22:14 -0400 |
commit | 746023159c40c523b08a3bc3d213dac212385895 (patch) | |
tree | 5ec30b81b256a202bf44940360dd1849325c4af8 | |
parent | 7c3f2ab7b844f1a859afbc3d41925e8a0faba5fa (diff) |
sched: Fix race in migrate_swap_stop()
There is a subtle race in migrate_swap, when task P, on CPU A, decides to swap
places with task T, on CPU B.
Task P:
- call migrate_swap
Task T:
- go to sleep, removing itself from the runqueue
Task P:
- double lock the runqueues on CPU A & B
Task T:
- get woken up, place itself on the runqueue of CPU C
Task P:
- see that task T is on a runqueue, and pretend to remove it
from the runqueue on CPU B
Now CPUs B & C both have corrupted scheduler data structures.
This patch fixes it, by holding the pi_lock for both of the tasks
involved in the migrate swap. This prevents task T from waking up,
and placing itself onto another runqueue, until after migrate_swap
has released all locks.
This means that, when migrate_swap checks, task T will be either
on the runqueue where it was originally seen, or not on any
runqueue at all. Migrate_swap deals correctly with of those cases.
Tested-by: Joe Mario <jmario@redhat.com>
Acked-by: Mel Gorman <mgorman@suse.de>
Reviewed-by: Rik van Riel <riel@redhat.com>
Signed-off-by: Peter Zijlstra <peterz@infradead.org>
Cc: hannes@cmpxchg.org
Cc: aarcange@redhat.com
Cc: srikar@linux.vnet.ibm.com
Cc: tglx@linutronix.de
Cc: hpa@zytor.com
Link: http://lkml.kernel.org/r/20131010181722.GO13848@laptop.programming.kicks-ass.net
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r-- | kernel/sched/core.c | 4 | ||||
-rw-r--r-- | kernel/sched/fair.c | 9 | ||||
-rw-r--r-- | kernel/sched/sched.h | 18 |
3 files changed, 22 insertions, 9 deletions
diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0c3feebcf112..a972acd468b0 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c | |||
@@ -1049,6 +1049,8 @@ static int migrate_swap_stop(void *data) | |||
1049 | src_rq = cpu_rq(arg->src_cpu); | 1049 | src_rq = cpu_rq(arg->src_cpu); |
1050 | dst_rq = cpu_rq(arg->dst_cpu); | 1050 | dst_rq = cpu_rq(arg->dst_cpu); |
1051 | 1051 | ||
1052 | double_raw_lock(&arg->src_task->pi_lock, | ||
1053 | &arg->dst_task->pi_lock); | ||
1052 | double_rq_lock(src_rq, dst_rq); | 1054 | double_rq_lock(src_rq, dst_rq); |
1053 | if (task_cpu(arg->dst_task) != arg->dst_cpu) | 1055 | if (task_cpu(arg->dst_task) != arg->dst_cpu) |
1054 | goto unlock; | 1056 | goto unlock; |
@@ -1069,6 +1071,8 @@ static int migrate_swap_stop(void *data) | |||
1069 | 1071 | ||
1070 | unlock: | 1072 | unlock: |
1071 | double_rq_unlock(src_rq, dst_rq); | 1073 | double_rq_unlock(src_rq, dst_rq); |
1074 | raw_spin_unlock(&arg->dst_task->pi_lock); | ||
1075 | raw_spin_unlock(&arg->src_task->pi_lock); | ||
1072 | 1076 | ||
1073 | return ret; | 1077 | return ret; |
1074 | } | 1078 | } |
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 4aa0b10889d0..813dd61a9b43 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c | |||
@@ -1448,15 +1448,6 @@ static inline void put_numa_group(struct numa_group *grp) | |||
1448 | kfree_rcu(grp, rcu); | 1448 | kfree_rcu(grp, rcu); |
1449 | } | 1449 | } |
1450 | 1450 | ||
1451 | static void double_lock(spinlock_t *l1, spinlock_t *l2) | ||
1452 | { | ||
1453 | if (l1 > l2) | ||
1454 | swap(l1, l2); | ||
1455 | |||
1456 | spin_lock(l1); | ||
1457 | spin_lock_nested(l2, SINGLE_DEPTH_NESTING); | ||
1458 | } | ||
1459 | |||
1460 | static void task_numa_group(struct task_struct *p, int cpupid, int flags, | 1451 | static void task_numa_group(struct task_struct *p, int cpupid, int flags, |
1461 | int *priv) | 1452 | int *priv) |
1462 | { | 1453 | { |
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index d69cb325c27e..ffc708717b70 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h | |||
@@ -1249,6 +1249,24 @@ static inline void double_unlock_balance(struct rq *this_rq, struct rq *busiest) | |||
1249 | lock_set_subclass(&this_rq->lock.dep_map, 0, _RET_IP_); | 1249 | lock_set_subclass(&this_rq->lock.dep_map, 0, _RET_IP_); |
1250 | } | 1250 | } |
1251 | 1251 | ||
1252 | static inline void double_lock(spinlock_t *l1, spinlock_t *l2) | ||
1253 | { | ||
1254 | if (l1 > l2) | ||
1255 | swap(l1, l2); | ||
1256 | |||
1257 | spin_lock(l1); | ||
1258 | spin_lock_nested(l2, SINGLE_DEPTH_NESTING); | ||
1259 | } | ||
1260 | |||
1261 | static inline void double_raw_lock(raw_spinlock_t *l1, raw_spinlock_t *l2) | ||
1262 | { | ||
1263 | if (l1 > l2) | ||
1264 | swap(l1, l2); | ||
1265 | |||
1266 | raw_spin_lock(l1); | ||
1267 | raw_spin_lock_nested(l2, SINGLE_DEPTH_NESTING); | ||
1268 | } | ||
1269 | |||
1252 | /* | 1270 | /* |
1253 | * double_rq_lock - safely lock two runqueues | 1271 | * double_rq_lock - safely lock two runqueues |
1254 | * | 1272 | * |