购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.4 读写锁与顺序锁

读写锁(rwlock)在本质上是自旋锁的一种,它在语义层面给出的定义是允许多个读操作同时进行,写操作将跟其他的操作串行。在实现上,该定义在自旋锁概念上增加了一个类似信号量的读个数计数器。读操作的时候首先获得自旋锁,然后增加引用计数,最后释放自旋锁。写操作需要同时满足的条件是:引用计数为0和获得自旋锁,并且写操作在获得rwlock后就不会释放自旋锁。这样就可以做到在某个写操作进行的时候读操作和其他的写操作无法进入。

读写锁一般用于读操作显著多于写操作的时候,但是这个锁从语义层面有一个很大的缺陷,就是写操作必须要等读操作全部结束才能获得锁,而读操作比较频繁,会一边结束旧的读操作,一边有新的读操作加入,这就非常容易导致写操作饥饿。由于在自旋等待中,写操作的饥饿会带来严重的卡顿问题,因此不建议在内核中使用当前实现方式下的这种锁。在允许睡眠的情况下可以使用rwsem读写锁,但是对于不允许睡眠的高响应要求场景,可以使用顺序锁。

顺序锁(seqlock)能够改善rwlock在内核中的写饥饿缺陷,seqlock与rwlock一样,都是自旋锁的一个增强,seqlock的定义如下。

img

seqlock_t相当于一个计数器和一个自旋锁的组合,顺序锁也是针对读操作多、写操作少的情况进行的优化。顺序锁相当于读操作完全没有加锁,而写操作是一个普通的自旋锁,但是在自旋锁的基础上增加了对seqcount计数器的修改,来表示当前写操作持锁的状态。在持有写锁之后,执行用户的真实写操作之前,对seqount进行自增;在释放写锁之前,完成用户写操作之后,也对seqcount进行一次自增。在读操作的一侧通过观察seqcount的值就能知道当前变量是否已经发生了写操作。seqcount的值从0开始,其在持有写锁的时候被增加一次,在释放写锁的时候被增加一次,所以seqcount值只要是偶数,就表示当前没有人在进行写操作,只要seqcount值在读操作前后不一样,就说明在读取的过程中发生了写竞态,需要重新读。而如果在进行读操作的时刻发现seqcount值是奇数,则表示当前有写操作正在发生,这时读侧就会先阻塞等待写操作的完成,然后继续进行读操作。否则即使读取了数据,在退出时看到的seqcount值依然是同样的奇数,也不能确定变量是否被修改。

seqlock与其他的锁有一个很大的不同点:seqlock的读锁部分是由两个API组成的,也就是需要用户参与来完成读锁操作,具体代码如下。

img

读操作的部分一般是一个do while循环,循环条件通过read_seqretry函数判断seqcount值有没有发生变化。循环体通过read_seqbegin函数先记录读取之前的seqcount值到seq,以备后面read_seqretry函数对seqcount值进行是否变化的判断。

顺序锁的读锁部分的函数定义如下。

img
img

cpu_relax的定义在arch/x86/include/asm/processor.h中,提供的是pause指令的语义,具体代码如下。

img

在x86下cpu_relax的实现最终只是一个rep;nop指令,并且使用“memory”禁止了GCC内存访问优化。__read_seqcount_begin就相当于一个简单的CAS自旋锁,直到seqcount值的最后一位为0,也就是偶数的时候才会返回。

顺序锁的获得写锁函数定义如下。

img
img

顺序锁的写操作也比较简单,自旋锁负责上锁和释放锁,顺序锁的其他部分增加1的计数器即可,增加的后面有一个smp_wmp()函数来保证写内存栅语义。 MOrcY7ktU9JL7oCTrijqSGbOBwtuvmGqYMEASc13AbqdjIXY/GaFOPb+KsDgnPoe

点击中间区域
呼出菜单
上一章
目录
下一章
×