summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-05-16 13:54:19 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2019-05-16 13:54:19 -0400
commitf57d7715d7645b7c3d1e7b7cb79ac7690fe2d260 (patch)
tree56ce411d562b92a8a369f74ad71d1bdbcc701b77 /kernel
parentb2ca74d32bba153a1507e6b7e36d3ec8a89311a1 (diff)
parenta9e9bcb45b1525ba7aea26ed9441e8632aeeda58 (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.c46
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/*