Linux 锁机制(3)之自旋锁

1. 自旋锁

1.1 两种锁

内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:

  1. 一个是原地等待,如自旋锁
    特点:不会引起调用者睡眠,而是一直循环查看该锁是否释放
  2. 一个是挂起当前进程,调度其他进程执行,如互斥锁,信号量等
    特点:如果资源已经被占用,调用线程则进入睡眠等待

1.2 自旋锁

1.自旋锁简单说就是:
当一个线程获取了锁之后,其他试图获取这个锁的线程一直在循环等待获取这个锁,直至锁可用为止。

1.3 自旋名字来源:自旋锁一直循环等待,直到获取锁为止。

自旋锁一直循环等待,线程通过 busy-wait-loop 的方式来获取锁
即:当前的执行thread会不断的重新尝试直到获取锁进入临界区。

1.4 自旋锁优点:

  1. 自旋锁不会使线程状态发生切换,线程一直都是active的;
  2. 不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

2 自旋锁特点/使用:

2.1 临界区要小(否则出现死等,浪费cpu)

由于spin lock死等这种特性,因此它使用在那些代码不是非常复杂的临界区
如果临界区执行时间太长,那么不断在临界区门口“死等”的那些thread是多么的浪费CPU啊

(临界区代码短小,最好100行以内)
线程等待锁的时间很短,短到比线程两次上下文切换时间还少,说白了就是锁里操作的事情很简单。

2.2 线程获取自旋锁之前,要禁止当前处理器上的中断

防止获取锁的线程和中断形成竞争条件

例子说明:
比如:当前线程获取自旋锁后,在临界区中被中断处理程序打断,中断处理程序正好也要获取这个锁,
于是中断处理程序会等待当前线程释放锁,而当前线程也在等待中断执行完后再执行临界区和释放锁的代码。

2.3 自旋锁是不可递归的

递归的请求同一个自旋锁会自己锁死自己。

例子:
自旋锁可以在中断上下文中使用,但是试想一个场景:一个线程获取了一个锁,但是被中断处理程序打断,中断处理程序也获取了这个锁(但是之前已经被锁住了,无法获取到,只能自旋),中断无法退出,导致线程中后面释放锁的代码无法被执行,导致死锁。

2.4 由于不睡眠(忙等),自旋锁可以在中断上下文使用。

在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?
如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也参和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin lock。

在中断上下文,是不允许睡眠的。所以,这里需要的是一个不会导致睡眠的锁——spinlock。

参见:操作系统二:上下文,进程上下文和中断上下文
https://blog.csdn.net/lqy971966/article/details/119103989

3. spinlock_t 源码学习

3.1 spinlock 源码文件解析

根据CPU体系结构,spinlock分为SMP版本和UP版本

[root@localhost home]# uname -a 
Linux localhost.localdomain 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

[root@localhost home]#
root@localhost home]# find / -name *spinlock*

/* 针对 ARM 平台的 arch_spin_lock */
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/qspinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/qspinlock_paravirt.h
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/spinlock_types.h
/usr/src/kernels/3.10.0-862.el7.x86_64/drivers/hwspinlock
/usr/src/kernels/3.10.0-862.el7.x86_64/include/asm-generic/qspinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/asm-generic/qspinlock_types.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/asm-generic/spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/config/arch/use/queued/spinlocks.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/config/paravirt/spinlocks.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/config/queued/spinlocks.h

/* 以下是和体系结构无关的代码*/
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/bit_spinlock.h	
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/hwspinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/rwsem-spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock.h
	//spinlock.h 通用spin lock的接口函数声明,例如spin_lock、spin_unlock等
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_api_smp.h
	//SMP上的spin lock模块的接口声明
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_api_up.h
	//spinlock_api_up.h 这个头文件是non-debug版本的spin lock需要的
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_types.h	
	//spinlock_types.h 定义了通用spin lock的基本的数据结构(例如spinlock_t)
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_types_up.h
	//spinlock_types.h文件会根据系统的配置,如果是SMPUP则会include该头文件
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_up.h
	//spinlock_up.h 这个头文件是debug版本的SMP spin lock需要的
[root@localhost home]#

3.2 自旋锁API

方法				描述
spin_lock()			获取指定的自旋锁
spin_lock_irq()		禁止本地中断并获取指定的锁
spin_lock_irqsave()	保存本地中断的当前状态,禁止本地中断,并获取指定的锁
spin_unlock()		释放指定的锁
spin_unlock_irq()	释放指定的锁,并激活本地中断
spin_unlock_irqstore()	释放指定的锁,并让本地中断恢复到以前状态
spin_lock_init()	动态初始化指定的spinlock_t
spin_trylock()		试图获取指定的锁,如果未获取,则返回0
spin_is_locked()	如果指定的锁当前正在被获取,则返回非0,否则返回0

3.3 自旋锁变体 API

接口API的类型			spinlock中的定义	raw_spinlock的定义
定义spin lock并初始化	DEFINE_SPINLOCK		DEFINE_RAW_SPINLOCK
动态初始化spin 	lock	spin_lock_init		raw_spin_lock_init
获取指定的spin lock		spin_lock			raw_spin_lock
获取指定的spin lock		spin_lock_irq		raw_spin_lock_irq
	同时disable本CPU中断
	
保存本CPU当前的irq状态	spin_lock_irqsave	raw_spin_lock_irqsave
	disable本CPU中断并获取指定的spin lock
	
获取指定的spin lock		spin_lock_bh		raw_spin_lock_bh
	同时disable本CPU的bottom half
	
释放指定的spin lock		spin_unlock			raw_spin_unlock
释放指定的spin lock		spin_unlock_irq		raw_spin_unock_irq
	同时enable本CPU中断
	
释放指定的spin lock		spin_unlock_irqstore raw_spin_unlock_irqstore
	同时恢复本CPU的中断状态
	
获取指定的spin lock		spin_unlock_bh		raw_spin_unlock_bh
	同时enable本CPU的bottom half
	
尝试去获取spin lock,	spin_trylock		raw_spin_trylock
	如果失败,不会spin,而是返回非零值	
	
判断spin lock是否是locked,	spin_is_locked	raw_spin_is_locked
	如果其他的thread已经获取了该lock,那么返回非零值,否则返回0

3.4 自旋锁伪代码实现

spinlock_t lock;
spin_lock_init(lock);	//初始化
……
spin_lock(&lock);	//加锁
/*  临界区 */
spin_unlock(&lock);	//释放锁

4. 扩展:自旋锁在处理中断处理下半部时注意事项:

4.1 进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行

下半部处理和进程上下文共享数据时,由于下半部的处理可以抢占进程上下文的代码,
所以进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行。

4.2 下半部在对共享数据加锁前要禁止中断处理(上半部),解锁时再允许中断的执行

中断处理程序(上半部)和下半部处理共享数据时,由于中断处理(上半部)可以抢占下半部的执行,
所以下半部在对共享数据加锁前要禁止中断处理(上半部),解锁时再允许中断的执行。

4.3 同类tasklet中的共享数据不需要保护

同一种tasklet不能同时运行,所以同类tasklet中的共享数据不需要保护。

4.4 同一个处理器上不会有tasklet相互抢占的情况

不同类tasklet中共享数据时,其中一个tasklet获得锁后,不用禁止其他tasklet的执行,因为同一个处理器上不会有tasklet相互抢占的情况

4.5 同一个处理器上不会有软中断互相抢占的情况

同类型或者非同类型的软中断在共享数据时,也不用禁止下半部,因为同一个处理器上不会有软中断互相抢占的情况

参考:
https://blog.csdn.net/zhoutaopower/article/details/86598839
https://blog.csdn.net/zhoutaopower/article/details/86598839
https://www.cnblogs.com/sky-heaven/p/12802063.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值