天天看点

并发与竞态——自旋锁

Release log:

2021-05-07 五: 完成初版

2021-05-10 一: 添加自旋锁和信号量的对比

原文地址

Linux 内核中自旋锁的使用

在 Linux 内核中,要使用自旋锁需要包含的文件

<linux/spinlock.h>

,锁的数据类型为

spinlock_t

自旋锁的声明以及初始化

void spin_lock_init(spinlock_t *lock);
           

自旋锁的获取以及释放

void spin_lock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
           

注意: 所有自旋锁等待在本质上是不可中断的

使用自旋锁的核心规则

  1. 任何拥有自旋锁的代码都必须是原子的。它不能休眠,事实上,它不能因为任何原因放弃处理器,除了服务中断以外(某些情况下此时也不能放弃处理器)
  • 内核抢占的情况由自旋锁代码本身处理。任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止
  • 在拥有锁的时候避免休眠有时候很难做到,当我们编写需要自旋锁下执行的代码时,必须注意每一个所调用的函数
  • 拥有锁的时间越短越好

自旋锁函数

锁定一个自旋锁的函数

void spin_lock(spinlock_t *lock);
/* 这是一个宏,获得自旋锁之前禁止中断(只在本地处理器上),而先前的中断状态保存在 flags 中 */
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
/* 在获得锁之前禁止软件中断,但是会让硬件中断保持打开 */
void spin_lock_bh(spinlock_t *lock);
           

如果我们有一个自旋锁,它可以被运行在(硬件或软件)中断上下文中的代码获得,则必须使用某个禁止中断的 spin_lock 形式

释放自旋锁的方法需要严格对应

void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
           

另外,还有如下非阻塞的自旋锁操作

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
           

读取者/写入者自旋锁

读取者/写入者锁的类型为 rwlock_t,在

<linux/spinlock.h>

中定义

自旋锁的初始化

void rwlock_init(rwlock_t *lock);
           

读取者对应的函数(这里没有 read_trylock 可用)

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);

void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
           

写入者对应的函数

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
void write_trylock(rwlock_t *lock);

void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
           

和 rwsem 类似,读取者/写入者锁可能造成读取者饥饿

对比信号量与自旋锁

自旋锁可以在不能休眠的代码中使用,比如中断处理例程。自旋锁如果被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止。信号量是一种睡眠锁,如果一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。信号量不能在中断上下文中被调度

自旋锁会关闭抢占,而信号量不会

在正确使用的情况下,自旋锁通常可以提供比信号量更高的性能

需求                        建议的加锁方式
低开销加锁                  优先使用自旋锁
短期锁定                    优先使用自旋锁
中断上下文中加锁            使用自旋锁

长期加锁                    优先使用信号量
持有锁是需要睡眠、调度      使用信号量
           

继续阅读