Linux Device Driver Tutorial Part 23 – Spinlock in Linux Kernel Part 1

This is the Series on Linux Device Driver. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Linux Device Driver Tutorial Part 23 – Spinlock in Linux Kernel Part 1.

Prerequisites

In the example section, I had used Kthread to explain Mutex. If you don’t know what is Kthread and How to use it, then I would recommend you to explore that by using below link.

  1. Kthread Tutorial in Linux Kernel
  2. Mutex Tutorial in Linux Kernel

Introduction

In our previous tutorial we have understood the use of Mutex and its Implementation. If you have understood Mutex then Spinlock is also similar. Both are used to protect a shared resource from being modified by two or more processes simultaneously.

SpinLock

In Mutex concept, when thread is trying to lock or acquire the Mutex which is not available then that thread will go to sleep until that Mutex is available. Whereas in Spinlock it is different. The spinlock is a very simple single-holder lock. If a process attempts to acquire a spinlock and it is unavailable, the process will keep trying (spinning) until it can acquire the lock. This simplicity creates a small and fast lock.

Like Mutex, there are two possible states in Spinlock: Locked or Unlocked.

SpinLock in Linux Kernel Device Driver

If the kernel is running on a uniprocessor and CONFIG_SMPCONFIG_PREEMPT aren’t enabled while compiling the kernel then spinlock will not be available. Because there is no reason to have a lock, when no one else can run at the same time.

But if you have disabled CONFIG_SMPand enabled  CONFIG_PREEMPT then spinlock will simply disable preemption, which is sufficient to prevent any races.

Initialize

We can initialize Spinlock in two ways.

  1. Static Method
  2. Dynamic Method

Static Method

You can statically initialize a Spinlock using the macro given below.

The macro given above will create spinlock_t variable in the name of and initialize to UNLOCKED STATE. Take a look at the expansion of DEFINE_SPINLOCK below.

Dynamic Method

If you want to initialize dynamically you can use the method as given below.

You can use any one of the methods.

After initializing the spinlock, there are several ways to use spinlock to lock or unlock, based on where the spinlock is used; either in user context or interrupt context. Let’s look at the approaches with these situations.

Approach 1 (Locking between User context)

If you share data with user context (between Kernel Threads), then you can use this approach.

Lock:

spin_lock(spinlock_t *lock)

This will take the lock if it is free, otherwise it’ll spin until that lock is free (Keep trying).

Try Lock:

spin_trylock(spinlock_t *lock)

Locks the spinlock if it is not already locked. If unable to obtain the lock it exits with an error and do not spin. It returns non-zero if obtains the lock otherwise returns zero.

Unlock:

spin_unlock(spinlock_t *lock)

It does the reverse of lock. It will unlock which is locked by above call.

Checking Lock:

spin_is_locked(spinlock_t *lock)

This is used to check whether the lock is available or not. It returns non-zero if the lock is currently acquired. otherwise returns zero.

Example

Approach 2 (Locking between Bottom Halves)

If you want to share data between two different Bottom halves or same bottom halves, then you can use the Approach 1.

Approach 3 (Locking between User context and Bottom Halves)

If you share data with a bottom half and user context (like Kernel Thread), then this approach will be useful.

Lock:

spin_lock_bh(spinlock_t *lock)

It disables soft interrupts on that CPU, then grabs the lock. This has the effect of preventing softirqs, tasklets, and bottom halves from running on the local CPU. Here the suffix ‘_bh‘ refers to “Bottom Halves“.

Unlock:

spin_unlock_bh(spinlock_t *lock)

It will release the lock and re-enables the soft interrupts which is disabled by above call.

Example

Approach 4 (Locking between Hard IRQ and Bottom Halves)

If you share data between Hardware ISR and Bottom halves then you have to disable the IRQ before lock. Because, the bottom halves processing can be interrupted by a hardware interrupt. So this will be used in that scenario.

Lock:

spin_lock_irq(spinlock_t *lock)

This will disable interrupts on that cpu, then grab the lock.

Unlock:

spin_unlock_irq(spinlock_t *lock)

It will release the lock and re-enables the interrupts which is disabled by above call.

Example

Approach 5 (Alternative way of Approach 4)

If you want to use different variant rather than using spin_lock_irq() and spin_unlock_irq() then you can use this approach.

Lock:

spin_lock_irqsave( spinlock_t *lock, unsigned long flags );

This will save whether interrupts were on or off in a flags word and grab the lock.

Unlock:

spin_unlock_irqrestore( spinlock_t *lock, unsigned long flags );

This will releases the spinlock and restores the interrupts using the flags argument.

Approach 6 (Locking between Hard IRQs)

If you want to share data between two different IRQs, then you should use Approach 5.

Example Programming

This code snippet explains how to create two threads that accesses a global variable (etx_gloabl_variable). So before accessing the variable, it should lock the spinlock. After that it will release the spinlock. This example is using Approach 1.

Driver Source Code

MakeFile

In our next part of the tutorial, we will see the another spinlock (Reader/writer spinlocks).

Download our new Android app. You can learn all Embedded Tutorials from your Android Phone easily.

Click Here to Download App!