前面我们介绍的读/写自旋锁通过执行read_lock或write_lock操作获得相同的优先权。也就是说在读操作时,写操作必须等待;写操作时,读操作也需要的等待。这样虽然避免了数据的不一致,但是某些操作要等待。
这里我们引入了顺序锁,顺序锁与读/写自旋锁类似,只是现在写的优先级高于读操作。事实是:即使載读者正在读的时候也允许写操作的运行。这种策略的好处是写操作不会等待,除非另一个写操作正在进行。其缺点就是读操作不得不多进行几次直到获得有效的副本。这里你会不会问难道读得时候写不会造成数据不一致吗?这个问题接下来会讲到。
顺序锁通过seqlock_t这样一个数据结构来进行描述,其原型如下:
33 typedef struct {
34 unsigned sequence;
35 spinlock_t lock;
36 } seqlock_t;
其包含两个字段,第一个字段为顺序计数器。每个读者都必须載读数据前后两次读顺序计数器,并检查两次读到的值是否相同,如果不相同,说明新的写操作已经执行并顺序增加了顺序计数器,一次暗示读者重新读取数据。第二个字段为一个spinlock_t类型的lock。
顺序锁的初始化可以通过宏DEFINE_SEQLOCK()或seqlock_init()。把 seqlock_t声明为“未上锁”。写操作通过调用write_seqlock()和write_sequnlock()获取顺序锁。具体函数如下:(3.1.2源码)
58 static inline void write_seqlock(seqlock_t *sl)
59 {
60 spin_lock(&sl->lock);
61 ++sl->sequence;
62 smp_wmb();
63 }
64
65 static inline void write_sequnlock(seqlock_t *sl)
66 {
67 smp_wmb();
68 sl->sequence++;
69 spin_unlock(&sl->lock);
70 }
71
通过上面可以看出write_seqlock()先获取自旋锁后再对顺序计数器加一,而write_sequnlock()则是按照反反向来,先对顺序锁加一,然后释放自旋锁。总的来说执行一次写操作顺序计数器加2.这样保证了載写的过程中,计数器保持奇数。当写操作完成后计数器变为偶数。
read_seqbegin()则开始读计算,获得顺序锁的顺序号。如果顺序锁的值与当前不匹配,就通过read_seqretry()来返回1.
static __always_inline unsigned read_seqbegin(const seqlock_t *sl)
85 {
86 unsigned ret;
87
88 repeat:
89 ret = ACCESS_ONCE(sl->sequence);
90 if (unlikely(ret & 1)) {
91 cpu_relax();
92 goto repeat;
93 }
94 smp_rmb();
95
96 return ret;
97 }
/*
100 * Test if reader processed invalid data.
101 *
102 * If sequence value changed then writer changed data while in section.
103 */
104 static __always_inline int read_seqretry(const seqlock_t *sl, unsigned start )
105 {
106 smp_rmb();
107
108 return unlikely(sl->sequence != start);
109 }
接下来讨论顺序锁适应那种情况:首先读者的代码应该尽可能短且写者不能频繁获得锁,其次被保护的数据结构不包括被写修改的指针或被读间接引用的指针。
阅读(7793) | 评论(0) | 转发(1) |