diff options
author | Tejun Heo <tj@kernel.org> | 2012-08-13 20:08:19 -0400 |
---|---|---|
committer | Tejun Heo <tj@kernel.org> | 2012-08-13 20:08:19 -0400 |
commit | 23657bb192f14b789e4c478def8f11ecc95b4f6c (patch) | |
tree | 53d92c542c219d60ccef2ada045235f5e7076863 /kernel/workqueue.c | |
parent | 1265057fa02c7bed3b6d9ddc8a2048065a370364 (diff) |
workqueue: add missing wmb() in clear_work_data()
Any operation which clears PENDING should be preceded by a wmb to
guarantee that the next PENDING owner sees all the changes made before
PENDING release.
There are only two places where PENDING is cleared -
set_work_cpu_and_clear_pending() and clear_work_data(). The caller of
the former already does smp_wmb() but the latter doesn't have any.
Move the wmb above set_work_cpu_and_clear_pending() into it and add
one to clear_work_data().
There hasn't been any report related to this issue, and, given how
clear_work_data() is used, it is extremely unlikely to have caused any
actual problems on any architecture.
Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Diffstat (limited to 'kernel/workqueue.c')
-rw-r--r-- | kernel/workqueue.c | 19 |
1 files changed, 12 insertions, 7 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 11723c5b2b20..4fef9527a620 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c | |||
@@ -570,11 +570,19 @@ static void set_work_cwq(struct work_struct *work, | |||
570 | static void set_work_cpu_and_clear_pending(struct work_struct *work, | 570 | static void set_work_cpu_and_clear_pending(struct work_struct *work, |
571 | unsigned int cpu) | 571 | unsigned int cpu) |
572 | { | 572 | { |
573 | /* | ||
574 | * The following wmb is paired with the implied mb in | ||
575 | * test_and_set_bit(PENDING) and ensures all updates to @work made | ||
576 | * here are visible to and precede any updates by the next PENDING | ||
577 | * owner. | ||
578 | */ | ||
579 | smp_wmb(); | ||
573 | set_work_data(work, (unsigned long)cpu << WORK_OFFQ_CPU_SHIFT, 0); | 580 | set_work_data(work, (unsigned long)cpu << WORK_OFFQ_CPU_SHIFT, 0); |
574 | } | 581 | } |
575 | 582 | ||
576 | static void clear_work_data(struct work_struct *work) | 583 | static void clear_work_data(struct work_struct *work) |
577 | { | 584 | { |
585 | smp_wmb(); /* see set_work_cpu_and_clear_pending() */ | ||
578 | set_work_data(work, WORK_STRUCT_NO_CPU, 0); | 586 | set_work_data(work, WORK_STRUCT_NO_CPU, 0); |
579 | } | 587 | } |
580 | 588 | ||
@@ -2182,14 +2190,11 @@ __acquires(&gcwq->lock) | |||
2182 | wake_up_worker(pool); | 2190 | wake_up_worker(pool); |
2183 | 2191 | ||
2184 | /* | 2192 | /* |
2185 | * Record the last CPU and clear PENDING. The following wmb is | 2193 | * Record the last CPU and clear PENDING which should be the last |
2186 | * paired with the implied mb in test_and_set_bit(PENDING) and | 2194 | * update to @work. Also, do this inside @gcwq->lock so that |
2187 | * ensures all updates to @work made here are visible to and | 2195 | * PENDING and queued state changes happen together while IRQ is |
2188 | * precede any updates by the next PENDING owner. Also, clear | 2196 | * disabled. |
2189 | * PENDING inside @gcwq->lock so that PENDING and queued state | ||
2190 | * changes happen together while IRQ is disabled. | ||
2191 | */ | 2197 | */ |
2192 | smp_wmb(); | ||
2193 | set_work_cpu_and_clear_pending(work, gcwq->cpu); | 2198 | set_work_cpu_and_clear_pending(work, gcwq->cpu); |
2194 | 2199 | ||
2195 | spin_unlock_irq(&gcwq->lock); | 2200 | spin_unlock_irq(&gcwq->lock); |