linux并发控制之顺序锁

顺序锁是对读写锁的一种优化。
    1.读执行单元绝对不会被写执行单元阻塞。即读执行单元可以在写执行单元对被顺序锁保护的共享资源进行写操作的同时仍然可以继续读,而不必等待写执行单元完成之后再去读,同样,写执行单元也不必等待所有的读执行单元读完之后才去进行写操作
    2.写执行单元与写执行单元之间仍然是互斥的。
    3.如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新去读数据,以便确保读到的数据是完整的。
    4.要求共享资源中不能含有指针。

注意
    1.顺序锁:允许读和写操作之间的并发,也允许读与读操作之间的并发,但写与写操作之间只能是互斥的、串行的。
    2.读写自旋锁:只允许读操作与读操作之间的并发,而读与写操作,写与写操作之间只能是互斥的、串行的。
    3.自旋锁:不允许任何操作之间并发。

理解:
定义于#include<linux/seqlock.h>

首先看它的结构体,这个很重要
typedef struct {
unsigned sequence;
spinlock_t lock;
} seqlock_t;

1.若读操作期间发生了写操作,则要重读,怎样实现重读呢?
do{
    seqnum = read_seqbegin(&seqlock_r);    //读开始
    ……
}while(read_seqretry(&seqlock_r,seqnum));    //读操作期间是否发生写
其中:
int read_seqretry(const seqlock_t* sl, unsigned start);
read_seqretry_irqrestore(lock, iv, flags);

static __always_inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret = sl->sequence;
smp_rmb();
return ret;
}
static __always_inline int read_seqretry(const seqlock_t *sl, unsigned iv)
{
smp_rmb();
return (iv & 1) | (sl->sequence ^ iv);
}
从上面的代码可以看出,重读的关键就是对sequence变量的操作。

2.为什么可以允许同时读写,而写操作之间仍然互斥?
static inline void write_seqlock(seqlock_t *sl)
{
spin_lock(&sl->lock);
++sl->sequence;
smp_wmb();
}
static inline void write_sequnlock(seqlock_t *sl)
{
smp_wmb();
sl->sequence++;
spin_unlock(&sl->lock);
}
写操作其实就是利用自旋锁是对sequence变量的保护,进程在申请写锁的时候,只是简单的将sequence变量的值增1
对于写操作,首先将sequence变量的值保存下来(这时候已在do-while循环中),然后在read_seqretry函数中作判断,检测当前的读锁变量是否发生变化,若发生变化,则调用写锁函数,sequence变量的值增1,重新读;若不发生变化,则表示申请读锁成功

操作:
seqlock_init(x);       //动态初始化
DEFINE_SEQLOCK(x);     //静态初始化

void write_seqlock(seqlock_t* sl);        //写加锁
int write_tryseqlock(seqlock_t* sl);      //尝试写加锁
write_seqlock_irqsave(lock, flags);       //local_irq_save() + write_seqlock()
write_seqlock_irq(lock);                  //local_irq_disable() + write_seqlock()
write_seqlock_bh(lock);                  //local_bh_disable() + write_seqlock()

void write_sequnlock(seqlock_t* sl);         //写解锁
write_sequnlock_irqrestore(lock, flags);     //write_sequnlock() + local_irq_restore()
write_sequnlock_irq(lock);                   //write_sequnlock() + local_irq_enable()
write_sequnlock_bh(lock);                    //write_sequnlock() + local_bh_enable()

A.读操作:
unsigned int read_seqbegin(const seqlock_t* sl);
read_seqbegin_irqsave(lock, flags);          //local_irq_save() + read_seqbegin()
读执行单元在访问共享资源时要调用顺序锁的读函数,返回顺序锁s1的顺序号;该函数没有任何获得锁和释放锁的开销,只是简单地返回顺序锁当前的序号;
B.重读操作:
int read_seqretry(const seqlock_t* sl, unsigned start);
read_seqretry_irqrestore(lock, iv, flags);
在顺序锁的一次读操作结束之后,调用顺序锁的重读函数,用于检查是否有写执行单元对共享资源进行过写操作;如果有,则需要重新读取共享资源;iv为顺序锁的顺序号;

用例:

write_seqlock(&lock);
...... //写操作代码
write_sequnlock(&lock);

unsigned int seq_num = 0;
do
{
  seq_num = read_seqbegin(&seqlock);
  //读操作代码
  ......
} while(read_seqretry(&seqlock, seq_num));


注:如果写执行单元在操作被顺序锁保护的共享资源时已经保持了互斥锁保护对共享资源的写操作,即:写执行单元与写执行单元之间已经是互斥的,但是读执行单元仍然可以与写执行单元同时访问,那么这种情况下仅需要使用顺序计数(struct seqcount)即可,而不必使用spinlock。
使用顺序计数必须非常小心,只有确定在访问共享资源时已经保持了互斥锁才可以使用;即:只有写操作与写操作之间已经是互斥的、串行的时,才可以使用顺序计数。

 

posted on 2013-01-31 17:19  疯子123  阅读(444)  评论(0编辑  收藏  举报

导航