KERNEL_LOCK(9)

πŸ“¦ version 11.0, in nixpkgs unstable

✨ KERNEL_LOCK(9) ✨

Okay so bestie, welcome to the KERNEL_LOCK(9) page from the Kernel Developer's Manual. This is the compatibility with legacy uniprocessor code situation, and honestly? It's giving "we're transitioning, don't get too comfortable." Let's get into it.

πŸ’– NAME πŸ’–

KERNEL_LOCK β€” compatibility with legacy uniprocessor code. That's the whole vibe: it's lowkey a bridge from the old single-CPU era, and we're gonna explain why you should not be moving into it long-term.

πŸŽ€ SYNOPSIS πŸŽ€

#include <sys/systm.h>

void
KERNEL_LOCK(int nlocks, struct lwp *l);

void
KERNEL_UNLOCK_ONE(struct lwp *l);

void
KERNEL_UNLOCK_ALL(struct lwp *l, int *nlocksp);

void
KERNEL_UNLOCK_LAST(struct lwp *l);

bool
KERNEL_LOCKED_P();

🌸 DESCRIPTION 🌸

So the KERNEL_LOCK facility is literally there to gradually transition software from the kernel's legacy uniprocessor execution model β€” where the kernel runs on only a single CPU and never in parallel on multiple CPUs β€” to a multiprocessor system. It's a glow-up in progress, not the final look.

New code should not use KERNEL_LOCK. No cap. It's meant only for the gradual transition of NetBSD to natively MP-safe code, which uses mutex(9) or other locking(9) facilities to synchronize between threads and interrupt handlers. Using KERNEL_LOCK hurts system performance and responsiveness, and this man page exists only to document the legacy API so it's easier to transition away from it. It's a "for reference only, bestie" situation. Not iconic, but necessary for the lore.

The kernel lock β€” sometimes also known as the 'giant lock' or 'big lock' β€” is a recursive exclusive spin-lock that can be held by a CPU at any interrupt priority level and is dropped while sleeping. The way that works:

  • recursive β€” If a CPU already holds the kernel lock, it can be acquired again and again, as long as it is released an equal number of times. It's like stacking bracelets: you gotta take off exactly as many as you put on. πŸ’…
  • exclusive β€” Only one CPU at a time can hold the kernel lock. One main character only, no sharing the spotlight.
  • spin-lock β€” When one CPU holds the kernel lock and another CPU wants to hold it, the second CPU 'spins', i.e., repeatedly executes instructions to see if the kernel lock is available yet, until the first CPU releases it. During this time, no other threads can run on the spinning CPU. That means holding the kernel lock for long periods of time, such as nontrivial computation, must be avoided. Under LOCKDEBUG kernels, holding the kernel lock for too long can lead to 'spinout' crashes. So like, don't camp on it. πŸ’€
  • held by a CPU β€” The kernel lock is held by a CPU, not by a process, kthread, LWP, or interrupt handler. It may be shared by a kthread LWP and several softint LWPs at the same time, for example, if the softints interrupted the thread on a CPU. It's a CPU accessory, not a personality trait of your thread.
  • any interrupt priority level β€” The kernel lock does not block interrupts; subsystems running with the kernel lock use spl(9) to synchronize with interrupt handlers. Interrupt handlers that are not marked MP-safe are always run with the kernel lock. If the interrupt arrives on a CPU where the kernel lock is already held, it is simply taken again recursively on interrupt entry and released to its original recursion depth on interrupt exit. Recursion again β€” it ate and left no crumbs.
  • dropped while sleeping β€” Any time the kernel sleeps to let other threads run, for any reason including tsleep(9) or condvar(9) or even adaptive mutex(9) locks, it releases the kernel lock before going to sleep and then reacquires it afterward. This means, for instance, that although data structures accessed only under the kernel lock won't be changed before the sleep, they may be changed by another thread during the sleep. For example, the following program may crash on an assertion failure because the sleep in mutex_enter(9) can allow another CPU to run and change the global variable x:
KERNEL_LOCK(1, NULL);
x = 42;
mutex_enter(...);
...
mutex_exit(...);
KASSERT(x == 42);
KERNEL_UNLOCK_ONE(NULL);

This means simply introducing calls to mutex_enter(9) and mutex_exit(9) can break kernel-locked assumptions. Subsystems need to be consistently converted from KERNEL_LOCK and spl(9) to mutex(9), condvar(9), etc.; mixing mutex(9) and KERNEL_LOCK usually doesn't work. It's not a "mix and match" situation, bestie. Pick a lane and commit. Slay, but only if you're converting away.

Holding the kernel lock does not prevent other code from running on other CPUs at the same time. It only prevents other kernel-locked code from running on other CPUs at the same time. Read that again: it's not a global pause button. It's a very specific "you can't sit with us" for other kernel-locked code.

πŸ¦‹ FUNCTIONS πŸ¦‹

  • KERNEL_LOCK(nlocks, l) β€” Acquire nlocks recursive levels of kernel lock. If the kernel lock is already held by another CPU, spins until it can be acquired by this one. If the kernel lock is already held by this CPU, records the kernel lock recursion depth and returns immediately. Most of the time nlocks is 1, but code that deliberately releases all of the kernel locks held by the current CPU in order to sleep and later reacquire the same number of kernel locks will pass a value of nlocks obtained from KERNEL_UNLOCK_ALL().
  • KERNEL_UNLOCK_ONE(l) β€” Release one level of the kernel lock. Equivalent to KERNEL_UNLOCK(1, l, ,NULL ). (Yes, that's exactly how it's written, no I will not be fixing it, periodt.) πŸ’€
  • KERNEL_UNLOCK_ALL(l, nlocksp) β€” Store the kernel lock recursion depth at nlocksp and release all recursive levels of the kernel lock. This is often used inside logic implementing sleep, around a call to mi_switch(9), so that the same number of recursive kernel locks can be reacquired afterward once the thread is reawoken:
int nlocks;

KERNEL_UNLOCK_ALL(l, &nlocks);
... mi_switch(l) ...
KERNEL_LOCK(nlocks, l);
  • KERNEL_UNLOCK_LAST(l) β€” Release the kernel lock, which must be held at exactly one level. This is normally used at the end of a non-MP-safe thread, which was known to have started with exactly one level of the kernel lock, and is now about to exit. It's the "last one out, turn off the lights" move. ✨
  • KERNEL_LOCKED_P() β€” True if the kernel lock is held. To be used only in diagnostic assertions with KASSERT(9). It's a vibe check, not a lifestyle.

The legacy argument l must be NULL or curlwp, which mean the same thing. Either one, bestie, they're the same.

πŸ”₯ NOTES πŸ”₯

Some NetBSD kernel abstractions execute caller-specified functions with the kernel lock held by default, for compatibility with legacy code, but can be explicitly instructed not to hold the kernel lock by passing an MP-safe flag:

The following NetBSD subsystems are still kernel-locked and need re-engineering to take advantage of parallelism on multiprocessor systems β€” fr fr, they're not there yet:

All interrupt handlers at IPL_VM, or lower (spl(9)) run with the kernel lock on most ports.

🌈 SEE ALSO 🌈

locking(9), mutex(9), spl(9)


That's the whole page, bestie. It came from Debian, dated February 13, 2022, and it's the KERNEL_LOCK(9) page β€” now go forth and use mutex(9) instead, mwah! πŸ’…πŸ«Ά

πŸ“¦ where this page comes from