linux 内核同步机制之自旋锁
1. 前言
在内核开发过程中,经常遇到这种情况:共享数据的临界区即位于进程上下文,也处于中断上下文。这时该如何保护呢?首先可以肯定的是涉及睡眠的锁不能使用了,因为中断上下文不能睡眠。
这时应该考虑使用spinlock自旋锁。
2. 自旋锁的特点
-
spin lock是一种死等的锁机制。当前的执行例程会不断的重新尝试直到获取锁进入临界区。
-
同一时间只允许一个例程进入临界区。
-
保护的临界区执行时间短。由于spin lock死等这种特性,要求保护的临界区需要尽量短,避免加大锁的情况出现。
-
可以在中断上下文执行。由于不睡眠,因此spin lock可以在中断上下文中适用
3 场景分析
3.1 spin lock保护的临界区处于的上下文
spin lock保护的资源可能被来自多个CPU核心上的进程上下文和中断上下文的中的访问,其中:
-
进程上下文包括
-
用户进程通过系统调用访问
-
内核线程直接访问
-
来自workqueue中work function的访问(本质上也是内核线程)
-
-
中断上下文包括
-
硬件中断处理程序,也就是中断上半部(中断handler)
-
中断的下半部,软中断上下文(softirq,当然由于各种原因,该softirq被推迟到softirqd的内核线程中执行的时候就不属于这个场景了,属于进程上下文那个分类了)
-
定时器的callback函数(本质上也是softirq)
-
tasklet(本质上也是softirq)。
-
3.2 共享资源临界区位于支持抢占的进程上下文中
共享资源R被进程A执行的系统调用访问,同时也被进程B通过系统调用访问。
3.2.1 单核cpu情况
假设进程A访问共享资源R的过程中发生了中断,在中断返回现场的时候,由于支持进程抢占,发生进程切换,进程优先级更高的B被调度执行,通过系统调用访问了R,如果没有锁保护,则会出现两个thread进入临界区,导致程序执行不正确。如果加入锁,假如A在进入临界区之前获取了spin lock,同样的,在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的,优先级更高的B,B在访问临界区之前仍然会试图获取spin lock,这时候由于A进程持有spin lock而导致B进程进入了永久的spin,也即发生了AA类型的死锁。怎么解决?既然死锁是由于抢占引起的,那就关闭抢占。也即在A进程获取spin lock的时候,禁止本CPU上的抢占。
3.2.2 多核cpu情况
如果A和B运行在不同的CPU上,那么情况会简单一些:A进程虽然持有spin lock而导致B进程进入spin状态,不过由于运行在不同的CPU上,A进程会持续执行并会很快释放spin lock,解除B进程的spin状态。上面AA类型的死锁仅仅在本CPU的进程抢占本CPU的当前进程这样的场景中发生。
3.2 共享资源临界区位于进程上下文和中断上下文的上半部
运行在CPU0上的进程A在某个系统调用过程中访问了共享资源R;运行在CPU1上的进程B在某个系统调用过程中也访问了共享资源R;外设P的中断handler中也会访问共享资源R;
再这样的场景下,假设CPU0上的进程A持有spin lock进入临界区,这时候,外设P发生了中断事件,并且调度到了CPU1上执行,看起来没有什么问题,执行在CPU1上的handler会稍微等待一会CPU0上的进程A,等它离开临界区就会释放spin lock的,但是,如果外设P的中断事件被调度到了CPU0上执行会怎么样?CPU0上的进程A在持有spin lock的状态下被中断上下文抢占,而抢占它的CPU0上的handler在进入临界区之前仍然会试图获取spin lock,悲剧发生了,CPU0上的P外设的中断handler永远的进入spin状态,这时候,CPU1上的进程B也不可避免在试图持有spin lock的时候失败而导致进入spin状态。为了解决这样的问题,linux kernel采用了这样的办法:如果涉及到中断上下文的访问,spin lock需要和禁止本CPU上的中断联合使用。
3.3 共享资源临界区位于进程上下文和中断上下文的下半部
运行在CPU0上的进程A在某个系统调用过程中访问了共享资源R;运行在CPU1上的进程B在某个系统调用过程中也访问了共享资源R;外设P的中断的下半部中也会访问共享资源R;
由于中断的下半部同样可以抢占进程上下文,所有也可能造成AA死锁的发生,解决的办法是spin lock需要和禁止本CPU上的下半部联合使用。
3.4 共享资源临界区位于中断上下文的上半部
同一种中断handler之间在单核和多核上都不会并行执行,这是linux kernel的特性。如果不同中断handler需要使用spin lock保护共享资源,所有handler都是关闭中断的,因此使用spin lock不需要关闭中断的配合。
3.5 共享资源临界区位于中断上下文的上半部
下半部又分成softirq和tasklet,同一种softirq会在不同的CPU上并发执行,因此如果某个驱动中的sofirq的handler中会访问某个全局变量,对该全局变量是需要使用spin lock保护的,不用配合disable CPU中断或者bottom half。tasklet更简单,因为同一种tasklet不会多个CPU上并发。
4. 总结
通过对spinlock的特性介绍以及各个应用场景的分析,可知需要特别注意会不会引起AA死锁,也即是cpu在第一个临界区执行期间被强占,同时该cpu又尝试获得锁进入另一个临界区。这种时候的解决办法是什么引起的抢占就把什么给禁止了。