自旋锁spinlock的实现-05
自旋锁,顾名思义:自己在原地打转,等待资源可用,一旦可用就上锁霸占它。
问题来了,假设别人已经上锁了,你原地打转会占住CPU资源了,别的程序怎么运行?它没有CPU怎么解锁?
这个问题,有2个答案:
① 原地打转的是CPU x,以后CPU y会解锁:这涉及多个CPU,适用于SMP系统;
② 对于单CPU系统,自旋锁的“自旋”功能就去掉了:只剩下禁止抢占、禁止中断
我先禁止别的线程来打断我(preempt_disable),我慢慢享用临界资源,用完再使能系统抢占(preempt_enable),这样别人就可以来抢资源了。
注意:SMP就是Symmetric Multi-Processors,对称多处理器;UP即Uni-Processor,系统只有一个单核CPU。
要理解spinlock,要通过2个情景来分析:
① 一开始,怎么争抢资源?不能2个程序都抢到。
这挺好解决,使用原子变量就可以实现。
② 某个程序已经获得资源,怎么防止别人来同时使用这个资源。
这是使用spinlock时要注意的地方,对应会有不同的衍生函数(_bh/_irq/_irqsave/_restore)。
1 自旋锁的内核结构体
spinlock对应的结构体如下定义,不同的架构可能有不同的实现:
上述__raw_tickets结构体中有owner、next两个成员,这是在SMP系统中实现spinlock的关键。
2 spinlock在UP系统中的实现
对于“自旋锁”,它的本意是:如果还没获得锁,我就原地打转等待。等待谁释放锁?
① 其他CPU
② 其他进程/线程
对于单CPU系统,没有“其他CPU”;如果内核不支持preempt,当前在内核态执行的线程也不可能被其他线程抢占,也就“没有其他进程/线程”。所以,对于不支持preempt的单CPU系统,spin_lock是空函数,不需要做其他事情。
如果单CPU系统的内核支持preempt,即当前线程正在执行内核态函数时,它是有可能被别的线程抢占的。这时spin_lock的实现就是调用“preempt_disable()”:你想抢我,我干脆禁止你运行。
在UP系统中,spin_lock函数定义如下:
从以上代码可知,在UP系统中spin_lock()就退化为preempt_disable(),如果用的内核不支持preempt,那么spin_lock()什么事都不用做。
对于spin_lock_irq(),在UP系统中就退化为local_irq_disable()和preempt_disable(),如下图所示:
假设程序A要访问临界资源,可能会有中断也来访问临界资源,可能会有程序B也来访问临界资源,那么使用spin_lock_irq()来保护临界资源:先禁止中断防止中断来抢,再禁止preempt防止其他进程来抢。
对于spin_lock_bh(),在UP系统中就退化为禁止软件中断和preempt_disable(),如下图所示:
对于spin_lock_irqsave,它跟spin_lock_irq类似,只不过它是先保存中断状态再禁止中断,如下:
对应的spin_unlock函数就不再讲解。
3 spinlock在SMP系统中的实现
要让多CPU中只能有一个获得临界资源,使用原子变量就可以实现。但是还要保证公平,先到先得。比如有CPU0、CPU1、CPU2都调用spin_lock想获得临界资源,谁先申请谁先获得。
要想理解SMP系统中spinlock的实现,得举一个例子。感谢这篇文章:
Linux内核同步机制之(四):spin lock
http://www.wowotech.net/kernel_synchronization/spinlock.html
wowotech真是一个神奇的网站,里面Linux文章的作者统一标为“linuxer”,牛!
我借用这篇文章的例子讲解,餐厅里只有一个座位,去吃饭的人都得先取号、等叫号。注意,有2个动作:顾客从取号机取号,电子叫号牌叫号。
① 一开始取号机待取号码为0
② 顾客A从取号机得到号码0,电子叫号牌显示0,顾客A上座;
取号机显示下一个待取号码为1。
③ 顾客B从取号机得到号码1,电子叫号牌还显示为0,顾客B等待;
取号机显示下一个待取号码为2。
④ 顾客C从取号机得到号码2,电子叫号牌还显示为0,顾客C等待;
取号机显示下一个待取号码为3。
⑤ 顾客A吃完离座,电子叫号牌显示为1,顾客B的号码等于1,他上座;
⑥ 顾客B吃完离座,电子叫号牌显示为2,顾客C的号码等于2,他上座;
在这个例子中有2个号码:取号机显示的“下一个号码”,顾客取号后它会自动加1;电子叫号牌显示“当前号码”,顾客离座后它会自动加1。某个客户手上拿到的号码等于电子叫号牌的号码时,该客户上座。
在这个过程中,即使顾客B、C同时到店,只要保证他们从取号机上得到的号码不同,他们就不会打架。
所以,关键点在于:取号机的号码发放,必须互斥,保证客户的号码互不相同。而电子叫号牌上号码的变动不需要保护,只有顾客离开后它才会变化,没人争抢它。
在ARMv6及以上的ARM架构中,支持SMP系统。它的spinlock结构体定义如下:
owner就相当于电子叫号牌,现在谁在吃饭。next就当于于取号机,下一个号码是什么。每一个CPU从取号机上取到的号码保存在spin_lock函数中的局部变量里。
spin_lock函数调用关系如下,核心是arch_spin_lock:
arch_spin_lock代码如下:
图中的注释把原理讲得非常清楚了,即使不同的个体去同时取号,也可以保证取到的号码各不相同。
假设第1个程序取到了号码,它访问了临界资源后,调用spin_unlock,代码如下:
假如有其他程序正在spin_lock函数中循环等待,它就会立刻判断自己手上的next是否等于lock->tickets.owner,如果相等就表示输到它获得了锁。
深入分析_linux_spinlock_实现机制
https://blog.csdn.net/electrombile/article/details/51289813
深入分析Linux自旋锁
http://blog.chinaunix.net/uid-20543672-id-3252604.html
Linux内核同步机制之(四):spin lock
http://www.wowotech.net/kernel_synchronization/spinlock.html