diff options
Diffstat (limited to 'kernel/locking/rwsem-xadd.c')
| -rw-r--r-- | kernel/locking/rwsem-xadd.c | 98 |
1 files changed, 58 insertions, 40 deletions
diff --git a/kernel/locking/rwsem-xadd.c b/kernel/locking/rwsem-xadd.c index 2f7cc4076f50..3417d0172a5d 100644 --- a/kernel/locking/rwsem-xadd.c +++ b/kernel/locking/rwsem-xadd.c | |||
| @@ -14,8 +14,9 @@ | |||
| 14 | #include <linux/init.h> | 14 | #include <linux/init.h> |
| 15 | #include <linux/export.h> | 15 | #include <linux/export.h> |
| 16 | #include <linux/sched/rt.h> | 16 | #include <linux/sched/rt.h> |
| 17 | #include <linux/osq_lock.h> | ||
| 17 | 18 | ||
| 18 | #include "mcs_spinlock.h" | 19 | #include "rwsem.h" |
| 19 | 20 | ||
| 20 | /* | 21 | /* |
| 21 | * Guide to the rw_semaphore's count field for common values. | 22 | * Guide to the rw_semaphore's count field for common values. |
| @@ -186,6 +187,13 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type) | |||
| 186 | waiter = list_entry(next, struct rwsem_waiter, list); | 187 | waiter = list_entry(next, struct rwsem_waiter, list); |
| 187 | next = waiter->list.next; | 188 | next = waiter->list.next; |
| 188 | tsk = waiter->task; | 189 | tsk = waiter->task; |
| 190 | /* | ||
| 191 | * Make sure we do not wakeup the next reader before | ||
| 192 | * setting the nil condition to grant the next reader; | ||
| 193 | * otherwise we could miss the wakeup on the other | ||
| 194 | * side and end up sleeping again. See the pairing | ||
| 195 | * in rwsem_down_read_failed(). | ||
| 196 | */ | ||
| 189 | smp_mb(); | 197 | smp_mb(); |
| 190 | waiter->task = NULL; | 198 | waiter->task = NULL; |
| 191 | wake_up_process(tsk); | 199 | wake_up_process(tsk); |
| @@ -258,6 +266,7 @@ static inline bool rwsem_try_write_lock(long count, struct rw_semaphore *sem) | |||
| 258 | RWSEM_ACTIVE_WRITE_BIAS) == RWSEM_WAITING_BIAS) { | 266 | RWSEM_ACTIVE_WRITE_BIAS) == RWSEM_WAITING_BIAS) { |
| 259 | if (!list_is_singular(&sem->wait_list)) | 267 | if (!list_is_singular(&sem->wait_list)) |
| 260 | rwsem_atomic_update(RWSEM_WAITING_BIAS, sem); | 268 | rwsem_atomic_update(RWSEM_WAITING_BIAS, sem); |
| 269 | rwsem_set_owner(sem); | ||
| 261 | return true; | 270 | return true; |
| 262 | } | 271 | } |
| 263 | 272 | ||
| @@ -270,15 +279,17 @@ static inline bool rwsem_try_write_lock(long count, struct rw_semaphore *sem) | |||
| 270 | */ | 279 | */ |
| 271 | static inline bool rwsem_try_write_lock_unqueued(struct rw_semaphore *sem) | 280 | static inline bool rwsem_try_write_lock_unqueued(struct rw_semaphore *sem) |
| 272 | { | 281 | { |
| 273 | long old, count = ACCESS_ONCE(sem->count); | 282 | long old, count = READ_ONCE(sem->count); |
| 274 | 283 | ||
| 275 | while (true) { | 284 | while (true) { |
| 276 | if (!(count == 0 || count == RWSEM_WAITING_BIAS)) | 285 | if (!(count == 0 || count == RWSEM_WAITING_BIAS)) |
| 277 | return false; | 286 | return false; |
| 278 | 287 | ||
| 279 | old = cmpxchg(&sem->count, count, count + RWSEM_ACTIVE_WRITE_BIAS); | 288 | old = cmpxchg(&sem->count, count, count + RWSEM_ACTIVE_WRITE_BIAS); |
| 280 | if (old == count) | 289 | if (old == count) { |
| 290 | rwsem_set_owner(sem); | ||
| 281 | return true; | 291 | return true; |
| 292 | } | ||
| 282 | 293 | ||
| 283 | count = old; | 294 | count = old; |
| 284 | } | 295 | } |
| @@ -287,60 +298,67 @@ static inline bool rwsem_try_write_lock_unqueued(struct rw_semaphore *sem) | |||
| 287 | static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem) | 298 | static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem) |
| 288 | { | 299 | { |
| 289 | struct task_struct *owner; | 300 | struct task_struct *owner; |
| 290 | bool on_cpu = false; | 301 | bool ret = true; |
| 291 | 302 | ||
| 292 | if (need_resched()) | 303 | if (need_resched()) |
| 293 | return false; | 304 | return false; |
| 294 | 305 | ||
| 295 | rcu_read_lock(); | 306 | rcu_read_lock(); |
| 296 | owner = ACCESS_ONCE(sem->owner); | 307 | owner = READ_ONCE(sem->owner); |
| 297 | if (owner) | 308 | if (!owner) { |
| 298 | on_cpu = owner->on_cpu; | 309 | long count = READ_ONCE(sem->count); |
| 299 | rcu_read_unlock(); | 310 | /* |
| 300 | 311 | * If sem->owner is not set, yet we have just recently entered the | |
| 301 | /* | 312 | * slowpath with the lock being active, then there is a possibility |
| 302 | * If sem->owner is not set, yet we have just recently entered the | 313 | * reader(s) may have the lock. To be safe, bail spinning in these |
| 303 | * slowpath, then there is a possibility reader(s) may have the lock. | 314 | * situations. |
| 304 | * To be safe, avoid spinning in these situations. | 315 | */ |
| 305 | */ | 316 | if (count & RWSEM_ACTIVE_MASK) |
| 306 | return on_cpu; | 317 | ret = false; |
| 307 | } | 318 | goto done; |
| 308 | 319 | } | |
| 309 | static inline bool owner_running(struct rw_semaphore *sem, | ||
| 310 | struct task_struct *owner) | ||
| 311 | { | ||
| 312 | if (sem->owner != owner) | ||
| 313 | return false; | ||
| 314 | |||
| 315 | /* | ||
| 316 | * Ensure we emit the owner->on_cpu, dereference _after_ checking | ||
| 317 | * sem->owner still matches owner, if that fails, owner might | ||
| 318 | * point to free()d memory, if it still matches, the rcu_read_lock() | ||
| 319 | * ensures the memory stays valid. | ||
| 320 | */ | ||
| 321 | barrier(); | ||
| 322 | 320 | ||
| 323 | return owner->on_cpu; | 321 | ret = owner->on_cpu; |
| 322 | done: | ||
| 323 | rcu_read_unlock(); | ||
| 324 | return ret; | ||
| 324 | } | 325 | } |
| 325 | 326 | ||
| 326 | static noinline | 327 | static noinline |
| 327 | bool rwsem_spin_on_owner(struct rw_semaphore *sem, struct task_struct *owner) | 328 | bool rwsem_spin_on_owner(struct rw_semaphore *sem, struct task_struct *owner) |
| 328 | { | 329 | { |
| 330 | long count; | ||
| 331 | |||
| 329 | rcu_read_lock(); | 332 | rcu_read_lock(); |
| 330 | while (owner_running(sem, owner)) { | 333 | while (sem->owner == owner) { |
| 331 | if (need_resched()) | 334 | /* |
| 332 | break; | 335 | * Ensure we emit the owner->on_cpu, dereference _after_ |
| 336 | * checking sem->owner still matches owner, if that fails, | ||
| 337 | * owner might point to free()d memory, if it still matches, | ||
| 338 | * the rcu_read_lock() ensures the memory stays valid. | ||
| 339 | */ | ||
| 340 | barrier(); | ||
| 341 | |||
| 342 | /* abort spinning when need_resched or owner is not running */ | ||
| 343 | if (!owner->on_cpu || need_resched()) { | ||
| 344 | rcu_read_unlock(); | ||
| 345 | return false; | ||
| 346 | } | ||
| 333 | 347 | ||
| 334 | cpu_relax_lowlatency(); | 348 | cpu_relax_lowlatency(); |
| 335 | } | 349 | } |
| 336 | rcu_read_unlock(); | 350 | rcu_read_unlock(); |
| 337 | 351 | ||
| 352 | if (READ_ONCE(sem->owner)) | ||
| 353 | return true; /* new owner, continue spinning */ | ||
| 354 | |||
| 338 | /* | 355 | /* |
| 339 | * We break out the loop above on need_resched() or when the | 356 | * When the owner is not set, the lock could be free or |
| 340 | * owner changed, which is a sign for heavy contention. Return | 357 | * held by readers. Check the counter to verify the |
| 341 | * success only when sem->owner is NULL. | 358 | * state. |
| 342 | */ | 359 | */ |
| 343 | return sem->owner == NULL; | 360 | count = READ_ONCE(sem->count); |
| 361 | return (count == 0 || count == RWSEM_WAITING_BIAS); | ||
| 344 | } | 362 | } |
| 345 | 363 | ||
| 346 | static bool rwsem_optimistic_spin(struct rw_semaphore *sem) | 364 | static bool rwsem_optimistic_spin(struct rw_semaphore *sem) |
| @@ -358,7 +376,7 @@ static bool rwsem_optimistic_spin(struct rw_semaphore *sem) | |||
| 358 | goto done; | 376 | goto done; |
| 359 | 377 | ||
| 360 | while (true) { | 378 | while (true) { |
| 361 | owner = ACCESS_ONCE(sem->owner); | 379 | owner = READ_ONCE(sem->owner); |
| 362 | if (owner && !rwsem_spin_on_owner(sem, owner)) | 380 | if (owner && !rwsem_spin_on_owner(sem, owner)) |
| 363 | break; | 381 | break; |
| 364 | 382 | ||
| @@ -432,7 +450,7 @@ struct rw_semaphore __sched *rwsem_down_write_failed(struct rw_semaphore *sem) | |||
| 432 | 450 | ||
| 433 | /* we're now waiting on the lock, but no longer actively locking */ | 451 | /* we're now waiting on the lock, but no longer actively locking */ |
| 434 | if (waiting) { | 452 | if (waiting) { |
| 435 | count = ACCESS_ONCE(sem->count); | 453 | count = READ_ONCE(sem->count); |
| 436 | 454 | ||
| 437 | /* | 455 | /* |
| 438 | * If there were already threads queued before us and there are | 456 | * If there were already threads queued before us and there are |
