Linux并发控制——顺序锁(seqlock)
定义在头文件linux/seqlock.h中;
顺序锁(seqlock)是对读写锁的一种优化,提高了读锁和写锁的独立性。写锁不会被读锁阻塞,读锁也不会被写锁阻塞。写锁会被写锁阻塞。
若使用顺序锁,读执行单元绝对不会被写执行单元所阻塞,也就是说,临界区可以在写临界区对被顺序锁保护的共享资源进行写操作的同时仍然可以继续读,而不必等待写执行单元完成之后再去读,同样,写执行单元也不必等待所有的读执行单元读完之后才去进行写操作。
但是写执行单元与写执行单元之间仍然是互斥的,即:如果有写执行单元正在进行写操作,那么其它的写执行单元必须自旋在那里,直到写执行单元释放顺序锁为止。
如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新去读数据,以便确保读到的数据是完整的;这种锁在读写操作同时进行的概率比较小,性能是非常好的,而且它允许读写操作同时进行,因而更大地提高了并发性。
顺序锁有一个限制:它必须要求被保护的共享资源中不能含有指针;因为写执行单元可能会使指针失效,当读执行单元如果正要访问该指针时,系统就会崩溃。
用于能够区分读与写的场合,并且是读操作很多、写操作很少,写操作的优先权大于读操作。 seqlock的实现思路是,用一个递增的整型数表示sequence。写操作进入临界区时,sequence++;退出临界区时,sequence再++。写操作还需要获得一个锁(比如mutex),这个锁仅用于写写互斥,以保证同一时间最多只有一个正在进行的写操作。 当sequence为奇数时,表示有写操作正在进行,这时读操作要进入临界区需要等待,直到sequence变为偶数。读操作进入临界区时,需要记录下当前sequence的值,等它退出临界区的时候用记录的sequence与当前sequence做比较,不相等则表示在读操作进入临界区期间发生了写操作,这时候读操作读到的东西是无效的,需要返回重试。 seqlock写写是必须要互斥的。但是seqlock的应用场景本身就是读多写少的情况,写冲突的概率是很低的。所以这里的写写互斥基本上不会有什么性能损失。 而读写操作是不需要互斥的。seqlock的应用场景是写操作优先于读操作,对于写操作来说,几乎是没有阻塞的(除非发生写写冲突这一小概率事件),只需要做sequence++这一附加动作。而读操作也不需要阻塞,只是当发现读写冲突时需要retry。 seqlock的一个典型应用是时钟的更新,系统中每1毫秒会有一个时钟中断,相应的中断处理程序会更新时钟(见《linux时钟浅析》)(写操作)。而用户程序可以调用gettimeofday之类的系统调用来获取当前时间(读操作)。在这种情况下,使用seqlock可以避免过多的gettimeofday系统调用把中断处理程序给阻塞了(如果使用读写锁,而不用seqlock的话就会这样)。中断处理程序总是优先的,而如果gettimeofday系统调用与之冲突了,那用户程序多等等也无妨。
比较:顺序锁允许读操作和写操作之间的并发,也允许读操作与读操作之间的并发,但不允许写操作与写操作之间的并发,写操作与写操作之间只能是互斥的、串行的;读写自旋锁只允许读操作与读操作之间的并发,而读操作与写操作之间只能是互斥的、串行的,写操作与写操作之间也只能是互斥的、串行的;自旋锁则是不允许任何操作之间并发,即:读操作与读操作之间、读操作与写操作之间、写操作与写操作之间都是互斥的、串行的;
1).顺序锁的初始化:
seqlock_init(x); //动态初始化
DEFINE_SEQLOCK(x); //静态初始化
2).顺序锁的写锁定:
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()
3).顺序锁的写解锁:
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()
4).顺序锁的读锁:
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为顺序锁的顺序号;
5).顺序锁的写执行单元模式:
write_seqlock(&lock);
...... //写操作代码
write_sequnlock(&lock);
6).顺序锁的读执行单元模式:
unsigned int seq_num = 0;
do
{
seq_num = read_seqbegin(&seqlock);
//读操作代码
......
} while(read_seqretry(&seqlock, seq_num));
7).如果写执行单元在操作被顺序锁保护的共享资源时已经保持了互斥锁保护对共享资源的写操作,即:写执行单元与写执行单元之间已经是互斥的,但是读执行单元仍然可以与写执行单元同时访问,那么这种情况下仅需要使用顺序计数(struct seqcount)即可,而不必使用spinlock;
A.顺序计数的初始化:
seqcount_init(sc);
B.顺序计数读锁定:
unsigned int read_seqcount_begin(const seqcount_t* sc);
在读执行单元对被顺序计数sc保护的共享资源进行读操作之前调用该函数来获得顺序计数sc的当前序号;
int read_seqcount_retry(const seqcount_t* sc, unsigned int start);
在读执行单元对被顺序计数sc保护的共享资源执行完一次读操作之后调用该函数,来检查在读操作期间是否有写执行单元访问过该共享资源;如果是,则读执行单元就需要重新读取共享资源,否则,就算是成功完成了读操作;
C.顺序计数写锁定:
void write_seqcount_begin(seqcount_t* sc);
在写执行单元对被顺序计数sc保护的共享资源进行写操作之前调用该函数,来对顺序计数sc的顺序号加1,以便于读执行单元能够检查出是否在读操作期间有写执行单元访问过;
void write_seqcount_end(seqcount_t* sc);
在写执行单元对被顺序计数sc保护的共享资源进行写操作之后调用该函数,来对顺序计数sc的顺序号加1,以便于读执行单元能够检查出是否在读操作期间有写执行单元访问过;
D.顺序计数读锁定模式:
unsigned int seq_num = 0;
do
{
seq_num = read_seqcount_begin(&sc);
//读操作代码
......
} while(read_seqcount_retry($sc, seq_num));
E:顺序计数的写锁定模式:
write_seqcount_begin(&sc);
//写操作代码
......
write_seqcount_end(&sc);
特别提醒:使用顺序计数必须非常小心,只有确定在访问共享资源时已经保持了互斥锁才可以使用;即:只有写操作与写操作之间已经是互斥的、串行的时,才可以使用顺序计数;
例子:
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
//这三个头文件与内核线程的使用有关;
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/err.h>
//顺序锁相关
#include <linux/seqlock.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("*************");
MODULE_VERSION("2.6.35.000");
static int sleep_time = (1*10*HZ);
static int shared_res = 0;
//STEP1:定义顺序锁
seqlock_t my_seq_lock;
//STEP5:实现线程函数
static int thread_process1(void* param)
{
//int val = 0, ret = 0;
while(1)
{
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop())
{
printk("kernel thread '%s' should stop;file:%s;line:%d\n", __FUNCTION__, __FILE__, __LINE__);
break;
}
//STEP3:对临界区加锁
write_seqlock(&my_seq_lock);
shared_res++;
//STEP4:对临界区解锁
write_sequnlock(&my_seq_lock);
mdelay(sleep_time);
}
return 12;
};
static int thread_process2(void* param)
{
//int val = 0, ret = 0;
while(1)
{
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop())
{
printk("kernel thread '%s' should stop;file:%s;line:%d\n", __FUNCTION__, __FILE__, __LINE__);
break;
}
//STEP3:对临界区加锁
write_seqlock(&my_seq_lock);
shared_res++;
//STEP4:对临界区解锁
write_sequnlock(&my_seq_lock);
msleep(sleep_time);
}
return 34;
};
static int thread_process3(void* param)
{
int val = 0;//, ret = 0;
unsigned int seq_num = 0;
while(1)
{
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop())
{
printk("kernel thread '%s' should stop;file:%s;line:%d\n", __FUNCTION__, __FILE__, __LINE__);
break;
}
//seq lock read --- begin
do
{
seq_num = read_seqbegin(&my_seq_lock);
val = shared_res;
printk("%s: shared resource = %d;\n%s", __FUNCTION__, val, ((val % 3) ? "" : "\n"));
} while(read_seqretry(&my_seq_lock, seq_num));
//seq lock read --- end
msleep(sleep_time);
}
return 56;
};
static int thread_process4(void* param)
{
int val = 0;//, ret = 0;
unsigned int seq_num = 0;
while(1)
{
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop())
{
printk("kernel thread '%s' should stop;file:%s;line:%d\n", __FUNCTION__, __FILE__, __LINE__);
break;
}
//seq lock read --- begin
do
{
seq_num = read_seqbegin(&my_seq_lock);
val = shared_res;
printk("%s: shared resource = %d;\n%s", __FUNCTION__, val, ((val % 3) ? "" : "\n"));
} while(read_seqretry(&my_seq_lock, seq_num));
//seq lock read --- end
msleep(sleep_time);
}
return 78;
};
static struct task_struct* my_thread1 = NULL;
static struct task_struct* my_thread2 = NULL;
static struct task_struct* my_thread3 = NULL;
static struct task_struct* my_thread4 = NULL;
static int __init study_init(void)
{
int err = 0;
printk("%s\n", __PRETTY_FUNCTION__);
//STEP2:初始化顺序锁
seqlock_init(&my_seq_lock);
printk("init seq lock ok\n");
my_thread1 = kthread_create(thread_process1, NULL, "my_thread1");
if(IS_ERR(my_thread1))
{
err = PTR_ERR(my_thread1);
my_thread1 = NULL;
printk(KERN_ERR "unable to start kernel thread1:%d\n", err);
return err;
}
my_thread2 = kthread_create(thread_process2, NULL, "my_thread2");
if(IS_ERR(my_thread2))
{
err = PTR_ERR(my_thread2);
my_thread2 = NULL;
printk(KERN_ERR "unable to start kernel thread2:%d\n", err);
return err;
}
my_thread3 = kthread_create(thread_process3, NULL, "my_thread3");
if(IS_ERR(my_thread3))
{
err = PTR_ERR(my_thread3);
my_thread3 = NULL;
printk(KERN_ERR "unable to start kernel thread3:%d\n", err);
return err;
}
my_thread4 = kthread_create(thread_process4, NULL, "my_thread4");
if(IS_ERR(my_thread4))
{
err = PTR_ERR(my_thread4);
my_thread4 = NULL;
printk(KERN_ERR "unable to start kernel thread4:%d\n", err);
return err;
}
wake_up_process(my_thread1);
wake_up_process(my_thread2);
wake_up_process(my_thread3);
wake_up_process(my_thread4);
printk("%s:all kernel thread start;\n", __FUNCTION__);
return 0;
}
static void __exit study_exit(void)
{
int ret = -1;
printk("%s\n",__PRETTY_FUNCTION__);
if(my_thread1)
{
ret = kthread_stop(my_thread1);
my_thread1 = NULL;
printk("kernel thread1 stop,exit code is %d;\n",ret);
}
if(my_thread2)
{
ret = kthread_stop(my_thread2);
my_thread2 = NULL;
printk("kernel thread2 stop,exit code is %d;\n",ret);
}
if(my_thread3)
{
ret = kthread_stop(my_thread3);
my_thread3 = NULL;
printk("kernel thread3 stop,exit code is %d;\n",ret);
}
if(my_thread4)
{
ret = kthread_stop(my_thread4);
my_thread4 = NULL;
printk("kernel thread4 stop,exit code is %d;\n",ret);
}
printk("%s:all kernel thread stop;\n", __FUNCTION__);
}
module_init(study_init);
module_exit(study_exit);