SX(9)

πŸ“¦
version 15.1 Β· 2 versions
, in freebsd 15.1

πŸ’… SX(9) β€” kernel shared/exclusive lock πŸ’…

okay so bestie, this is a page from the Kernel Developer's Manual, and it's all about sx β€” the kernel's shared/exclusive lock. it's a lot of names on the marquee, so let's just list the whole squad: sx, sx_init, sx_init_flags, sx_destroy, sx_slock, sx_xlock, sx_slock_sig, sx_xlock_sig, sx_try_slock, sx_try_xlock, sx_sunlock, sx_xunlock, sx_unlock, sx_try_upgrade, sx_downgrade, sx_sleep, sx_xholder, sx_xlocked, sx_has_waiters, sx_assert, SX_SYSINIT, SX_SYSINIT_FLAGS. all of them. one page. iconic behavior honestly πŸŽ€


✨ SYNOPSIS ✨

first you gotta invite the headers in:

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

then the whole cast, in order, no cap:

void
sx_init(struct sx *sx, const char *description);

void
sx_init_flags(struct sx *sx, const char *description, int opts);

void
sx_destroy(struct sx *sx);

void
sx_slock(struct sx *sx);

void
sx_xlock(struct sx *sx);

int
sx_slock_sig(struct sx *sx);

int
sx_xlock_sig(struct sx *sx);

int
sx_try_slock(struct sx *sx);

int
sx_try_xlock(struct sx *sx);

void
sx_sunlock(struct sx *sx);

void
sx_xunlock(struct sx *sx);

void
sx_unlock(struct sx *sx);

int
sx_try_upgrade(struct sx *sx);

void
sx_downgrade(struct sx *sx);

int
sx_sleep(void *chan, struct sx *sx, int priority, const char *wmesg,
    int timo);

struct thread *
sx_xholder(struct sx *sx);

int
sx_xlocked(const struct sx *sx);

int
sx_has_waiters(const struct sx *sx);

and for the assertion girlies, you need these compile options or the party doesn't start:

options INVARIANTS
options INVARIANT_SUPPORT

void
sx_assert(const struct sx *sx, int what);

also this one, different header, don't forget it:

#include <sys/kernel.h>

SX_SYSINIT(name, struct sx *sx, const char *desc);

SX_SYSINIT_FLAGS(name, struct sx *sx, const char *desc, int flags);

🌸 DESCRIPTION 🌸

so shared/exclusive locks exist to protect data that gets read wayyyyy more often than it gets written. that's the whole vibe. πŸ¦‹

⚠️ real talk though: shared/exclusive locks do not do priority propagation like mutexes and reader/writer locks do to prevent priority inversions. so like… use them prudently, bestie. this is not a "slap it on everything" situation.

πŸ’– making one

you create a shared/exclusive lock with either sx_init() or sx_init_flags(), where sx is a pointer to space for a struct sx, and description is a pointer to a null-terminated character string describing the lock (basically its little name tag 🏷️).

the opts argument to sx_init_flags() is where you pick a set of optional flags to alter the behavior of sx. it contains one or more of these:

  • SX_DUPOK β€” witness should not log messages about duplicate locks being acquired. mind your business, witness.
  • SX_NOWITNESS β€” tells witness(4) to straight up ignore this lock. not me ghosting the witness system πŸ™ˆ
  • SX_NOPROFILE β€” do not profile this lock.
  • SX_RECURSE β€” allows threads to recursively acquire exclusive locks for sx.
  • SX_QUIET β€” do not log any operations for this lock via ktr(4).
  • SX_NEW β€” if the kernel has been compiled with options INVARIANTS, sx_init() will assert that the sx has not been initialized multiple times without intervening calls to sx_destroy(), unless this option is specified. i.e. no secretly re-initializing the same lock, that's shady behavior πŸ‘€

πŸ’€ destroying one

locks get destroyed with sx_destroy(). but the lock sx must not be locked by any thread when it is destroyed. like, nobody holding it. zero threads. empty. periodt.

πŸŽ€ shared locks: acquire & release

threads acquire and release a shared lock by calling sx_slock(), sx_slock_sig() or sx_try_slock() β€” and releasing with sx_sunlock() or sx_unlock().

πŸ‘‘ exclusive locks: acquire & release

threads acquire and release an exclusive lock by calling sx_xlock(), sx_xlock_sig() or sx_try_xlock() β€” and releasing with sx_xunlock() or sx_unlock().

🌈 upgrades & downgrades (main character energy)

a thread can attempt to upgrade a currently held shared lock to an exclusive lock by calling sx_try_upgrade(). and a thread that has an exclusive lock can downgrade it to a shared lock by calling sx_downgrade(). glow up, glow down πŸ’…

🫢 the try-functions, explained

sx_try_slock() and sx_try_xlock() will return 0 if the shared/exclusive lock cannot be acquired immediately; otherwise the shared/exclusive lock will be acquired and a non-zero value will be returned.

sx_try_upgrade() will return 0 if the shared lock cannot be upgraded to an exclusive lock immediately; otherwise the exclusive lock will be acquired and a non-zero value will be returned.

😭 the signal-safe versions

sx_slock_sig() and sx_xlock_sig() do the same as their normal versions, but performing an interruptible sleep. they return a non-zero value if the sleep has been interrupted by a signal or an interrupt, otherwise 0.

πŸ“ sleeping on it

a thread can atomically release a shared/exclusive lock while waiting for an event by calling sx_sleep(). for more details on the parameters to this function, see sleep(9).

🧚 assertions (the INVARIANTS girlies)

when compiled with options INVARIANTS and options INVARIANT_SUPPORT, the sx_assert() function tests sx for the assertions specified in what, and panics if they are not met. pick exactly one of these:

  • SA_LOCKED β€” assert that the current thread has either a shared or an exclusive lock on the sx lock pointed to by the first argument.
  • SA_SLOCKED β€” assert that the current thread has a shared lock on the sx lock pointed to by the first argument.
  • SA_XLOCKED β€” assert that the current thread has an exclusive lock on the sx lock pointed to by the first argument.
  • SA_UNLOCKED β€” assert that the current thread has no lock on the sx lock pointed to by the first argument.

and on top of that, one of these optional assertions may be included with either an SA_LOCKED, SA_SLOCKED, or SA_XLOCKED assertion:

  • SA_RECURSED β€” assert that the current thread has a recursed lock on sx.
  • SA_NOTRECURSED β€” assert that the current thread does not have a recursed lock on sx.

πŸ”₯ who's holding it?

sx_xholder() will return a pointer to the thread which currently holds an exclusive lock on sx. if no thread holds an exclusive lock on sx, then NULL is returned instead. nobody home, no drama πŸ«₯

sx_xlocked() will return non-zero if the current thread holds the exclusive lock; otherwise, it will return zero.

sx_has_waiters() will return non-zero if there are threads waiting for this lock; otherwise, it will return zero. the function assumes (but does not assert) that the caller already holds the lock and that it is interested in other threads waiting for it to release the lock. read that twice, it's a vibe check on your assumptions ⭐

✨ sx_unlock() is a macro btw

for ease of programming, sx_unlock() is provided as a macro frontend to the respective functions, sx_sunlock() and sx_xunlock(). algorithms that are aware of what state the lock is in should use either of the two specific functions for a minor performance benefit. iykyk πŸŽ€

🌟 SX_SYSINIT()

the SX_SYSINIT() macro is used to generate a call to the sx_sysinit() routine at system startup in order to initialize a given sx lock. the parameters are the same as sx_init() but with an additional argument, name, that is used in generating unique variable names for the related structures associated with the lock and the sysinit routine. the SX_SYSINIT_FLAGS() macro can similarly be used to initialize a given sx lock using sx_init_flags().

πŸ’€ the one big NO

a thread may not hold both a shared lock and an exclusive lock on the same lock simultaneously β€” attempting to do so will result in deadlock. not the cute kind of lock-in. the bad kind. fr fr do not.

🌈 CONTEXT 🌈

a thread may hold a shared or exclusive lock on an sx lock while sleeping. as a result, an sx lock may not be acquired while holding a mutex.

why? ok so picture it: if one thread slept while holding an sx lock, while another thread blocked on the same sx lock after acquiring a mutex, then the second thread would effectively end up sleeping while holding a mutex β€” which is not allowed. chain reaction of bad vibes. 🚫


πŸ¦‹ SEE ALSO πŸ¦‹

lock(9), locking(9), mutex(9), panic(9), rwlock(9), sema(9) β€” go read those next, bestie, they're all linked in the same friend group πŸ’–


πŸ™ˆ BUGS πŸ™ˆ

a kernel without WITNESS cannot assert whether the current thread does or does not hold a shared lock. SA_LOCKED and SA_SLOCKED can only assert that any thread holds a shared lock β€” they cannot ensure that the current thread holds a shared lock. further, SA_UNLOCKED can only assert that the current thread does not hold an exclusive lock. so like… the assertions are doing their best but they're not omniscient 😭


and that's the whole page, bestie! πŸ’œ this one came from the Debian build of the manpage, dated Apri 30, 2026 (yes, "Apri" β€” we love a typo that ate, no notes πŸŽ€), and the footer tags it SX(9). now go forth and lock responsibly ✨🫢

πŸ“¦ where this page comes from
  • freebsd 15.1: base 15.1
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms