目录
[自旋锁 API 函数](#自旋锁 API 函数)
Linux 内核提供的几种并发和竞争的处理方法中:

我们已经学习了:驱动开发篇14------原子操作。
本讲实验我们学习自旋锁。
自旋锁简介
当一个线程要访问某个共享资源的时候首先要先获取相应的锁, 锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。
自旋锁是一种 忙等待(Busy-Waiting)锁,当线程尝试获取已被占用的锁时,会持续循环检查("自旋")直到锁释放,而非进入睡眠状态。
特性 | 说明 |
---|---|
忙等待机制 | 获取锁失败时持续占用CPU循环检测(对比互斥锁的睡眠等待) |
短临界区优化 | 适用于极短耗时操作(通常<10μs) |
不可休眠 | 持有锁期间禁止休眠(否则可能导致死锁) |
SMP/UP通用 | 在单核(UP)和多核(SMP)系统均有优化实现 |
中断安全 | 提供spin_lock_irqsave 等变体支持中断上下文 |
把自旋锁比作一个变量 a,变量 a=1 的时候表示共享资源可用,当 a=0的时候表示共享资源不可用。现在线程 A 要访问共享资源,发现 a=0(自旋锁被其他线程持有),那么线程 A 就会不断的查询 a 的值,直到 a=1。
从这里我们可以看到自旋锁的一个缺点:那就等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长,也就是自旋锁适用于短时期的轻量级加锁。
定义自旋锁
Linux 内核使用结构体 spinlock_t 表示自旋锁,结构体定义如下所示:
cpp
/*
* 自旋锁核心数据结构定义
* 采用联合体(union)实现调试与生产环境的内存布局兼容
*/
typedef struct spinlock {
union {
/* 生产环境实际使用的自旋锁结构 */
struct raw_spinlock rlock; // 基础自旋锁实现
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/* 调试模式下扩展的锁验证结构 */
struct {
/* 填充字节保证与raw_spinlock内存对齐 */
u8 __padding[LOCK_PADSIZE];
/* 锁依赖跟踪映射(用于死锁检测) */
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
在使用自旋锁之前,需要先定义一个自旋锁变量。
自旋锁定义方法如下所示:
cpp
spinlock_t lock; //定义自旋锁
自旋锁 API 函数
线程之间自旋锁
linux下最基本的自旋锁 API 函数如表:

这些自旋锁API 函数,适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间。
被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。
API函数使用的示例代码如下:
cpp
#include <linux/module.h>
#include <linux/spinlock.h>
// 静态定义自旋锁(方法1)
static DEFINE_SPINLOCK(static_lock);
// 动态自旋锁(方法2)
static spinlock_t dynamic_lock;
static int __init spinlock_demo_init(void)
{
printk(KERN_INFO "Spinlock Demo Start\n");
// 初始化动态锁
spin_lock_init(&dynamic_lock);
// 1. 基本加锁/解锁
spin_lock(&static_lock);
printk("Critical section 1\n");
spin_unlock(&static_lock);
// 2. 尝试获取锁(非阻塞)
if (spin_trylock(&dynamic_lock)) {
printk("Got dynamic lock\n");
spin_unlock(&dynamic_lock);
} else {
printk("Failed to get dynamic lock\n");
}
// 3. 检查锁状态
printk("Static lock is %slocked\n",
spin_is_locked(&static_lock) ? "" : "not ");
return 0;
}
static void __exit spinlock_demo_exit(void)
{
printk(KERN_INFO "Spinlock Demo End\n");
}
module_init(spinlock_demo_init);
module_exit(spinlock_demo_exit);
MODULE_LICENSE("GPL");
中断与自旋锁
中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断,否则可能导致锁死现象的发生,如图:

线程 A 先运行,并且获取到了 lock 这个锁,当线程 A 运行 functionA 函数的时候中断发生了,中断抢走了 CPU 使用权。右边的中断服务函数也要获取 lock 这个锁,但是这个锁被线程 A 占有着,中断就会一直自旋,等待锁有效。这样就形成了死锁。
所以需要在获取锁之前关闭本地中断, Linux 内核提供了相应的 API 函数:

示例代码如下:
cpp
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
static DEFINE_SPINLOCK(irq_lock); // 定义自旋锁
static unsigned long irq_flags; // 保存中断状态
// 共享资源
static int critical_data = 0;
// 模拟中断处理函数
static irqreturn_t sample_irq_handler(int irq, void *dev_id)
{
spin_lock(&irq_lock); // 基本中断上下文锁定
critical_data++;
spin_unlock(&irq_lock);
return IRQ_HANDLED;
}
static int __init spinlock_irq_init(void)
{
printk(KERN_INFO "Spinlock IRQ Demo Start\n");
// 场景1:明确知道中断未禁用时
spin_lock_irq(&irq_lock); // 禁用中断+获取锁
critical_data = 100;
spin_unlock_irq(&irq_lock); // 恢复中断+释放锁
// 场景2:安全通用版本(推荐)
spin_lock_irqsave(&irq_lock, irq_flags); // 保存状态+禁用中断+锁
printk("Critical data: %d\n", critical_data);
spin_unlock_irqrestore(&irq_lock, irq_flags); // 恢复精确中断状态
// 注册模拟中断
request_irq(1, sample_irq_handler, 0, "sample_irq", NULL);
return 0;
}
static void __exit spinlock_irq_exit(void)
{
free_irq(1, NULL);
printk(KERN_INFO "Spinlock IRQ Demo End\n");
}
module_init(spinlock_irq_init);
module_exit(spinlock_irq_exit);
MODULE_LICENSE("GPL");
一般在线程中使用 spin_lock_irqsave/ spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock。
下半部与自旋锁
Linux 将中断处理分为两个部分:
- 上半部(Top Half):直接响应硬件中断的快速处理
- 下半部(Bottom Half):延迟执行的耗时操作

下半部也会竞争共享资源,如果要在下半部里面使用自旋锁,可以使用表中的API函数:

示例代码如下:
cpp
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
static DEFINE_SPINLOCK(bh_lock); // 定义自旋锁
static int shared_data = 0; // 共享数据
// 模拟的下半部处理函数
static void bottom_half_handler(unsigned long data)
{
spin_lock_bh(&bh_lock); // 关闭下半部并加锁
printk("BH processing: %d\n", shared_data);
shared_data++;
spin_unlock_bh(&bh_lock); // 释放锁并恢复下半部
}
// 声明tasklet(一种下半部实现)
static DECLARE_TASKLET(my_tasklet, bottom_half_handler, 0);
static int __init bh_lock_demo_init(void)
{
printk(KERN_INFO "Bottom Half Lock Demo Start\n");
spin_lock_bh(&bh_lock); // 关闭下半部并加锁
shared_data = 100; // 修改共享数据
spin_unlock_bh(&bh_lock); // 释放锁并恢复下半部
tasklet_schedule(&my_tasklet); // 触发下半部处理
return 0;
}
static void __exit bh_lock_demo_exit(void)
{
tasklet_kill(&my_tasklet); // 确保tasklet完成
printk(KERN_INFO "Final shared_data: %d\n", shared_data);
printk(KERN_INFO "Bottom Half Lock Demo End\n");
}
module_init(bh_lock_demo_init);
module_exit(bh_lock_demo_exit);
MODULE_LICENSE("GPL");
其他类型的锁
读写自旋锁
读写自旋锁是一种区分读写操作的同步机制,允许多个读者同时访问共享资源,但写者必须独占访问。

Linux 内核使用 rwlock_t 结构体表示读写锁,结构体定义如下:
cpp
typedef struct {
arch_rwlock_t raw_lock;
} rwlock_t;
读写锁操作 API 函数分为两部分,一个是给读使用的,一个是给写使用的。
示例代码如下:
cpp
#include <linux/module.h>
#include <linux/rwlock.h>
#include <linux/interrupt.h>
static DEFINE_RWLOCK(data_rwlock); // 定义并初始化读写锁
static int shared_data = 0; // 共享数据
static unsigned long irq_flags; // 保存中断状态
// 模拟中断处理函数
static irqreturn_t sample_irq_handler(int irq, void *dev_id)
{
// 中断上下文获取读锁(安全版本)
read_lock_irqsave(&data_rwlock, irq_flags);
printk("IRQ Read: %d\n", shared_data);
read_unlock_irqrestore(&data_rwlock, irq_flags);
return IRQ_HANDLED;
}
static int __init rwlock_demo_init(void)
{
printk(KERN_INFO "Read-Write Spinlock Demo Start\n");
// 1. 写者模式(完全独占)
write_lock_bh(&data_rwlock); // 禁用下半部+获取写锁
shared_data = 100; // 安全修改数据
write_unlock_bh(&data_rwlock); // 释放写锁+启用下半部
// 2. 读者模式(并发读取)
read_lock(&data_rwlock);
printk("Reader 1: %d\n", shared_data);
read_unlock(&data_rwlock);
// 3. 尝试获取写锁(非阻塞)
if (write_trylock(&data_rwlock)) {
shared_data += 20;
write_unlock(&data_rwlock);
}
// 注册模拟中断
request_irq(1, sample_irq_handler, 0, "sample_irq", NULL);
return 0;
}
static void __exit rwlock_demo_exit(void)
{
// 中断安全写操作
write_lock_irqsave(&data_rwlock, irq_flags);
shared_data = 0;
write_unlock_irqrestore(&data_rwlock, irq_flags);
free_irq(1, NULL);
printk(KERN_INFO "Read-Write Spinlock Demo End\n");
}
module_init(rwlock_demo_init);
module_exit(rwlock_demo_exit);
MODULE_LICENSE("GPL");
顺序锁
顺序锁是一种读写共存锁,通过版本号机制实现:
- 写操作:完全独占(类似普通自旋锁)
- 读操作:无锁访问,通过检测版本变化发现冲突

Linux 内核使用 seqlock_t 结构体表示顺序锁,结构体定义如下:
cpp
typedef struct {
unsigned sequence; // 版本计数器
spinlock_t lock; // 写锁
} seqlock_t;
关于顺序锁的 API 函数如表:

示例代码如下:
cpp
#include <linux/module.h>
#include <linux/seqlock.h>
#include <linux/interrupt.h>
static DEFINE_SEQLOCK(data_seqlock); // 定义并初始化顺序锁
static int shared_data = 0; // 共享数据
static unsigned long irq_flags; // 保存中断状态
// 模拟中断处理函数
static irqreturn_t sample_irq_handler(int irq, void *dev_id)
{
unsigned seq;
int val;
// 中断上下文读取(无锁)
do {
seq = read_seqbegin(&data_seqlock);
val = shared_data; // 安全读取
} while (read_seqretry(&data_seqlock, seq));
printk("IRQ Read: %d\n", val);
return IRQ_HANDLED;
}
static int __init seqlock_demo_init(void)
{
printk(KERN_INFO "Seqlock Demo Start\n");
// 1. 普通写操作
write_seqlock(&data_seqlock);
shared_data = 100;
write_sequnlock(&data_seqlock);
// 2. 中断安全写操作
write_seqlock_irqsave(&data_seqlock, irq_flags);
shared_data += 50;
write_sequnlock_irqrestore(&data_seqlock, irq_flags);
// 3. 下半部安全写操作
write_seqlock_bh(&data_seqlock);
shared_data *= 2;
write_sequnlock_bh(&data_seqlock);
// 4. 读操作(可与其他读者并发)
unsigned seq;
int val;
do {
seq = read_seqbegin(&data_seqlock);
val = shared_data;
} while (read_seqretry(&data_seqlock, seq));
printk("Final Read: %d\n", val);
// 注册模拟中断
request_irq(1, sample_irq_handler, 0, "sample_irq", NULL);
return 0;
}
static void __exit seqlock_demo_exit(void)
{
// 禁用中断+写锁
write_seqlock_irq(&data_seqlock);
shared_data = 0;
write_sequnlock_irq(&data_seqlock);
free_irq(1, NULL);
printk(KERN_INFO "Seqlock Demo End\n");
}
module_init(seqlock_demo_init);
module_exit(seqlock_demo_exit);
MODULE_LICENSE("GPL");
自旋锁使用注意事项
我们在使用自旋锁的时候要注意一下几点:
- 因为在等待自旋锁的时候处于"自旋"状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体。
- 自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。
- 不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须"自旋",等待锁被释放,然而你正处于"自旋"状态,根本没法释放锁。结果就是自己把自己锁死了!
- 在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管我们用的是单核的还是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。