大家好,我是王鸽, 这篇文章主要介绍一下内核中的自旋锁,之前有篇文章介绍内核锁的种类。linux内核态锁有哪些?
在Linux内核中,自旋锁(spinlock)是一种常用的同步机制,主要用于多处理器系统中保护短临界区。自旋锁的特点是当锁被占用时,尝试获取锁的线程会一直循环(自旋)等待,直到锁可用。因此,自旋锁适用于锁持有时间非常短的场景,以避免线程切换的开销。
这是最基本的自旋锁类型,提供最原始的自旋锁操作,不会进行任何额外的检查或调试。
在单核处理器上,原始自旋锁在编译时可能会被替换为禁止内核抢占的机制,因为在单核上自旋没有意义(如果持有锁的线程正在运行,那么自旋的线程永远无法获得锁)。
使用原始自旋锁时,必须确保锁持有期间禁止内核抢占,并且不会睡眠。
raw_spinlock_t lock;raw_spin_lock(&lock);raw_spin_unlock(&lock);
特点:最基础的自旋锁,不会禁用内核抢占
使用场景:内核中底层同步,特别是中断上下文
这是对原始自旋锁的封装,提供了更多的调试和检测功能,例如锁的初始化检查、锁的持有者记录等。
在非抢占式内核中,普通自旋锁和原始自旋锁可能没有区别,但在抢占式内核中,普通自旋锁会处理内核抢占的相关问题。
spinlock_t lock;spin_lock_init(&lock);spin_lock(&lock);spin_unlock(&lock);
特点:在非抢占式内核中与 raw_spinlock_t 相同,在抢占式内核中会禁用内核抢占
使用场景:大多数内核代码的同步需求
读写自旋锁允许多个读者同时持有锁,但写者必须独占锁。
读者和写者之间是互斥的,写者与写者之间也是互斥的。
读写自旋锁适用于读多写少的场景,可以提高并发性。
rwlock_t lock;
read_lock(&lock); // 读锁
read_unlock(&lock);
write_lock(&lock); // 写锁
write_unlock(&lock);
特点:允许多个读者同时访问,但写者需要独占访问
使用场景:读多写少的共享数据结构
顺序锁是一种优化读写锁,它允许读者在写者正在写入时也能读取,但读者需要检查读取过程中是否发生了写操作,如果发生了则重试。
顺序锁适用于读远多于写,且写操作非常短的场景。
seqlock_t lock;
write_seqlock(&lock);
write_sequnlock(&lock);
unsigned seq;
do {
seq = read_seqbegin(&lock); // 读取数据
} while (read_seqretry(&lock, seq));
特点:写者优先,读者需要检查数据是否被修改
使用场景:读者多、写者少,且数据简单、读取快的情况
虽然RCU不是严格的自旋锁,但它是一种同步机制,适用于读多写少的场景,并且读操作没有开销。
写者通过复制和更新来修改数据,然后等待所有读者离开临界区后,再释放旧的数据。
MCS锁是一种基于队列的自旋锁,每个尝试获取锁的线程在本地变量上自旋,从而减少缓存行的竞争,提高可伸缩性。
Linux内核中的排队自旋锁(ticket spinlock)和MCS锁类似,但实现方式不同。
每个CPU在本地变量上自旋,减少缓存一致性流量
Linux 4.2+ 在某些架构上使用
排队自旋锁通过两个计数器(一个是当前服务的票号,一个是下一个可用的票号)来实现锁的排队,确保先来先服务,避免饥饿。
Linux 2.6.25 后默认采用
解决传统自旋锁的公平性问题
基于票据(ticket)实现先进先出
在某些系统中,自旋锁可能会自适应地选择自旋还是阻塞,例如在虚拟化环境中,如果锁被另一个CPU上的线程持有,则可能会选择阻塞以避免浪费CPU时间。
禁止中断的变体
禁中断的自旋锁
unsigned long flags;spin_lock_irqsave(&lock, flags); // 保存中断状态并禁用中断spin_unlock_irqrestore(&lock, flags);spin_lock_irq(&lock); // 禁用中断(不保存状态)spin_unlock_irq(&lock);
使用场景:中断处理程序与进程上下文共享数据时
禁下半部的自旋锁
spin_lock_bh(&lock); // 禁用软中断/下半部spin_unlock_bh(&lock);
使用场景:与软中断/Tasklet共享数据时
锁类型 | 适用场景 | 特点 |
|---|---|---|
spinlock_t | 一般内核数据保护 | 简单高效 |
rwlock_t | 读多写少的数据 | 提高读并发 |
seqlock_t | 频繁读、偶尔写 | 无锁读取 |
禁中断变体 | 中断上下文共享 | 防止死锁 |
原始自旋锁:#include
普通自旋锁:#include
读写自旋锁:#include
顺序锁:#include
初始化:spin_lock_init()
加锁:spin_lock()
解锁:spin_unlock()
持有时间短:自旋锁应保护很小的临界区
不能睡眠:持有自旋锁时不能调用可能睡眠的函数
中断安全:注意使用正确的锁变体防止死锁
初始化:动态初始化和静态初始化都要确保正确
初始化--静态和动态
// 静态初始化DEFINE_SPINLOCK(my_lock);// 动态初始化spinlock_t my_lock;spin_lock_init(&my_lock);
这些自旋锁机制为内核提供了高效的低级同步原语,是构建更高级同步机制的基础。
谢谢点赞关注!
