Linux Kernel Spinlock Tutorial: spin_lock(), spin_unlock() and DEFINE_SPINLOCK() Explained-Linux Device Driver Training in Hyderabad

Linux Kernel Spinlock Tutorial: spin_lock(), spin_unlock() and DEFINE_SPINLOCK() Explained
Linux Kernel Spinlock Tutorial: spin_lock() and spin_unlock() Explained
Free Linux Kernel Development Course — Kernel Synchronization Series (Updated for Kernel 6.x)
Lecture 11
Kernel Synchronization Chapter
Beginner Friendly

Every driver eventually needs to protect a small piece of shared data, and sometimes that data is touched from a context where sleeping is not allowed at all. That is exactly where the linux kernel spinlock comes in. In this lecture of our free linux kernel programming course, we introduce the spinlock API from scratch: how to declare one, how to initialize it, how to lock and unlock it, and how to structure a simple driver around it.

What You Will Learn
When to choose spinlock over mutex spinlock_t and its header spin_lock_init() vs DEFINE_SPINLOCK() spin_lock() / spin_unlock() usage Why there is no spin_lock_destroy() Building a simple spinlock-protected driver
Prerequisites

This lecture assumes you have completed the earlier mutex lectures in this free linux device drivers course, including the mutex vs spinlock conceptual comparison. You do not need any prior spinlock experience.

Quick Recap: When Do You Reach for a Spinlock?

We covered this rule in detail earlier in the course, but it is worth restating because it is the single most important decision point in kernel locking:

  • If the critical section runs in an atomic or interrupt context, where sleeping is not permitted — use a spinlock.
  • If the critical section runs in process context and sleeping inside it is acceptable or necessary — use a mutex.

From this point on in the lecture, we assume you have already decided that a spinlock is the right tool for the job.

What Is a Spinlock in Linux Kernel Programming?

A spinlock is a busy-wait lock. Unlike a mutex, a task that cannot immediately acquire a spinlock does not go to sleep. Instead, it spins in a tight loop, repeatedly checking whether the lock has become free, until it succeeds. This makes spinlocks appropriate for very short critical sections, and mandatory for critical sections that must run in contexts where the scheduler cannot be invoked, such as hardware interrupt handlers.

The spinlock_t Type and Required Header

To use any of the spinlock APIs, you must include the appropriate kernel header:

#include <linux/spinlock.h>

A spinlock is represented by the spinlock_t type, a structure defined internally by the kernel. Just like a mutex, a spinlock must always be initialized to the unlocked state before it is used for the first time.

Declaring and Initializing a Spinlock

There are two common ways to declare and initialize a spinlock, and both are equally valid depending on your situation.

Dynamic Initialization

spinlock_t ep_lock;
spin_lock_init(&ep_lock);

Use this style when the spinlock is embedded inside a structure that is allocated at runtime, such as a per-device context structure created in your driver’s probe function.

Static Initialization

static DEFINE_SPINLOCK(ep_lock);

This single macro both declares and initializes the spinlock in its unlocked state at compile time. It is the preferred style for module-scope (global/static) spinlocks that exist for the lifetime of the module.

StyleWhen To UseExample
Dynamic (spin_lock_init())Spinlock is a member of a runtime-allocated structureInside a per-device context struct
Static (DEFINE_SPINLOCK())Module-scope global spinlockProtecting a small set of global driver counters

Locking and Unlocking: spin_lock() and spin_unlock()

The simplest form of the spinlock API mirrors the mutex API you already know:

void spin_lock(spinlock_t *lock);

/* << critical section >> */

void spin_unlock(spinlock_t *lock);

Everything between the spin_lock() and spin_unlock() calls is the critical section. Because a spinlock busy-waits, that critical section must be kept as short as possible — every CPU spinning on the lock is wasting cycles doing nothing useful.

Spinlock Acquire/Release Flow
CPU 0 CPU 1 —– —– spin_lock(&lock) spin_lock(&lock) | lock is free -> acquired | lock is held by CPU 0 v v <> busy-wait loop | | (keeps checking lock state) v | spin_unlock(&lock) | | v | lock now free -> acquired v | continues other work v <> | v spin_unlock(&lock)

Why There Is No spin_lock_destroy()

If you have been through the mutex lectures, you will remember mutex_destroy() as part of a mutex’s lifecycle. Spinlocks have no equivalent destroy call, because a spinlock does not hold any dynamically allocated internal resources that need cleanup — it is simply a lightweight value that gets reset. Once you are done using a spinlock, there is nothing further to release.

Original Example: A Simple Spinlock-Protected Counter Driver

Below is an original, kernel 6.x-compatible driver skeleton that protects two shared integers using a spinlock. This mirrors the structure of the mutex-based counter driver from earlier in the course, but swaps the locking primitive.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>

#define OURMODNAME "ep_spinlock_demo"

static int ep_count_a, ep_count_b = 1;
static DEFINE_SPINLOCK(ep_counter_lock);

/* Safely increments both counters together, keeping them consistent */
static void ep_increment_counters(void)
{
    spin_lock(&ep_counter_lock);

    ep_count_a++;
    ep_count_b++;

    spin_unlock(&ep_counter_lock);
}

/* Safely reads both counters as a consistent pair */
static void ep_read_counters(int *a, int *b)
{
    spin_lock(&ep_counter_lock);

    *a = ep_count_a;
    *b = ep_count_b;

    spin_unlock(&ep_counter_lock);
}

static int __init ep_spinlock_demo_init(void)
{
    pr_info("%s: module loaded\n", OURMODNAME);
    ep_increment_counters();
    return 0;
}

static void __exit ep_spinlock_demo_exit(void)
{
    int a, b;
    ep_read_counters(&a, &b);
    pr_info("%s: unloading, counters = %d, %d\n", OURMODNAME, a, b);
}

module_init(ep_spinlock_demo_init);
module_exit(ep_spinlock_demo_exit);
MODULE_LICENSE("GPL");

Notice the pattern: any function that touches ep_count_a or ep_count_b takes the spinlock first and releases it immediately after, keeping the protected window as small as possible.

A Peek Ahead: Spinlock Variants

The plain spin_lock()/spin_unlock() pair shown here is only safe when you are certain the critical section can never be entered from a hardware interrupt handler. When interrupts can also touch the same protected data, the kernel provides variants such as spin_lock_irqsave() and spin_lock_bh() that additionally disable interrupts or softirqs around the critical section. We will cover these variants, and exactly when each one is required, in the next lecture of this free linux kernel development course.

Best Practices for Spinlocks

  • Keep the critical section as short as possible — a handful of instructions, not loops or I/O.
  • Never call any function that might sleep (memory allocation with GFP_KERNEL, mutex_lock(), blocking I/O) while holding a spinlock.
  • Prefer DEFINE_SPINLOCK() for module-scope locks and spin_lock_init() for locks embedded in runtime-allocated structures.
  • One spinlock should protect one clearly defined piece of data, following the same one-lock-per-structure discipline covered earlier in this course.

Common Mistakes and Troubleshooting

MistakeSymptomFix
Sleeping while holding a spinlock“Scheduling while atomic” kernel warning/crashNever call blocking APIs inside a spin_lock()/spin_unlock() section
Holding the spinlock too longOther CPUs waste cycles spinning; system feels sluggishShrink the critical section; move non-essential work outside the lock
Using spin_lock() where interrupts can also touch the dataDeadlock if an interrupt fires on the same CPU while the lock is heldUse spin_lock_irqsave() instead (covered in the next lecture)

Key Takeaways

  • A spinlock busy-waits instead of sleeping, so it is required for atomic/interrupt context critical sections.
  • Include <linux/spinlock.h> and initialize with either spin_lock_init() or DEFINE_SPINLOCK().
  • The basic API is spin_lock() / spin_unlock(), symmetric to the mutex API you already know.
  • There is no destroy call for spinlocks — nothing to free once you stop using one.
  • Keep critical sections short, and never sleep while holding a spinlock.

Conclusion

The spinlock is the second essential locking primitive every Linux kernel and driver developer must understand, complementing the mutex you learned earlier in this free linux kernel development course. With the basic API, initialization styles, and a working example driver under your belt, you are ready for the next lecture, where we dig into the interrupt-safe spinlock variants that real-world drivers rely on.

FAQ

Q1. What is the main difference between a mutex and a spinlock?
A mutex puts a waiting task to sleep; a spinlock makes the waiting task busy-wait (spin) instead of sleeping.

Q2. Which header do I need for spinlocks?
<linux/spinlock.h>.

Q3. What is the difference between spin_lock_init() and DEFINE_SPINLOCK()?
spin_lock_init() initializes a spinlock at runtime, useful for one embedded in a dynamically allocated structure. DEFINE_SPINLOCK() declares and initializes a static, module-scope spinlock at compile time.

Q4. Can I sleep while holding a spinlock?
No. Sleeping while holding a spinlock is a serious bug and will trigger a “scheduling while atomic” error on a debug kernel.

Q5. Why is there no spin_lock_destroy() function?
Because a spinlock does not allocate any internal resources that need to be freed; there is simply nothing to destroy.

Q6. Should I use spin_lock() inside an interrupt handler?
Plain spin_lock() is not always safe if the same lock is also taken from an interrupt handler on the same CPU. In that situation you need spin_lock_irqsave(), covered in the next lecture.

Q7. How short should a spinlock critical section be?
As short as practically possible — a handful of instructions. Any I/O, memory allocation, or blocking call has no place inside a spinlock-protected section.

linux kernel spinlock spin_lock spin_unlock DEFINE_SPINLOCK free linux kernel development course free linux device drivers course free embedded systems course
Continue the Free Linux Kernel Programming Course

More kernel synchronization lectures are coming up in this free embedded systems course.

Next Lecture: Spinlock Interrupt-Safe Variants →

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *