| /* Copyright (c) 2013 The Regents of the University of California |
| * Barret Rhoden <brho@cs.berkeley.edu> |
| * See LICENSE for details. |
| * |
| * Reader-writer queue locks (sleeping locks). |
| * |
| * We favor readers when reading, meaning new readers can move ahead of writers. |
| * Ex: If i have some readers, then a writer, clearly the writer blocks. If |
| * more readers come in, they can just come in and the presence of the writer |
| * doesn't stop them. |
| * |
| * You get potential writer starvation, but you also get the property that |
| * if a thread holds a read-lock, that thread can grab the same reader |
| * lock again. A more general statement would be "if some reader holds |
| * a rwlock, then any other thread (including itself) can get an rlock". |
| * |
| * Similarly, writers favor other writers. So if a writer is unlocking, it'll |
| * pass the lock to another writer first. Here, there is potential reader |
| * starvation. |
| * |
| * We also pass locks, instead of letting recently woken threads fight for it. |
| * In the case of a reader wakeup, we know that they all will wake up and read. |
| * Instead of having them fight for a lock and then incref, the waker (the last |
| * writer) will up the count and just wake everyone. |
| * |
| * This also helps when a writer wants to favor another writer. If we didn't |
| * pass the lock, then a new reader could squeeze in after our old writer |
| * signalled the new writer. Even worse, in this case, the readers that we |
| * didn't wake up are still sleeping, even though a reader now holds the lock. |
| * It won't deadlock, (since eventually the reader will wake the writer, who |
| * wakes the old readers) but it breaks the notion of a RW lock a bit. */ |
| |
| #include <rwlock.h> |
| #include <atomic.h> |
| #include <kthread.h> |
| |
| void rwinit(struct rwlock *rw_lock) |
| { |
| spinlock_init(&rw_lock->lock); |
| atomic_init(&rw_lock->nr_readers, 0); |
| rw_lock->writing = FALSE; |
| cv_init_with_lock(&rw_lock->readers, &rw_lock->lock); |
| cv_init_with_lock(&rw_lock->writers, &rw_lock->lock); |
| } |
| |
| void rlock(struct rwlock *rw_lock) |
| { |
| /* If we already have a reader, we can just increment and return. This |
| * is the only access to nr_readers outside the lock. All locked uses |
| * need to be aware that the nr could be concurrently increffed (unless |
| * it is 0). */ |
| if (atomic_add_not_zero(&rw_lock->nr_readers, 1)) |
| return; |
| /* Here's an alternate style: the broadcaster (a writer) will up the |
| * readers count and just wake us. All readers just proceed, instead of |
| * fighting to lock and up the count. The writer 'passed' the rlock to |
| * us. */ |
| spin_lock(&rw_lock->lock); |
| if (rw_lock->writing) { |
| cv_wait_and_unlock(&rw_lock->readers); |
| return; |
| } |
| atomic_inc(&rw_lock->nr_readers); |
| spin_unlock(&rw_lock->lock); |
| } |
| |
| bool canrlock(struct rwlock *rw_lock) |
| { |
| if (atomic_add_not_zero(&rw_lock->nr_readers, 1)) |
| return TRUE; |
| spin_lock(&rw_lock->lock); |
| if (rw_lock->writing) { |
| spin_unlock(&rw_lock->lock); |
| return FALSE; |
| } |
| atomic_inc(&rw_lock->nr_readers); |
| spin_unlock(&rw_lock->lock); |
| return TRUE; |
| } |
| |
| void runlock(struct rwlock *rw_lock) |
| { |
| spin_lock(&rw_lock->lock); |
| /* sub and test will tell us if we got the refcnt to 0, atomically. |
| * syncing with the atomic_add_not_zero of new readers. Since we're |
| * passing the lock, we need to make sure someone is sleeping. Contrast |
| * to the wunlock, where we can just blindly broadcast and add |
| * (potentially == 0). */ |
| if (atomic_sub_and_test(&rw_lock->nr_readers, 1) && |
| rw_lock->writers.nr_waiters) { |
| /* passing the lock to the one writer we signal. */ |
| rw_lock->writing = TRUE; |
| __cv_signal(&rw_lock->writers); |
| } |
| spin_unlock(&rw_lock->lock); |
| } |
| |
| void wlock(struct rwlock *rw_lock) |
| { |
| spin_lock(&rw_lock->lock); |
| if (atomic_read(&rw_lock->nr_readers) || rw_lock->writing) { |
| /* If we slept, the lock was passed to us */ |
| cv_wait_and_unlock(&rw_lock->writers); |
| return; |
| } |
| rw_lock->writing = TRUE; |
| spin_unlock(&rw_lock->lock); |
| } |
| |
| void wunlock(struct rwlock *rw_lock) |
| { |
| /* Pass the lock to another writer (we leave writing = TRUE) */ |
| spin_lock(&rw_lock->lock); |
| if (rw_lock->writers.nr_waiters) { |
| /* Just waking one */ |
| __cv_signal(&rw_lock->writers); |
| spin_unlock(&rw_lock->lock); |
| return; |
| } |
| rw_lock->writing = FALSE; |
| atomic_set(&rw_lock->nr_readers, rw_lock->readers.nr_waiters); |
| __cv_broadcast(&rw_lock->readers); |
| spin_unlock(&rw_lock->lock); |
| } |