简介

内核中的锁大致分为两类.

  • 自旋锁(spinning lock)
  • 睡眠锁(sleeping lock)

    自旋锁

    简单的理解就是,一直运行直到获得锁.有以下两种自旋锁.

  • raw_spinlock_t

    • 原始自旋锁,
  • bit spinlocks
    • 位自旋锁
    • 似乎使用的很少

      睡眠锁

      简单理解就是得不到锁,就会进入睡眠.
mutex mutual exclusion,彼此排斥,即互斥锁
rt_mutex
semaphore 信号量、旗语
rw_semaphore 读写信号量,读写互斥,但是可以多人同时读
ww_mutex
percpu_rw_semaphore 对rw_semaphore的改进,性能更优

锁的函数讲解

自旋锁

  1. # 加锁
  2. void spin_lock(spinlock_t *lock);
  3. void spin_lock_bh(spinlock_t *lock); # 加锁时禁止下半部(软中断),解锁时使能下半部(软中断)
  4. void spin_lock_irq(spinlock_t *lock); # 加锁时禁止中断,解锁时使能中断
  5. spin_lock_irqsave(lock, flags); # 加锁时禁止并中断并记录状态,解锁时恢复中断为所记录的状态
  6. # 解锁
  7. void spin_unlock(spinlock_t *lock);
  8. void spin_unlock_bh(spinlock_t *lock);
  9. void spin_unlock_irq(spinlock_t *lock);
  10. void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
  11. # 初始化操作
  12. spin_lock_init(_lock)
  13. # 尝试获取锁
  14. int spin_trylock(spinlock_t *lock)
  15. # 获取锁的状态
  16. int spin_is_locked(spinlock_t *lock)

信号量

函数名 作用
DEFINE_SEMAPHORE(name) 定义一个struct semaphore name结构体,
count值设置为1
void sema_init(struct semaphore *sem, int val) 初始化semaphore
void down(struct semaphore *sem) 获得信号量,如果暂时无法获得就会休眠
返回之后就表示肯定获得了信号量
在休眠过程中无法被唤醒,
即使有信号发给这个进程也不处理
int down_interruptible(struct semaphore *sem) 获得信号量,如果暂时无法获得就会休眠,
休眠过程有可能收到信号而被唤醒,
要判断返回值:
0:获得了信号量
-EINTR:被信号打断
int down_killable(struct semaphore *sem) 跟down_interruptible类似,
down_interruptible可以被任意信号唤醒,
但down_killable只能被“fatal signal”唤醒,
返回值:
0:获得了信号量
-EINTR:被信号打断
int down_trylock(struct semaphore *sem) 尝试获得信号量,不会休眠,
返回值:
0:获得了信号量
1:没能获得信号量
int down_timeout(struct semaphore *sem, long jiffies) 获得信号量,如果不成功,休眠一段时间
返回值:
0:获得了信号量
-ETIME:这段时间内没能获取信号量,超时返回
down_timeout休眠过程中,它不会被信号唤醒
void up(struct semaphore *sem) 释放信号量,唤醒其他等待信号量的进程

互斥量mutex

函数名 作用
mutex_init(mutex) 初始化一个struct mutex指针
DEFINE_MUTEX(mutexname) 初始化struct mutex mutexname
int mutex_is_locked(struct mutex *lock) 判断mutex的状态
1:被锁了(locked)
0:没有被锁
void mutex_lock(struct mutex *lock) 获得mutex,如果暂时无法获得,休眠
返回之时必定是已经获得了mutex
int mutex_lock_interruptible(struct mutex *lock) 获得mutex,如果暂时无法获得,休眠;
休眠过程中可以被信号唤醒,
返回值:
0:成功获得了mutex
-EINTR:被信号唤醒了
int mutex_lock_killable(struct mutex *lock) 跟mutex_lock_interruptible类似,
mutex_lock_interruptible可以被任意信号唤醒,
但mutex_lock_killable只能被“fatal signal”唤醒,
返回值:
0:获得了mutex
-EINTR:被信号打断
int mutex_trylock(struct mutex *lock) 尝试获取mutex,如果无法获得,不会休眠,
返回值:
1:获得了mutex,
0:没有获得
注意,这个返回值含义跟一般的mutex函数相反,
void mutex_unlock(struct mutex *lock) 释放mutex,会唤醒其他等待同一个mutex的线程
int atomic_dec_and_mutex_lock(atomic_t cnt, struct mutex lock) 让原子变量的值减1,
如果减1后等于0,则获取mutex,
返回值:
1:原子变量等于0并且获得了mutex
0:原子变量减1后并不等于0,没有获得mutex

互斥量和信号量之间的区别

semaphore中可以指定count为任意值,比如有10个厕所,所以10个人都可以使用厕所。而mutex的值只能设置为1或0,只有一个厕所。
换句话说,信号量可以表示有多少资源.

semaphore mutex
几把锁 任意,可设置 1
谁能解锁 别的程序、中断等都可以 谁加锁,就得由谁解锁
多次解锁 可以 不可以,因为只有1把锁
循环加锁 可以 不可以,因为只有1把锁
任务在持有锁的期间可否退出 可以 不建议,容易导致死锁
硬件中断、软件中断上下文中使用 可以 不可以

何时使用何种锁

参考 Unreliable Guide To Locking 内核锁指南
image.png
举例简单介绍一下,上表中第一行“IRQ Handler A”和第一列“Softirq A”的交叉点是“spin_lock_irq()”,意思就是说如果“IRQ Handler A”和“Softirq A”要竞争临界资源,那么需要使用“spin_lock_irq()”函数。为什么不能用spin_lock而要用spin_lock_irq?也就是为什么要把中断给关掉?假设在Softirq A中获得了临界资源,这时发生了IRQ A中断,IRQ Handler A去尝试获得自旋锁,这就会导致死锁:所以需要关中断。

按照上面的表格小结一下。
Softirq 之间的竞争使用 spin_lock。
Softirq 和 IRQ Handler 之间的竞争使用 spin_lock_irq。
IRQ Handler 之间的竞争使用 spin_lock_irqsave。
IRQ Handler 和 Tasklet 之间的竞争使用 spin_lock_irq。
最复杂的是, 内核普通函数和中断下半段处理函数之间的竞争。

参考资料

内核同步