diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-16 13:54:19 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-16 13:54:19 -0400 |
commit | f57d7715d7645b7c3d1e7b7cb79ac7690fe2d260 (patch) | |
tree | 56ce411d562b92a8a369f74ad71d1bdbcc701b77 /kernel | |
parent | b2ca74d32bba153a1507e6b7e36d3ec8a89311a1 (diff) | |
parent | a9e9bcb45b1525ba7aea26ed9441e8632aeeda58 (diff) |
Merge branch 'locking-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull locking fix from Ingo Molnar:
"A single rwsem fix"
* 'locking-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
locking/rwsem: Prevent decrement of reader count before increment
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/locking/rwsem-xadd.c | 46 |
1 files changed, 31 insertions, 15 deletions
diff --git a/kernel/locking/rwsem-xadd.c b/kernel/locking/rwsem-xadd.c index 6b3ee9948bf1..0b1f77957240 100644 --- a/kernel/locking/rwsem-xadd.c +++ b/kernel/locking/rwsem-xadd.c | |||
@@ -130,6 +130,7 @@ static void __rwsem_mark_wake(struct rw_semaphore *sem, | |||
130 | { | 130 | { |
131 | struct rwsem_waiter *waiter, *tmp; | 131 | struct rwsem_waiter *waiter, *tmp; |
132 | long oldcount, woken = 0, adjustment = 0; | 132 | long oldcount, woken = 0, adjustment = 0; |
133 | struct list_head wlist; | ||
133 | 134 | ||
134 | /* | 135 | /* |
135 | * Take a peek at the queue head waiter such that we can determine | 136 | * Take a peek at the queue head waiter such that we can determine |
@@ -188,18 +189,43 @@ static void __rwsem_mark_wake(struct rw_semaphore *sem, | |||
188 | * of the queue. We know that woken will be at least 1 as we accounted | 189 | * of the queue. We know that woken will be at least 1 as we accounted |
189 | * for above. Note we increment the 'active part' of the count by the | 190 | * for above. Note we increment the 'active part' of the count by the |
190 | * number of readers before waking any processes up. | 191 | * number of readers before waking any processes up. |
192 | * | ||
193 | * We have to do wakeup in 2 passes to prevent the possibility that | ||
194 | * the reader count may be decremented before it is incremented. It | ||
195 | * is because the to-be-woken waiter may not have slept yet. So it | ||
196 | * may see waiter->task got cleared, finish its critical section and | ||
197 | * do an unlock before the reader count increment. | ||
198 | * | ||
199 | * 1) Collect the read-waiters in a separate list, count them and | ||
200 | * fully increment the reader count in rwsem. | ||
201 | * 2) For each waiters in the new list, clear waiter->task and | ||
202 | * put them into wake_q to be woken up later. | ||
191 | */ | 203 | */ |
192 | list_for_each_entry_safe(waiter, tmp, &sem->wait_list, list) { | 204 | list_for_each_entry(waiter, &sem->wait_list, list) { |
193 | struct task_struct *tsk; | ||
194 | |||
195 | if (waiter->type == RWSEM_WAITING_FOR_WRITE) | 205 | if (waiter->type == RWSEM_WAITING_FOR_WRITE) |
196 | break; | 206 | break; |
197 | 207 | ||
198 | woken++; | 208 | woken++; |
199 | tsk = waiter->task; | 209 | } |
210 | list_cut_before(&wlist, &sem->wait_list, &waiter->list); | ||
211 | |||
212 | adjustment = woken * RWSEM_ACTIVE_READ_BIAS - adjustment; | ||
213 | lockevent_cond_inc(rwsem_wake_reader, woken); | ||
214 | if (list_empty(&sem->wait_list)) { | ||
215 | /* hit end of list above */ | ||
216 | adjustment -= RWSEM_WAITING_BIAS; | ||
217 | } | ||
218 | |||
219 | if (adjustment) | ||
220 | atomic_long_add(adjustment, &sem->count); | ||
221 | |||
222 | /* 2nd pass */ | ||
223 | list_for_each_entry_safe(waiter, tmp, &wlist, list) { | ||
224 | struct task_struct *tsk; | ||
200 | 225 | ||
226 | tsk = waiter->task; | ||
201 | get_task_struct(tsk); | 227 | get_task_struct(tsk); |
202 | list_del(&waiter->list); | 228 | |
203 | /* | 229 | /* |
204 | * Ensure calling get_task_struct() before setting the reader | 230 | * Ensure calling get_task_struct() before setting the reader |
205 | * waiter to nil such that rwsem_down_read_failed() cannot | 231 | * waiter to nil such that rwsem_down_read_failed() cannot |
@@ -213,16 +239,6 @@ static void __rwsem_mark_wake(struct rw_semaphore *sem, | |||
213 | */ | 239 | */ |
214 | wake_q_add_safe(wake_q, tsk); | 240 | wake_q_add_safe(wake_q, tsk); |
215 | } | 241 | } |
216 | |||
217 | adjustment = woken * RWSEM_ACTIVE_READ_BIAS - adjustment; | ||
218 | lockevent_cond_inc(rwsem_wake_reader, woken); | ||
219 | if (list_empty(&sem->wait_list)) { | ||
220 | /* hit end of list above */ | ||
221 | adjustment -= RWSEM_WAITING_BIAS; | ||
222 | } | ||
223 | |||
224 | if (adjustment) | ||
225 | atomic_long_add(adjustment, &sem->count); | ||
226 | } | 242 | } |
227 | 243 | ||
228 | /* | 244 | /* |