信号量与自旋锁(5): 自旋锁

时间:2021-01-19 15:12:48
信号量对互斥来讲是非常有用的, 但它并不是内核提供的唯一工具.
大多数锁定通过称为"自旋锁"的机制实现. 和信号量不同, 自旋锁可以在不能休眠的代码中使用, 比如: 中断处理例程. 在正确使用的情况下, 自旋锁通常可以提供比信号量更高的性能, 但它也有一组不同的使用限制.

1. 概念:
自旋锁在概念上非常简单, 一个自旋锁就是一个互斥设备, 它只有两个值: "锁定"和"解锁". 它通常实现为某个整数值中的单个位.
  • 希望获得某特定锁的代码, 测试相关的位. (原子操作)
  • 如果锁可用, 则"锁定"位被设置, 而代码继续进入临界区.
  • 相反, 如果锁被其他人获得, 则代码进入忙循环并重复检查这个锁, 直到该锁可用为止.
这个循环就是自旋锁的"自旋"部分. 当存在自旋锁时, 等待执行忙循环的处理器做不了任何有用的工作.

2. 自旋锁基本API:
  • 头文件: <linux/spinlock.h>
  • 数据类型: spinlock_t
  • 初始化, 以下两种方法:
    • 静态: spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
    • 动态: void spin_lock_init(spinlock_t *lock);
  • 获得锁: void spin_lock(spinlock_t *lock);
  • 释放锁: void spin_unlock(spinlock_t *lock);

3. 自旋锁其他API:
锁定一个自旋锁的函数实际有4个:
/* 基本函数 */
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);


对应的, 也有4个unlock函数:
void spin_unlock(spinlock_t *lock);
/* 这里面的flags必须和传递给spinlock_lock_irqsave的是同一个变量,
 * 还必须在同一函数中调用, 否则代码可能在某些架构上出现问题 */
void spin_unlock_irqstore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);


同时, 还有2个非阻塞的自旋锁操作:
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);

对禁止中断的情况, 没有对应的"try"版本.

4. 后续:
内核还提供了读取者/写入者形式的自旋锁, 这种锁与早先介绍的读取者/写入者信号量很相似. 因为不再赘述. 同时还有锁陷阱, 这些都会在以后的研究文章中出现.