RMLOCK(9)

🌸 RMLOCK(9) β€” kernel reader/writer lock, but make it read-mostly πŸ’…

okay bestie, so this is rmlock(9) from the Kernel Developer's Manual ✨ and it's literally the lock you reach for when your data is read-mostly. Not me being dramatic β€” the name says it. Let's get into it 🫢


✨ NAME ✨

rmlock, rm_init, rm_init_flags, rm_destroy, rm_rlock, rm_try_rlock, rm_wlock, rm_runlock, rm_wunlock, rm_wowned, rm_sleep, rm_assert, RM_SYSINIT, RM_SYSINIT_FLAGS β€” a kernel reader/writer lock optimized for read-mostly access patterns πŸ‘‘


✨ SYNOPSIS ✨

#include <sys/param.h>
#include <sys/lock.h>
#include <sys/rmlock.h>

void
rm_init(struct rmlock *rm, const char *name);

void
rm_init_flags(struct rmlock *rm, const char *name, int opts);

void
rm_destroy(struct rmlock *rm);

void
rm_rlock(struct rmlock *rm, struct rm_priotracker* tracker);

int
rm_try_rlock(struct rmlock *rm, struct rm_priotracker* tracker);

void
rm_wlock(struct rmlock *rm);

void
rm_runlock(struct rmlock *rm, struct rm_priotracker* tracker);

void
rm_wunlock(struct rmlock *rm);

int
rm_wowned(const struct rmlock *rm);

int
rm_sleep(void *wchan, struct rmlock *rm, int priority,
    const char *wmesg, int timo);

options INVARIANTS
options INVARIANT_SUPPORT

void
rm_assert(struct rmlock *rm, int what);

#include <sys/kernel.h>

RM_SYSINIT(name, struct rmlock *rm, const char *desc);

RM_SYSINIT_FLAGS(name, struct rmlock *rm, const char *desc, int flags);

✨ DESCRIPTION ✨

So basically 🌈 read-mostly locks let multiple threads share access to protected data, OR let one single thread have it all to themselves. The shared-access girlies are called readers, because all they do is read the protected data. The exclusive-access one is called a writer, because they're the ones actually modifying stuff πŸ’…

Read-mostly locks are built to be efficient for locks that are almost exclusively used as reader locks β€” so pls use them for protecting data that rarely changes. And heads up: acquiring an exclusive lock after the lock has already been locked for shared access is an expensive operation. Like, it's giving main character tantrum 😭

Normal read-mostly locks are similar to rwlock(9) locks and follow the same lock ordering rules as rwlock(9) locks. They also have full priority propagation, just like mutexes. But unlike rwlock(9), read-mostly locks propagate priority to both readers and writers 🫢 This is implemented via the rm_priotracker structure argument that you hand to rm_rlock() and rm_runlock(). Also: readers can recurse if the lock was initialized with the RM_RECURSE option β€” however, writers are never allowed to recurse. Periodt. 🚫

Sleepable read-mostly locks are created by passing RM_SLEEPABLE to rm_init_flags(). Unlike normal read-mostly locks, sleepable ones follow the same lock ordering rules as sx(9) locks. Sleepable read-mostly locks do not propagate priority to writers, but they do propagate priority to readers. Writers are permitted to sleep while holding a read-mostly lock, but readers are not. And unlike other sleepable locks such as sx(9) locks, readers must use try operations on other sleepable locks to avoid sleeping. iykyk 😴

πŸŽ€ Macros and Functions πŸŽ€

  • rm_init(struct rmlock *rm, const char *name) β€” Initializes the read-mostly lock rm. The name description is used solely for debugging purposes πŸ›. This function must be called before any other operations on the lock. No skipping steps, bestie.
  • rm_init_flags(struct rmlock *rm, const char *name, int opts) β€” Just like rm_init(), but you get to init the read-mostly lock rm with a set of optional flags ✨. The opts argument contains one or more of these:
    • RM_NOWITNESS β€” Instruct witness(4) to ignore this lock πŸ™ˆ
    • RM_RECURSE β€” Allow threads to recursively acquire shared locks for rm πŸ”
    • RM_SLEEPABLE β€” Create a sleepable read-mostly lock 😴
    • RM_NEW β€” If the kernel has been compiled with option INVARIANTS, rm_init_flags() will assert that the rm has not been initialized multiple times without intervening calls to rm_destroy() β€” unless this option is specified. So like, this is your "yes I know what I'm doing" pass πŸ’€
  • rm_rlock(struct rmlock *rm, struct rm_priotracker* tracker) β€” Locks rm as a reader, using tracker to track read owners of the lock for priority propagation. This data structure is only used internally by rmlock and must persist until rm_runlock() has been called. Good news: it can be allocated on the stack, since readers cannot sleep πŸ₯³. If any thread holds this lock exclusively, the current thread blocks, and its priority gets propagated to the exclusive holder. And if the lock was initialized with the RM_RECURSE option, rm_rlock() can be called even when the current thread has already acquired reader access on rm.
  • rm_try_rlock(struct rmlock *rm, struct rm_priotracker* tracker) β€” Tries to lock rm as a reader. rm_try_rlock() returns 0 if the lock cannot be acquired immediately; otherwise the lock will be acquired and a non-zero value will be returned. Note that rm_try_rlock() may fail even while the lock is not currently held by a writer πŸ‘€ β€” it's just like that sometimes. If the lock was initialized with the RM_RECURSE option, rm_try_rlock() will succeed if the current thread has already acquired reader access.
  • rm_wlock(struct rmlock *rm) β€” Locks rm as a writer πŸ‘‘. If there are any shared owners of the lock, the current thread blocks. And nope β€” rm_wlock() cannot be called recursively. Don't even try it.
  • rm_runlock(struct rmlock *rm, struct rm_priotracker* tracker) β€” Releases a shared lock previously acquired by rm_rlock(). The tracker argument must match the tracker argument used for acquiring the shared lock. Matching sets, bestie πŸŽ€
  • rm_wunlock(struct rmlock *rm) β€” Releases an exclusive lock previously acquired by rm_wlock().
  • rm_destroy(struct rmlock *rm) β€” Destroys a lock previously initialized with rm_init(). The rm lock must be unlocked. Like, unlocked unlocked. πŸ”“
  • rm_wowned(const struct rmlock *rm) β€” Returns a non-zero value if the current thread owns an exclusive lock on rm.
  • rm_sleep(void *wchan, struct rmlock *rm, int priority, const char *wmesg, int timo) β€” Atomically releases rm while waiting for an event 😴. The rm lock must be exclusively locked. For more details on the parameters to this function, go see sleep(9).
  • rm_assert(struct rmlock *rm, int what) β€” Asserts that the rm lock is in the state specified by what. If the assertions are not true and the kernel is compiled with options INVARIANTS and options INVARIANT_SUPPORT, the kernel will panic πŸ’€. Currently the following base assertions are supported:
    • RA_LOCKED β€” Assert that the current thread holds either a shared or exclusive lock of rm.
    • RA_RLOCKED β€” Assert that the current thread holds a shared lock of rm.
    • RA_WLOCKED β€” Assert that the current thread holds an exclusive lock of rm.
    • RA_UNLOCKED β€” Assert that the current thread holds neither a shared nor exclusive lock of rm.

In addition, one of the following optional flags may be specified with RA_LOCKED, RA_RLOCKED, or RA_WLOCKED:

  • RA_RECURSED β€” Assert that the current thread holds a recursive lock of rm.
  • RA_NOTRECURSED β€” Assert that the current thread does not hold a recursive lock of rm.

✨ SEE ALSO ✨

locking(9), mutex(9), panic(9), rwlock(9), sema(9), sleep(9), sx(9) β€” go read these too, they're all part of the same friend group 🫢


✨ HISTORY ✨

These functions appeared in FreeBSD 7.0. An icon since day one, fr fr πŸŽ€


✨ AUTHORS ✨

The rmlock facility was written by Stephan Uphoff πŸ’œ This manual page was written by Gleb Smirnoff for rwlock and modified to reflect rmlock by Stephan Uphoff. Collab of the century, honestly πŸ‘‘


✨ BUGS ✨

Heads up, the fine print bestie 🫣
  • The rmlock implementation is currently not optimized for single processor systems.
  • rm_try_rlock() can fail transiently even when there is no writer, while another reader updates the state on the local CPU 😭
  • The rmlock implementation uses a single per CPU list shared by all rmlocks in the system. If rmlocks become popular, hashing to multiple per CPU queues may be needed to speed up the writer lock process. Growth era incoming πŸ“ˆ

and that's the whole page, babe πŸ’– this was the RMLOCK(9) page, straight out of the Debian build dated November 11, 2017 β€” so yes, it's a lil vintage, but she still ate πŸ”₯βœ¨πŸ’…

πŸ“¦ where this page comes from