第10章 内核同步方法
一、原子操作
原子操作可以操作指令以原子的方式执行,执行过程不会被打断。
1.1 原子整数操作
image.png针对整数的原子操作只跟对atomic_t类型的数据进行处理。原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的。在编写代码时,能使用原子操作时,就尽量不要使用复杂的加锁机制。原子操作给系统带来的开销小,对高速缓存行(cache-line)的影响也小。
1.2 64位原子操作
所有的64位体系结构都提供了atomic64_t类型,以及一组对应的算数操作方法:
image.png
1.3 原子位操作
位操作函数是对普通的内存地址进行操作的:
image.png
内核还提供了一组与上述操作对应的非原子位函数,不保证原子性,其函数名字前缀多了两个下划线,执行速度可能更快。
二、自旋锁
Linux内核最常见的锁是自旋锁(spin lock)。自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获得一个被持有的自旋锁,那么该线程会一直进程忙循环--旋转--等待锁重新可用。要是锁未被争用,请求锁的执行线程便立即得到它,继续执行。因此,自旋锁不应该被长时间持有,最好小于完成两次上下文切换的耗时。
image.png
Linux内核提供了专门的读-写自旋锁(共享/排斥锁、并发/排斥锁)。一个或多个读任务可以并发地持有读者锁;写锁最多只能被一个写任务持有,而且不能有并发的读操作。
image.png
三、信号量
Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器可以执行其他代码。当持有信号量被释放后,处于等待队列的任务将被唤醒,并获得该信号量。信号量比自旋锁提供了更好的处理器利用率,但造成了更大的开销。
信号量可以同时允许任意数量的锁持有者,根据该数量的大小可分为:
- 二值信号量(或互斥信号量,数量为1):
- 计数信号量(数量大于1)
信号量也可区分读写信号量。
四、互斥体
互斥体是指任何可以睡眠的强制互斥锁,比如使用计数为1的信号量。
image.png一般情况下,除非互斥体不够用,才使用信号量。而对于互斥体和自旋锁:
image.png
五、完成变量
如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,可以利用完成变量(completion variable)。如果一个任务要执行一些工作时,另一个任务就会在完成变量上等待。当这个任务完成工作后,会使用完成变量去唤醒在等待的任务。
image.png
六 BLK:大内核锁
BLK是一个全局自旋锁:
- 持有BLK的任务可以休眠
- BLK是一种递归锁
- BLK只可以用在进程上下文
- 新用户不允许使用BLK
六、顺序锁
顺序锁(seq锁),主要依靠一个序列计数器。当有疑义的数据被写入时,会得到一个锁,并且序列值会增加,在读取数据之前和之后,序列号都会被读取。如果序列号相同则说明读操作进行过程中没有被写操作打断。适用于:
- 数据存在很多读者
- 数据写者很少
- 写者虽少,但写优先于读,不允许读者让写者饥饿
- 被保护的数据结构简单
七、禁止抢占
内核抢占代码使用自旋锁作为非抢占区域的标记。如果一个自旋锁被持有,内核便不能进行抢占。
某些情况下不需要自旋锁,但仍需要关闭内核抢占:
image.png