blob: 97b79ef2f9d6102f44bda47475777d0efb156ef1 [file] [log] [blame]
/* 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);
}