linux 内核同步互斥技术之顺序锁

顺序锁

顺序锁区分读者和写者,和读写自旋锁相比,它的优点是不会出现写者饿死的情况。读者不会阻塞写者,读者读数据的时候写者可以写数据。顺序锁有序列号,写者把序列号加 1,如果读者检测到序列号有变化,发现写者修改了数据,将会重试,读者的代价比较高。

顺序锁支持两种类型的读者。

(1)顺序读者( sequence readers):不会阻塞写者,但是如果读者检测到序列号有变化,发现写者修改了数据,读者将会重试。

(2)持锁读者( locking readers):如果写者或另一个持锁读者正在访问临界区,持锁读者将会等待。持锁读者也会阻塞写者。这种情况下顺序锁退化为自旋锁。如果使用顺序读者,那么互斥访问的资源不能是指针,因为写者可能使指针失效,读者访问失效的指针会出现致命的错误。

顺序锁比读写自旋锁更加高效,但读写自旋锁适用于所有场合,而顺序锁不能适用于所有场合,所以顺序锁不能完全替代读写自旋锁。

顺序锁有两个版本。

(1)完整版的顺序锁提供自旋锁和序列号。

(2)顺序锁只提供序列号,使用者有自己的自旋锁。

完整版的顺序锁

完整版的顺序锁的定义如下:

include/linux/seqlock.h

typedef struct {

struct seqcount seqcount;

spinlock_t lock;

} seqlock_t;

typedef struct seqcount {

unsigned sequence;

#ifdef CONFIG_DEBUG_LOCK_ALLOC

struct lockdep_map dep_map;

#endif

} seqcount_t;

成员 seqcount 是序列号,成员 lock 是自旋锁。

定义并且初始化静态顺序锁的方法如下:DEFINE_SEQLOCK(x)

运行时动态初始化顺序锁的方法如下:seqlock_init(x)

顺序读者读数据的方法如下:

seqlock_t seqlock;

unsigned int seq;

do {

seq = read_seqbegin(&seqlock);

//读数据

} while (read_seqretry(&seqlock, seq));

首先调用函数 read_seqbegin 读取序列号,然后读数据,最后调用函数 read_seqretry 判断序列号是否有变化。如果序列号有变化,说明写者修改了数据,那么读者需要重试。

持锁读者读数据的方法如下:

seqlock_t seqlock;

read_seqlock_excl(&seqlock);

//读数据

read_sequnlock_excl(&seqlock);

函数 read_seqlock_excl 有一些变体。

(1) read_seqlock_excl_bh():申请自旋锁,并且禁止当前处理器的软中断。

(2) read_seqlock_excl_irq():申请自旋锁,并且禁止当前处理器的硬中断。

(3) read_seqlock_excl_irqsave():申请自旋锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断。

读者还可以根据情况灵活选择:如果没有写者在写数据,那么读者成为顺序读者;如果写者正在写数据,那么读者成为持锁读者。方法如下:

seqlock_t seqlock;

unsigned int seq = 0;

do {

read_seqbegin_or_lock(&seqlock, &seq);

//读数据

} while (need_seqretry(&seqlock, seq));

done_seqretry(&seqlock, seq);

函数 read_seqbegin_or_lock 有一个变体。

read_seqbegin_or_lock_irqsave:如果没有写者在写数据,那么读者成为顺序读者;如果写者正在写数据,那么读者成为持锁读者,申请自旋锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断。

写者写数据的方法如下:

write_seqlock(&seqlock);

//写数据

write_sequnlock(&seqlock);

函数 write_seqlock 有一些变体。

(1) write_seqlock_bh():申请写锁,并且禁止当前处理器的软中断。

(2) write_seqlock_irq():申请写锁,并且禁止当前处理器的硬中断。

(3) write_seqlock_irqsave():申请写锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断。

函数 write_seqlock 的代码如下:

include/linux/seqlock.h

static inline void write_seqlock(seqlock_t *sl)

{

spin_lock(&sl->lock);

write_seqcount_begin(&sl->seqcount);

}

static inline void write_seqcount_begin(seqcount_t *s)

{

write_seqcount_begin_nested(s, 0);

}

static inline void write_seqcount_begin_nested(seqcount_t *s, int subclass)

{

raw_write_seqcount_begin(s);

...

}

static inline void raw_write_seqcount_begin(seqcount_t *s)

{

s->sequence++;

smp_wmb();

}

写者释放顺序锁的执行过程是:首先把序列号加 1,序列号变成偶数,然后释放自旋锁。

只提供序列号的顺序锁

只提供序列号的顺序锁的定义如下:

include/linux/seqlock.h

typedef struct seqcount {

unsigned sequence;

#ifdef CONFIG_DEBUG_LOCK_ALLOC

struct lockdep_map dep_map;

#endif

} seqcount_t;

定义并且初始化静态顺序锁的方法如下:seqcount_t x = SEQCNT_ZERO(x);

运行时动态初始化顺序锁的方法如下:seqcount_init(s)

读者读数据的方法如下:

seqcount_t sc;

unsigned int seq;

do {

seq = read_seqcount_begin(&sc);

//读数据

} while (read_seqcount_retry(&sc, seq));

写者写数据的方法如下:

spin_lock(&mylock);/* 假设使用者定义了自旋锁mylock */

write_seqcount_begin(&sc);

//写数据

write_seqcount_end(&sc);

spin unlock(&mylock);

相关推荐
脏脏a10 小时前
告别物理出勤:Nginx 搭配 cpolar 实现远程开发无缝协作
运维·nginx
浅念-10 小时前
C语言——动态内存管理
c语言·开发语言·c++·笔记·学习
小草cys11 小时前
在 openEuler 上安装 DDE 图形桌面环境(适用于华为鲲鹏服务器/PC)
运维·服务器
草履虫建模16 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
wenzhangli718 小时前
OoderAgent SDK(0.6.6) UDP通讯与协议测试深度解析
网络·网络协议·udp
naruto_lnq18 小时前
分布式系统安全通信
开发语言·c++·算法
天才奇男子18 小时前
HAProxy高级功能全解析
linux·运维·服务器·微服务·云原生
安科士andxe18 小时前
60km 远距离通信新选择:AndXe SFP-155M 单模单纤光模块深度测评
网络·信息与通信
Jasmine_llq18 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
小李独爱秋18 小时前
“bootmgr is compressed”错误:根源、笔记本与台式机差异化解决方案深度指南
运维·stm32·单片机·嵌入式硬件·文件系统·电脑故障