Linux Synchronization
原子操作
内存屏障
顺序锁
自旋锁
自旋锁:如果内核配置为 SMP 系统,自旋锁就按 SMP 系统上的要求来实现真正的自旋等待,但是对于 UP 系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”。如果配置了 CONFIG_DEBUG_SPINLOCK,那么自旋锁按照 SMP 系统来编译。
但是为什么在 UP 系统中不需要真正的“带有自旋的”自旋锁呢?其实在理解了自旋锁的概念和由来,这个问题就迎刃而解了。所以我重新查找了关于自旋锁的资料,认真研究了自旋锁的实现和相关内容。
- 一、自旋锁 spinlock 的由来
众所周知,自旋锁最初就是为了 SMP 系统设计的,实现在多处理器情况下保护临界区。所以在 SMP 系统中,自旋锁的实现是完整的本来面目。但是对于 UP 系统,自旋锁可以说是 SMP 版本的阉割版。因为只有在 SMP 系统中的自旋锁才需要真正“自旋”。
- 二、自旋锁的目的
自旋锁的实现是为了保护一段短小的临界区操作代码,保证这个临界区的操作是原子的,从而避免并发的竞争冒险。在 Linux 内核中,自旋锁通常用于包含内核数据结构的操作,你可以看到在许多内核数据结构中都嵌入有 spinlock,这些大部分就是用于保证它自身被操作的原子性,在操作这样的结构体时都经历这样的过程:上锁-操作-解锁。
如果内核控制路径发现自旋锁“开着”(可以获取),就获取锁并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个 CPU 上的内核控制路径“锁着”,就在原地“旋转”,反复执行一条紧凑的循环检测指令,直到锁被释放。 自旋锁是循环检测“忙等”,即等待时内核无事可做(除了浪费时间),进程在 CPU 上保持运行,所以它保护的临界区必须小,且操作过程必须短。不过,自旋锁通常非常方便,因为很多内核资源只锁 1 毫秒的时间片段,所以等待自旋锁的释放不会消耗太多 CPU 的时间。
| 需求 | 建议加锁方法 |
|---|---|
| 低开销 | 优先使用自旋锁 |
| 短期锁定 | 优先使用自旋锁 |
| 长期锁定 | 优先使用互斥锁 |
| 中断上下文锁定 | 优先使用自旋锁 |
| 持有锁需要睡眠 | 优先使用互斥体 |
3、自旋锁与上下文
由于自旋锁不睡眠,既可以用于进程上下文,也可用于非进程上下文,这是它与信号量相比的一个优势。
正常来讲,如果自旋锁都是在进程上下文中使用,则建议使用 spin_lock/spin_unlock。
若在中断处理例程中也要使用自旋锁,则所有争用同一个锁的地方应使用 spin_lock_irqsave/spin_unlock_irqrestore。
若没有在中断而在下半部使用自旋锁,则所有急用同一个锁的地方可以使用 spin_lock_bh/spin_unlock_bh,这对函数平时用得比较少。
注:自旋锁用在中断例程中,若是进程上下文中的内核代码使用 spin_lock,则在临界区,可能发生中断 ,要么原子性被破坏(UP),要么造成死锁(SMP).
4、使用 spin_lock()后为什么不能睡眠?
在 UP 下,正如前面所说,原子性得不到保证。
而在 SMP 下,则可能发生死锁:
假设有 3 个进程 A,B,C,2 个 CPU 称 CPU0,CPU1.
CPU0 上 A 进程获取自旋锁进入睡眠,调度了 B 进程,B 进程将自旋;
CPU1 上调度了 C 进程也将自旋。
B 等着 A 释放锁,A 等着 B 释放 CPU,自旋后的 CPU 不能发生调度,CPU0 和 CPU1 将一直自旋下去,形成了死锁。
信号量
那么究竟什么是 信号量 ?就像你可以猜到那样 - 信号量 是另外一种支持线程或者进程的同步机制。Linux 内核已经提供了一种同步机制 - 自旋锁, 为什么我们还需要另外一种呢?为了回答这个问题,我们需要理解这两种机制。我们已经熟悉了 自旋锁 ,因此我们从 信号量 机制开始。
自旋锁 的设计理念是它仅会被持有非常短的时间。 但持有自旋锁的时候我们不可以进入睡眠模式因为其他的进程在等待我们。为了防止 死锁 上下文交换 也是不允许的。
当需要长时间持有一个锁的时候 信号量 就是一个很好的解决方案。从另一个方面看,这个机制对于需要短期持有锁的应用并不是最优。为了理解这个问题,我们需要知道什么是 信号量。
就像一般的同步原语,信号量 是基于变量的。这个变量可以变大或者减少,并且这个变量的状态代表了获取锁的能力。注意这个变量的值并不限于 0 和 1。有两种类型的 信号量:
- 二值信号量;
- 普通信号量.
第一种 信号量 的值可以为 1 或者 0。第二种 信号量 的值可以为任何非负数。如果 信号量 的值大于 1 那么它被叫做 计数信号量,并且它允许多于 1 个进程获取它。这种机制允许我们记录现有的资源,而 自旋锁 只允许我们为一个任务上锁。除了所有这些之外,另外一个重要的点是 信号量 允许进入睡眠状态。 另外当某进程在等待一个被其他进程获取的锁时, 调度器 也许会切换别的进程。
本地中断的禁止
本地软中断的禁止
RCU
Reference
-
No backlinks found.