diff options
author | Lai Jiangshan <laijs@cn.fujitsu.com> | 2012-09-01 12:28:19 -0400 |
---|---|---|
committer | Tejun Heo <tj@kernel.org> | 2012-09-04 20:04:45 -0400 |
commit | 96e65306b81351b656835c15931d1d237b252f27 (patch) | |
tree | af06187bebae44b48ca8e68a639a4ddc6b0a3509 /kernel/workqueue.c | |
parent | 0d7614f09c1ebdbaa1599a5aba7593f147bf96ee (diff) |
workqueue: UNBOUND -> REBIND morphing in rebind_workers() should be atomic
The compiler may compile the following code into TWO write/modify
instructions.
worker->flags &= ~WORKER_UNBOUND;
worker->flags |= WORKER_REBIND;
so the other CPU may temporarily see worker->flags which doesn't have
either WORKER_UNBOUND or WORKER_REBIND set and perform local wakeup
prematurely.
Fix it by using single explicit assignment via ACCESS_ONCE().
Because idle workers have another WORKER_NOT_RUNNING flag, this bug
doesn't exist for them; however, update it to use the same pattern for
consistency.
tj: Applied the change to idle workers too and updated comments and
patch description a bit.
Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: stable@vger.kernel.org
Diffstat (limited to 'kernel/workqueue.c')
-rw-r--r-- | kernel/workqueue.c | 17 |
1 files changed, 11 insertions, 6 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 692d97628a10..c462cd60c374 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c | |||
@@ -1396,12 +1396,15 @@ retry: | |||
1396 | /* set REBIND and kick idle ones, we'll wait for these later */ | 1396 | /* set REBIND and kick idle ones, we'll wait for these later */ |
1397 | for_each_worker_pool(pool, gcwq) { | 1397 | for_each_worker_pool(pool, gcwq) { |
1398 | list_for_each_entry(worker, &pool->idle_list, entry) { | 1398 | list_for_each_entry(worker, &pool->idle_list, entry) { |
1399 | unsigned long worker_flags = worker->flags; | ||
1400 | |||
1399 | if (worker->flags & WORKER_REBIND) | 1401 | if (worker->flags & WORKER_REBIND) |
1400 | continue; | 1402 | continue; |
1401 | 1403 | ||
1402 | /* morph UNBOUND to REBIND */ | 1404 | /* morph UNBOUND to REBIND atomically */ |
1403 | worker->flags &= ~WORKER_UNBOUND; | 1405 | worker_flags &= ~WORKER_UNBOUND; |
1404 | worker->flags |= WORKER_REBIND; | 1406 | worker_flags |= WORKER_REBIND; |
1407 | ACCESS_ONCE(worker->flags) = worker_flags; | ||
1405 | 1408 | ||
1406 | idle_rebind.cnt++; | 1409 | idle_rebind.cnt++; |
1407 | worker->idle_rebind = &idle_rebind; | 1410 | worker->idle_rebind = &idle_rebind; |
@@ -1434,10 +1437,12 @@ retry: | |||
1434 | /* rebind busy workers */ | 1437 | /* rebind busy workers */ |
1435 | for_each_busy_worker(worker, i, pos, gcwq) { | 1438 | for_each_busy_worker(worker, i, pos, gcwq) { |
1436 | struct work_struct *rebind_work = &worker->rebind_work; | 1439 | struct work_struct *rebind_work = &worker->rebind_work; |
1440 | unsigned long worker_flags = worker->flags; | ||
1437 | 1441 | ||
1438 | /* morph UNBOUND to REBIND */ | 1442 | /* morph UNBOUND to REBIND atomically */ |
1439 | worker->flags &= ~WORKER_UNBOUND; | 1443 | worker_flags &= ~WORKER_UNBOUND; |
1440 | worker->flags |= WORKER_REBIND; | 1444 | worker_flags |= WORKER_REBIND; |
1445 | ACCESS_ONCE(worker->flags) = worker_flags; | ||
1441 | 1446 | ||
1442 | if (test_and_set_bit(WORK_STRUCT_PENDING_BIT, | 1447 | if (test_and_set_bit(WORK_STRUCT_PENDING_BIT, |
1443 | work_data_bits(rebind_work))) | 1448 | work_data_bits(rebind_work))) |