Spinlocks & Mutexes
Linux kernel modules must handle concurrency carefully to avoid race conditions and deadlocks. Spinlocks and mutexes are two fundamental synchronization primitives used to protect critical sections of code. While both ensure exclusive access to shared resources, their usage patterns, performance characteristics, and use cases differ significantly. This section explores their differences, provides practical examples, and highlights best practices for safe kernel module development.
Spinlocks: Busy-Waiting for Short Critical Sections¶
Overview¶
Spinlocks are lightweight synchronization mechanisms that busy-wait (loop indefinitely) until the lock is available. They are ideal for short, atomic operations where the critical section is expected to be held for a minimal amount of time. Spinlocks are typically used in interrupt context or for protecting small data structures.
Usage Scenarios¶
- Protecting small data structures (e.g., counters, flags)
- Synchronizing access to hardware registers
- Short-lived operations in interrupt handlers
API Functions¶
spinlock_t my_lock;
spin_lock(&my_lock); // Acquire the lock
spin_unlock(&my_lock); // Release the lock
spin_is_locked(&data.lock); // Check if the lock is held (macro)
Example: Protecting a Shared Counter¶
#include <linux/module.h>
#include <linux/spinlock.h>
struct my_data {
int counter;
spinlock_t lock;
};
static struct my_data data = {
.counter = 0,
.lock = __SPIN_LOCK_UNLOCKED(data.lock),
};
static int __init spinlock_example_init(void) {
spin_lock(&data.lock);
data.counter++;
spin_unlock(&data.lock);
return 0;
}
module_init(spinlock_example_init);
Best Practices¶
- Avoid holding spinlocks for long periods (e.g., in process context). This wastes CPU cycles.
- Initialize spinlocks explicitly using
spin_lock_init()or__SPIN_LOCK_UNLOCKED(). - Use spinlocks only for atomic operations where the critical section is minimal.
Mutexes: Sleeping for Longer Critical Sections¶
Overview¶
Mutexes are designed for longer critical sections where the holder may sleep. When a mutex is contended, the thread blocks (sleeps) until the lock is released. Mutexes are suitable for process context and complex operations.
Usage Scenarios¶
- Synchronizing access to large data structures
- Performing I/O operations or sleeping
- Protecting operations that may block (e.g., file reads)
API Functions¶
mutex_t my_mutex;
mutex_init(&my_mutex); // Initialize the mutex
mutex_lock(&my_mutex); // Acquire the lock
mutex_unlock(&my_mutex); // Release the lock
mutex_trylock(&my_mutex); // Attempt to acquire without blocking
Example: Protecting a Long-Running Operation¶
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/delay.h>
struct my_data {
int value;
mutex_t lock;
};
static struct my_data data = {
.value = 0,
};
static int __init mutex_example_init(void) {
mutex_init(&data.lock);
mutex_lock(&data.lock);
msleep(100); // Simulate a long operation
data.value = 42;
mutex_unlock(&data.lock);
return 0;
}
module_init(mutex_example_init);
Best Practices¶
- Use mutexes in process context where sleeping is acceptable.
- Minimize the scope of critical sections to reduce contention.
- Use
mutex_trylock()for non-blocking acquisition in cases where fallback logic is needed.
Comparison and Best Practices¶
| Feature | Spinlock | Mutex |
|---|---|---|
| Blocking Behavior | Busy-waits (no sleep) | Sleeps when contended |
| Use Case | Short, atomic operations | Longer operations with sleep |
| Context | Can be used in interrupt context | Only in process context |
| Performance | Lower latency for short holds | Higher latency for long holds |
Key Considerations¶
- Avoid using spinlocks in process context if the critical section might take significant time. This can lead to high CPU usage and potential deadlocks.
- Use lockdep (kernel's lock dependency checker) to detect deadlocks and improper lock usage during module development.
- Always pair lock acquisition with proper release to prevent deadlocks.
Key takeaways¶
- Use spinlocks for short, atomic operations where the critical section is minimal and the lock is expected to be released quickly.
- Use mutexes for longer operations that may involve sleeping or complex resource management.
- Always initialize locks properly and ensure critical sections are as small as possible to minimize contention.
- Leverage
lockdepto detect and prevent deadlocks in kernel modules.