SX(9)
π SX(9) β kernel shared/exclusive lock π
okay so bestie, pull up a chair, because this is SX(9) from the Kernel Developer's Manual and it's literally the blueprint for shared/exclusive locks in the kernel. it's giving "reads all day, writes almost never" energy and i am obsessed. let's get into it π«Ά
β¨ NAME β¨
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_assert, SX_SYSINIT, SX_SYSINIT_FLAGS β that's the whole squad, and they all exist to serve one iconic purpose: the kernel shared/exclusive lock. π
β¨ SYNOPSIS β¨
first, the includes, because obviously:
#include <sys/param.h>
#include <sys/lock.h>
#include <sys/sx.h>
setting up and tearing down:
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);
getting your shared and exclusive locks, bestie:
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);
and letting them go (the healthy thing to do):
void
sx_sunlock(struct sx *sx);
void
sx_xunlock(struct sx *sx);
void
sx_unlock(struct sx *sx);
the glow-up and the downgrade, we love character development:
int
sx_try_upgrade(struct sx *sx);
void
sx_downgrade(struct sx *sx);
napping on a channel while holding the lock (iconic behaviour, more on that later):
int
sx_sleep(void *chan, struct sx *sx, int priority, const char *wmesg,
int timo);
who's holding it, and do i hold it:
struct thread *
sx_xholder(struct sx *sx);
int
sx_xlocked(const struct sx *sx);
and for the paranoid girlies (affectionate), the invariants build options:
options INVARIANTS
options INVARIANT_SUPPORT
void
sx_assert(const struct sx *sx, int what);
plus the sysinit macros, which need a different header:
#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 β¨
okay so the whole concept: shared/exclusive locks protect data that gets read wayyyyy more often than it gets written. that's the vibe. that's the entire point.
π¨ but listen. shared/exclusive locks do not implement priority propagation like mutexes and reader/writer locks do to prevent priority inversions. so bestie, use them prudently. no cap.
you make one 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 that describes the shared/exclusive lock (yes, a whole string, be descriptive, it's cute).
the opts argument to sx_init_flags() is a set of optional flags that alter the behavior of sx, and it contains one or more of these:
SX_NOADAPTIVEβ disable adaptive spinning (instead of sleeping) for lock operations while an exclusive lock holder is executing on another CPU. adaptive spinning is the default unless the kernel was compiled withoptions NO_ADAPTIVE_SX. πSX_DUPOKβ witness should not log messages about duplicate locks being acquired. keep it quiet, keep it cute.SX_NOWITNESSβ tellswitness(4)to ignore this lock entirely. she's not seeing it.SX_NOPROFILEβ do not profile this lock.SX_RECURSEβ allow threads to recursively acquire exclusive locks forsx. πSX_QUIETβ do not log any operations for this lock viaktr(4). shhh. πSX_NEWβ if the kernel was compiled withoptions INVARIANTS,sx_init()will assert that thesxhas not been initialized multiple times without intervening calls tosx_destroy()β unless this option is specified. so if you're re-initializing, say so. periodt.
shared/exclusive locks get destroyed with sx_destroy().
β οΈ the lock sx must not be locked by any thread when it is destroyed. like, nobody. not even one. this is not a drill.
acquiring and releasing, the whole choreography:
- threads acquire and release a shared lock by calling
sx_slock(),sx_slock_sig()orsx_try_slock()andsx_sunlock()orsx_unlock(). - threads acquire and release an exclusive lock by calling
sx_xlock(),sx_xlock_sig()orsx_try_xlock()andsx_xunlock()orsx_unlock(). - a thread can attempt to upgrade a currently held shared lock to an exclusive lock by calling
sx_try_upgrade(). - a thread that has an exclusive lock can downgrade it to a shared lock by calling
sx_downgrade().
the return values, because numbers matter:
sx_try_slock()andsx_try_xlock()return 0 if the shared/exclusive lock cannot be acquired immediately; otherwise the lock will be acquired and a non-zero value will be returned. slay.sx_try_upgrade()returns 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.sx_slock_sig()andsx_xlock_sig()do the same as their normal versions but perform an interruptible sleep. they return a non-zero value if the sleep has been interrupted by a signal or an interrupt, otherwise 0.
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, go read sleep(9) β i'm not retyping her whole biography here. π«Ά
the assertions, when compiled with options INVARIANTS and options INVARIANT_SUPPORT:
sx_assert() tests sx for the assertions specified in what, and panics if they are not met. π one of the following assertions must be specified:
SA_LOCKEDβ assert that the current thread has either a shared or an exclusive lock on thesxlock pointed to by the first argument.SA_SLOCKEDβ assert that the current thread has a shared lock on thesxlock pointed to by the first argument.SA_XLOCKEDβ assert that the current thread has an exclusive lock on thesxlock pointed to by the first argument.SA_UNLOCKEDβ assert that the current thread has no lock on thesxlock pointed to by the first argument.
in addition, one of the following 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 onsx.SA_NOTRECURSEDβ assert that the current thread does not have a recursed lock onsx.
the little helpers:
sx_xholder()will return a pointer to the thread which currently holds an exclusive lock onsx. if no thread holds an exclusive lock onsx, thenNULLis returned instead. nobody home. πsx_xlocked()will return non-zero if the current thread holds the exclusive lock; otherwise, it will return zero.
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. π
the sysinit macros:
- the
SX_SYSINIT()macro is used to generate a call to thesx_sysinit()routine at system startup in order to initialize a givensxlock. the parameters are the same assx_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 givensxlock usingsx_init_flags().
π DEADLOCK ALERT: 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 "might". will. do not test this.
β¨ 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. because otherwise, 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. that's the whole explanation, it's giving domino effect, it's giving chaos. π
ββοΈ
β¨ SEE ALSO β¨
lock(9), locking(9), mutex(9), panic(9), rwlock(9), sema(9)
go read them, bestie. expand your horizons. π¦
β¨ BUGS β¨
okay so here's the messy part, and we're being honest about it:
- a kernel without WITNESS cannot assert whether the current thread does or does not hold a shared lock.
SA_LOCKEDandSA_SLOCKEDcan only assert that any thread holds a shared lock. they cannot ensure that the current thread holds a shared lock.- further,
SA_UNLOCKEDcan only assert that the current thread does not hold an exclusive lock.
so like. she's trying her best but she has limits. π
and that's the whole page, babe. this little gem came out of the Debian build of the kernel developer's manual, dated November 11, 2017 β yeah she's an elder but she still ate. π go forth, lock responsibly, and never hold a shared and an exclusive at the same time. mwah. πβ¨π
π¦ where this page comes from
- debian bookworm: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- debian trixie: freebsd-manpages 12.2-2 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu jammy: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu noble: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu questing: freebsd-manpages 12.2-2
- ubuntu resolute: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu stonking: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- Copyright (C) 2001 Jason Evans <jasone@FreeBSD.org>. All rights reserved
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms