前言
📫作者简介:小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫
🏆 InfoQ 签约博主、CSDN 专家博主/Java 领域优质创作者/CSDN 内容合伙人、阿里云专家/签约博主、华为云专家、51CTO 专家/TOP 红人 🏆
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
本文导读
前面的都是比较容易理解的普通互斥锁、自旋锁、信号量、读写锁,本问 seq 锁是在之前的锁实现上做了一些不一样的操作。
一、Linux 内核 seq 锁实现原理
这里的 seq 叫作顺序锁,为什么叫顺序锁?就是对一个 unsigned sequence 变量执行操作。它的特点在哪呢?读和写不会产生阻塞。我们知道在读写锁中,当读锁持有时,写者被阻塞;而这里持有读锁时还能写,也就是读写互不影响。它是如何做到的呢?来看源码实现。
二、Linux 内核 seq 锁源码解析
1、怎么用 seq 锁
do {
seq=read_seqbegin(&foo);
// do Something
}while(read_seqretry(&foo, seq));
复制代码
2、seq 锁源码解析
如果理解了自旋锁、信号量、读写锁后,理解 seq 锁就非常简单,其实就是让读者自己去做判断是否更改了数据,如果更改了那么自己重试去吧。
思考一下,写者之间互斥吗?
答案是互斥的,上述读锁、上锁,未看到自旋锁,所以写者互斥。
对于 seq 锁有什么场景限制吗?
答案是有场景限制。想象一下,如果 seq 锁被用于指针结构,那么会发生什么?A 读者读取了 M 指针并正在操作,然而 B 写者将 M 指针结束了。这对吗?所以 seq 锁不能用于指针类型
// seq 锁结构
typedef struct{
unsigned sequence; // 声明一个无符号整型大小的变量sequence
spinlock_t lock; //自旋锁用于保护上面的sequence,不妨大胆猜测写写互斥
}seqlock_t;
// 这两个宏相当简单,和之前的自旋锁一样,初始化一个 seq 锁结构
#define SEQLOCK_UNLOCKED {0, SPIN_LOCK_UNLOCKED }
#define seqlock_init(x)
do {
*(x) =(seqlock_t) SEQLOCK_UNLOCKED;
} while (0)
// 持有写锁
static inline void write_seqlock(seqlock_t*sl) {
// 上自旋锁,这在之前已讲过,这里就不多说了
spin lock(&sl->lock);
// 针对 sequence++,由于初始值为 0,不难看出,当 seq 为奇数时,为写者持有状态,为偶数时为无写状态
++sl->sequence;
// 全屏障,保证指令有序性
smp_wmb();
}
// 写顺序释放锁
static inline void write_sequnlock(seqlock_t *sl) {
smp_wmb();
// 这里会变为偶数
sl->sequence++;
spin_unlock(&sl->lock);
}
// 无阻塞的尝试获取写锁,其实就是调用spin_trylock
static inline int write_tryseqlock(seqlock_t *sl) {
int ret =spin_trylock(&sl->lock);
if(ret) {
++sl->sequence;
smp_wmb();
}
return ret;
}
// 这里并没上锁,而是使用了rmb读屏障。
// 这是为何?其实读屏障是为了保证对sequence的读不会被重排序到后面的读操作之后。
// 读sequence后返回是为何?很简单,用来比较,想想为何在读者读数据时,
// 允许写操作进入执行修改操作?就是在读完并操作完后,读者通过这里读的变量与当前的sequence 做比较,
// 如果不相等,是不是就说明了操作完成时有写者更改了状态,那么怎么办?直接重试执行即可
static inline unsigned read seqbegin(const seqlockt*sl) {
unsigned ret=sl->sequence;
smp_rmb();
return ret;
}
// 用于判断在readseqbegin期间有没有写者更改了变量,如果之前的iv也就是读者开始读操作时保存的seq值为奇数
// 直接重试。如果当前的sequence异或之前保存的seq值不为0的话,即不相等的话,也须重试
static inline int read_seqretry(const seqlock_t*sl,unsigned iv) {
smp_rmb();
return (iv &1)|(sl->sequence ^iv);
}
复制代码
总结
前面的都是比较容易理解的普通互斥锁、自旋锁、信号量、读写锁,本问 seq 锁是在之前的锁实现上做了一些不一样的操作。
评论