Fundamentals 15 min read

Linux Kernel Locks: Semaphores, Mutexes, RWLocks, Atomics, Spinlocks & Barriers

The article categorizes Linux kernel synchronization primitives by their target—critical sections, CPUs, memory, or cache—detailing atomic variables, spinlocks, semaphores, mutexes, read‑write locks, preemption, RCU, and memory barriers, explaining their mechanisms, trade‑offs, and typical kernel usage examples.

Linux Code Review Hub
Linux Code Review Hub
Linux Code Review Hub
Linux Kernel Locks: Semaphores, Mutexes, RWLocks, Atomics, Spinlocks & Barriers

Atomic Variables and Spinlocks – CPU

Atomic variables provide lock‑free, chip‑level atomicity for read‑modify‑write sequences on multi‑core x86 systems using the lock instruction. A typical declaration is atomic_int x; or atomic<int> x;. Spinlocks protect data on multi‑CPU systems by busy‑waiting when the lock is already held, avoiding the overhead of sleeping.

#include <linux/spinlock.h>
spinlock_t my_lock;

void my_function(void)
{
    spin_lock(&my_lock);
    // access shared resource
    spin_unlock(&my_lock);
}

If a thread fails to acquire a spinlock it loops, continuously checking the lock state. This yields lower latency than a mutex when the critical section is very short, but excessive spinning on many CPUs reduces overall performance.

Semaphores and Mutexes – Critical Section

Semaphores are counting resources. A semaphore value of 3 indicates three available resources; sem_wait() decrements the count (blocking if zero) and sem_post() increments it (unblocking waiting threads). The kernel implements a semaphore as:

struct semaphore {
    spinlock_t lock; // protects the semaphore itself
    unsigned int count;
    struct list_head wait_list;
};

Mutexes allow only one thread into the critical section. When a thread cannot obtain the mutex it is put to sleep, causing a context switch to kernel mode and later a wake‑up when the mutex becomes available. A timed mutex ( std::timed_mutex) can give up after a specified timeout using try_lock_for():

std::timed_mutex g_mutex;
if (g_mutex.try_lock_for(std::chrono::seconds(2))) {
    // do something
} else {
    std::cout << "lock acquisition failed";
}

Read‑Write Locks – Critical Section

Read‑write locks separate read and write protection, allowing multiple concurrent readers while restricting writers to exclusive access. This reduces lock granularity in read‑heavy workloads and can be implemented on top of spinlocks.

Preemption – CPU

Kernel preemption is tracked by the preempt_count field: it is incremented on lock acquisition and decremented on release. When the count is zero the kernel may preempt the current task; a non‑zero value disables preemption.

Per‑CPU Variables – Cache

Per‑CPU variables solve cache‑coherency issues by giving each CPU its own instance of a variable, avoiding false sharing and ensuring that updates are visible only after explicit synchronization.

RCU Mechanism and Memory Barriers – Memory

Read‑Copy‑Update (RCU) permits many readers to access data concurrently while writers work on a copy and replace the original atomically. The core steps are (1) copy the data, (2) modify the copy, and (3) atomically switch the pointer, followed by deferred reclamation of the old memory.

Memory barriers enforce ordering of memory operations. The kernel provides generic barriers ( mb()), write‑only barriers ( wmb()), and read‑only barriers ( rmb()). Example macro definitions:

#define mb() _asm__volatile("mfence" ::: "memory")
#define barrier() __asm__ __volatile__("" ::: "memory")

In C++11, std::atomic_thread_fence(std::memory_order_acquire) and std::atomic_thread_fence(std::memory_order_release) provide similar semantics. The acquire fence guarantees that subsequent reads/writes are not reordered before the fence; the release fence guarantees prior operations are completed before the fence.

Kernel Usage Examples

Locks are used throughout the kernel to protect critical data structures:

Process scheduling:

spin_lock(&rq->lock); … spin_unlock(&rq->lock);

File system metadata:

spin_lock(&inode->i_lock); … spin_unlock(&inode->i_lock);

Network stack:

read_lock(&rt_hash_lock); … read_unlock(&rt_hash_lock);

Memory management:

spin_lock(&mm->page_table_lock); … spin_unlock(&mm->page_table_lock);

These examples illustrate how each lock type fits a specific synchronization need within the Linux kernel.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

LinuxSynchronizationSemaphoreLocksSpinlockmemory barrier
Linux Code Review Hub
Written by

Linux Code Review Hub

A professional Linux technology community and learning platform covering the kernel, memory management, process management, file system and I/O, performance tuning, device drivers, virtualization, and cloud computing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.