Reference for Linux kernel locking APIs including spinlocks, mutexes, reader-writer locks, semaphores, and atomic operations, with guidance on choosing the right primitive.
The kernel provides several locking primitives, each with different performance characteristics, sleeping behavior, and valid calling contexts. Choosing the wrong primitive leads to deadlocks, priority inversion, or subtle data corruption.
spinlock_t
Busy-wait lock. Cannot sleep. Safe from any context including interrupt handlers.
struct mutex
Sleeping lock. Only usable from process context. Preferred for most driver and subsystem code.
rwlock_t / rw_semaphore
Allow concurrent readers, exclusive writers. Use when reads vastly outnumber writes.
atomic_t
Lock-free integer operations via hardware atomic instructions. No lock overhead.
Spinlocks are the most basic locking primitive. A thread acquiring a contested spinlock busy-waits (spins) rather than sleeping. This makes them usable from any context — including interrupt handlers — but also means they must be held for very short durations.
Storage for the saved interrupt state. Must be declared by the caller as unsigned long flags;.
Disables local interrupts before acquiring the lock, saving the interrupt state in flags. Restores interrupt state on release. Use this variant whenever the lock may also be acquired from an interrupt handler.
If you hold a spinlock and an interrupt fires on the same CPU, and the interrupt handler tries to acquire the same spinlock, you get a deadlock. Always use spin_lock_irqsave when the lock can be acquired from interrupt context.
Disables software interrupts (bottom halves) before acquiring the lock. Use when the lock is shared between process context and softirq/tasklet handlers but not hardware interrupt handlers.
Mutexes are sleeping locks. A thread that cannot acquire a mutex is put to sleep and woken when the lock becomes available. This makes them suitable for protecting longer critical sections, but they cannot be used from interrupt context.
mutex_lock sleeps until the mutex is acquired. mutex_trylock returns 1 on success and 0 if the mutex is already held — it never sleeps. mutex_unlock releases the lock; only the owner may call it.
int mutex_lock_interruptible(struct mutex *lock);int mutex_lock_killable(struct mutex *lock);
Both variants sleep while waiting but can be interrupted. mutex_lock_interruptible returns -EINTR if a signal is delivered. mutex_lock_killable returns -EINTR only for fatal signals. Always check the return value.
if (mutex_lock_interruptible(&my_mutex)) return -EINTR;/* critical section */mutex_unlock(&my_mutex);
The kernel enforces the following rules (verified by CONFIG_DEBUG_MUTEXES):
Only one task may hold the mutex at a time.
Only the owner may call mutex_unlock.
Recursive locking is not permitted.
A task must not exit while holding a mutex.
Mutexes may not be used in interrupt or softirq context.
struct mutex uses optimistic spinning (MCS lock) before falling back to sleeping. In practice this makes it competitive with spinlocks for short critical sections while still allowing the holder to be preempted.
/* Reader */unsigned long flags;read_lock_irqsave(&my_rwlock, flags);/* read shared data */read_unlock_irqrestore(&my_rwlock, flags);/* Writer */write_lock_irqsave(&my_rwlock, flags);/* modify shared data */write_unlock_irqrestore(&my_rwlock, flags);
Reader-writer spinlocks require more atomic memory operations than plain spinlocks. Unless read-side critical sections are long, a plain spinlock is often faster. The kernel is actively removing rwlock_t from many subsystems in favor of RCU. Do not add new uses without prior review.
You cannot upgrade a read lock to a write lock. If you ever need to write — even rarely — acquire the write lock from the start.
Lockdep is the kernel’s runtime lock dependency validator. It detects potential deadlocks by tracking the order in which locks are acquired and flagging circular dependency chains.
/* Annotate a lock with a custom class key to teach lockdep * that two logically different locks of the same type are * in distinct dependency classes. */static struct lock_class_key my_lock_key;lockdep_set_class(&my_lock, &my_lock_key);
Enable lockdep with CONFIG_PROVE_LOCKING=y and CONFIG_DEBUG_LOCKDEP=y. Violations are printed to the kernel log with a full dependency chain.Lock ordering rule: Always acquire locks in a consistent global order. If lock A is ever acquired while B is held, then B must never be acquired while A is held anywhere else in the kernel.
Use struct mutex. It is the safest and most debuggable sleeping lock. Prefer mutex_lock_interruptible in paths that may block for a long time so that the process remains killable.
Use spinlock_t with spin_lock_irqsave / spin_unlock_irqrestore. Keep the critical section as short as possible — no sleeping, no blocking operations.
For data that is read far more often than written and where read-side latency matters, consider struct rw_semaphore (if sleeping is acceptable) or RCU (for maximum read-side performance with no lock at all).
Use atomic_t or atomic64_t for a single integer variable shared between contexts. No lock required. For reference counts specifically, use refcount_t.