自旋锁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结构体中有ownernext两个成员,这是在SMP系统中实现spinlock的关键。

 

2 spinlockUP系统中的实现

对于“自旋锁”,它的本意是:如果还没获得锁,我就原地打转等待。等待谁释放锁?

① 其他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 spinlockSMP系统中的实现

要让多CPU中只能有一个获得临界资源,使用原子变量就可以实现。但是还要保证公平,先到先得。比如有CPU0CPU1CPU2都调用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

posted on 2024-05-05 15:54  拉风摊主  阅读(10)  评论(0编辑  收藏  举报

导航