当前位置:首页>Linux>Linux 内核自旋锁有哪些?

Linux 内核自旋锁有哪些?

  • 2026-10-21 13:29:23
Linux 内核自旋锁有哪些?

大家好,我是王鸽, 这篇文章主要介绍一下内核中的自旋锁,之前有篇文章介绍内核锁的种类。linux内核态锁有哪些?

在Linux内核中,自旋锁(spinlock)是一种常用的同步机制,主要用于多处理器系统中保护短临界区。自旋锁的特点是当锁被占用时,尝试获取锁的线程会一直循环(自旋)等待,直到锁可用。因此,自旋锁适用于锁持有时间非常短的场景,以避免线程切换的开销。

Linux内核中自旋锁的种类和用法包括:

原始自旋锁(raw_spinlock_t):

这是最基本的自旋锁类型,提供最原始的自旋锁操作,不会进行任何额外的检查或调试。

在单核处理器上,原始自旋锁在编译时可能会被替换为禁止内核抢占的机制,因为在单核上自旋没有意义(如果持有锁的线程正在运行,那么自旋的线程永远无法获得锁)。

使用原始自旋锁时,必须确保锁持有期间禁止内核抢占,并且不会睡眠。

raw_spinlock_t lock;  raw_spin_lock(&lock);  raw_spin_unlock(&lock);

特点:最基础的自旋锁,不会禁用内核抢占

使用场景:内核中底层同步,特别是中断上下文

普通自旋锁(spinlock_t):

这是对原始自旋锁的封装,提供了更多的调试和检测功能,例如锁的初始化检查、锁的持有者记录等。

在非抢占式内核中,普通自旋锁和原始自旋锁可能没有区别,但在抢占式内核中,普通自旋锁会处理内核抢占的相关问题。

spinlock_t lock;  spin_lock_init(&lock);  spin_lock(&lock);spin_unlock(&lock);

特点:在非抢占式内核中与 raw_spinlock_t 相同,在抢占式内核中会禁用内核抢占

使用场景:大多数内核代码的同步需求

读写自旋锁(rwlock_t):

读写自旋锁允许多个读者同时持有锁,但写者必须独占锁。

读者和写者之间是互斥的,写者与写者之间也是互斥的。

读写自旋锁适用于读多写少的场景,可以提高并发性。

rwlock_t lock;

read_lock(&lock);     // 读锁

read_unlock(&lock);

write_lock(&lock);    // 写锁

write_unlock(&lock);

特点:允许多个读者同时访问,但写者需要独占访问

使用场景:读多写少的共享数据结构

顺序锁(seqlock_t):

顺序锁是一种优化读写锁,它允许读者在写者正在写入时也能读取,但读者需要检查读取过程中是否发生了写操作,如果发生了则重试。

顺序锁适用于读远多于写,且写操作非常短的场景。

seqlock_t lock;

write_seqlock(&lock);

write_sequnlock(&lock);

unsigned seq;

do {

seq = read_seqbegin(&lock);  // 读取数据

} while (read_seqretry(&lock, seq));

特点:写者优先,读者需要检查数据是否被修改

使用场景:读者多、写者少,且数据简单、读取快的情况

RCU(Read-Copy-Update):

虽然RCU不是严格的自旋锁,但它是一种同步机制,适用于读多写少的场景,并且读操作没有开销。

写者通过复制和更新来修改数据,然后等待所有读者离开临界区后,再释放旧的数据。

MCS锁(MCS lock):

MCS锁是一种基于队列的自旋锁,每个尝试获取锁的线程在本地变量上自旋,从而减少缓存行的竞争,提高可伸缩性。

Linux内核中的排队自旋锁(ticket spinlock)和MCS锁类似,但实现方式不同。

每个CPU在本地变量上自旋,减少缓存一致性流量

Linux 4.2+ 在某些架构上使用

排队自旋锁(ticket spinlock):

排队自旋锁通过两个计数器(一个是当前服务的票号,一个是下一个可用的票号)来实现锁的排队,确保先来先服务,避免饥饿。

Linux 2.6.25 后默认采用

解决传统自旋锁的公平性问题

基于票据(ticket)实现先进先出

自适应自旋锁(adaptive spinlock):

在某些系统中,自旋锁可能会自适应地选择自旋还是阻塞,例如在虚拟化环境中,如果锁被另一个CPU上的线程持有,则可能会选择阻塞以避免浪费CPU时间。

禁止中断的变体

需要注意的是,自旋锁在内核中通常与中断处理程序一起使用,因此还有禁止中断的变体,如spin_lock_irqsave()和spin_unlock_irqrestore()。

禁中断的自旋锁

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共享数据时

由于自旋锁会自旋等待,因此必须确保锁持有的时间非常短,否则会导致CPU时间的浪费和系统性能下降。在无法预知锁持有时间的情况下,可以考虑使用互斥锁(mutex)等其他同步机制。

使用选择指南

锁类型

适用场景

特点

spinlock_t

一般内核数据保护

简单高效

rwlock_t

读多写少的数据

提高读并发

seqlock_t

频繁读、偶尔写

无锁读取

禁中断变体

中断上下文共享

防止死锁

在Linux内核中,自旋锁的使用需要包含相应的头文件,例如:

原始自旋锁:#include

普通自旋锁:#include

读写自旋锁:#include

顺序锁:#include

自旋锁的初始化、加锁和解锁操作都有相应的函数,例如:

初始化:spin_lock_init()

加锁:spin_lock()

解锁:spin_unlock()

使用注意事项

持有时间短:自旋锁应保护很小的临界区

不能睡眠:持有自旋锁时不能调用可能睡眠的函数

中断安全:注意使用正确的锁变体防止死锁

初始化:动态初始化和静态初始化都要确保正确

初始化--静态和动态

// 静态初始化  DEFINE_SPINLOCK(my_lock);   // 动态初始化  spinlock_t my_lock;  spin_lock_init(&my_lock);

这些自旋锁机制为内核提供了高效的低级同步原语,是构建更高级同步机制的基础。

谢谢点赞关注!

最新文章

随机文章