互斥的方法有mutex、spinlock等,如果要偏向于reader,有read-write spinlock,那么如果要偏向于writer呢?怎么办。
使用seqlock,seqlock是sequential lock的简写。
seqlock是读写自旋锁的一种辅助解决方案,偏向于写者,避免写者的饥饿。
概要
工作机制
顺序锁是一种偏向于写者的读写锁机制。
- 当没有写者在临界区内时,writer可以获得写锁,进入临界区,此时,它会将sequence number 加1,这样,sequence number就是奇数值;当它完成写操作退出时,再次加sequence number加1,此时sequence number变为偶数值;所以,如果sequence number是奇数,在进行写操作,sequence number为偶数,写操作完成。
- 当读者需要读取数据时,首先读取sequence number值,如果是偶数,则读者进入临界区读取数据,如果是奇数,则读者等待,等待sequence number变为偶数值(不阻塞等待);当读者进入临界区时,会记录下当前的sequence number值。
- 读取完数据后,读者会再次比较现在的sequence number与进入时记录的sequence number,如果相同,则一切正常,如果不同,则说明有人进行了写操作,读者会再次循环第二步,直至读取完毕。读者不会阻塞,如果有写者在操作,读者便一直retry尝试下去。
- 当临界区中只有读者时,写者可以获得写锁随时进入;这意味着读者不能阻塞写者,当写者操作时,读者只能重复尝试读取,seqlock是一种偏向于写者的读写锁机制。
使用场景
seqlock不是任何场景都有效果的,且在以下一些场景中有效果:
- 当读操作频率明显多于写操作频率;
- 写操作较少且必须快速的场景;
- 共享的数据结构比较简单,结构中最好不包含指针,因为读者读指针时,该指针可能正在被写者修改。
initial seqlock
结构体
/*
* Sequential locks (seqlock_t)
*
* Sequence counters with an embedded spinlock for writer serialization
* and non-preemptibility.
*
* For more info, see:
* - Comments on top of seqcount_t
* - Documentation/locking/seqlock.rst
*/
typedef struct {
/*
* Make sure that readers don't starve writers on PREEMPT_RT: use
* seqcount_spinlock_t instead of seqcount_t. Check __SEQ_LOCK().
*/
seqcount_spinlock_t seqcount;
spinlock_t lock;
} seqlock_t;
seqlock本质上还是spinlock,只是多了一个sequence number计数。
include/linux/seqlock.h
中定义
/**
* seqlock_init() - dynamic initializer for seqlock_t
* @sl: Pointer to the seqlock_t instance
*/
#define seqlock_init(sl) \
do { \
spin_lock_init(&(sl)->lock); \
seqcount_spinlock_init(&(sl)->seqcount, &(sl)->lock); \
} while (0)
比如
#include<linux/seqlock.h>
seqlock_t lock;
seqlock_init(&lock);
howto
write
1. 写顺序锁
static inline void write_seqlock(seqlock_t *sl)
{
spin_lock(&sl->lock);
write_seqcount_t_begin(&sl->seqcount.seqcount);
}
2. bottom halves中
static inline void write_seqlock_bh(seqlock_t *sl)
{
spin_lock_bh(&sl->lock);
write_seqcount_t_begin(&sl->seqcount.seqcount);
}
3. hard irq环境
static inline void write_seqlock_irq(seqlock_t *sl)
{
spin_lock_irq(&sl->lock);
write_seqcount_t_begin(&sl->seqcount.seqcount);
}
#define write_seqlock_irqsave(lock, flags) \
do { flags = __write_seqlock_irqsave(lock); } while (0)
static inline unsigned long __write_seqlock_irqsave(seqlock_t *sl)
{
unsigned long flags;
spin_lock_irqsave(&sl->lock, flags);
write_seqcount_t_begin(&sl->seqcount.seqcount);
return flags;
}
read
1. 获取读顺序锁并记录seq num
/**
* read_seqbegin() - start a seqlock_t read side critical section
* @sl: Pointer to seqlock_t
*
* Return: count, to be passed to read_seqretry()
*/
static inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret = read_seqcount_begin(&sl->seqcount);
kcsan_atomic_next(0); /* non-raw usage, assume closing read_seqretry() */
kcsan_flat_atomic_begin();
return ret;
}
2. 结束read seqlock
/**
* read_seqretry() - end a seqlock_t read side section
* @sl: Pointer to seqlock_t
* @start: count, from read_seqbegin()
*
* read_seqretry closes the read side critical section of given seqlock_t.
* If the critical section was invalid, it must be ignored (and typically
* retried).
*
* Return: true if a read section retry is required, else false
*/
static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
{
/*
* Assume not nested: read_seqretry() may be called multiple times when
* completing read critical section.
*/
kcsan_flat_atomic_end();
return read_seqcount_retry(&sl->seqcount, start);
}
read seqlock用法比如
unsigned int seq_no;
do {
seq_no = read_seqbegin(&lock);
/* Read the data */
} while ( read_seqretry(&lock, seq_no) );
示例
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h> //kernel threads
#include <linux/sched.h> //task_struct
#include <linux/delay.h>
#include <linux/seqlock.h>
//Seqlock variable
seqlock_t test_seq_lock;
unsigned long test_global_variable = 0;
static struct task_struct *test_thread1;
static struct task_struct *test_thread2;
//Thread used for writing
int thread_function1(void *pv)
{
while(!kthread_should_stop()) {
write_seqlock(&test_seq_lock);
test_global_variable++;
pr_info("In Thread Function1 : write value %lu\n", test_global_variable);
write_sequnlock(&test_seq_lock);
msleep(1000);
}
return 0;
}
//Thread used for reading
int thread_function2(void *pv)
{
unsigned int seq_no;
unsigned long read_value;
while(!kthread_should_stop()) {
do {
seq_no = read_seqbegin(&test_seq_lock);
read_value = test_global_variable;
} while (read_seqretry(&test_seq_lock, seq_no));
pr_info("In Thread Function2 : Read value %lu\n", read_value);
msleep(1000);
}
return 0;
}
static int __init seqlock_driver_init(void)
{
/* Creating Thread 1 */
test_thread1 = kthread_run(thread_function1,NULL,"seqlock Thread1");
if(test_thread1) {
pr_err("Kthread1 Created Successfully...\n");
} else {
pr_err("Cannot create kthread1\n");
return -1;
}
/* Creating Thread 2 */
test_thread2 = kthread_run(thread_function2,NULL,"seqlock Thread2");
if(test_thread2) {
pr_err("Kthread2 Created Successfully...\n");
} else {
pr_err("Cannot create kthread2\n");
return -1;
}
//Initialize the seqlock
seqlock_init(&test_seq_lock);
pr_info("Seqlock Driver Insert...Done!!!\n");
return 0;
}
/*
** Module exit function
*/
static void __exit seqlock_driver_exit(void)
{
kthread_stop(test_thread1);
kthread_stop(test_thread2);
pr_info("Seqlock Driver Remove...Done!!\n");
}
module_init(seqlock_driver_init);
module_exit(seqlock_driver_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple device driver - Seqlock");
运行效果
root@pc:seqlock# dmesg
[242501.365747] Kthread1 Created Successfully...
[242501.365798] In Thread Function1 : write value 1
[242501.365905] Kthread2 Created Successfully...
[242501.365909] Seqlock Driver Insert...Done!!!
[242501.365919] In Thread Function2 : Read value 1
[242502.379110] In Thread Function2 : Read value 1
[242502.379119] In Thread Function1 : write value 2
[242503.403163] In Thread Function1 : write value 3
[242503.403173] In Thread Function2 : Read value 3
[242504.427142] In Thread Function2 : Read value 3
[242504.427150] In Thread Function1 : write value 4
[242505.451206] In Thread Function1 : write value 5
[242505.451216] In Thread Function2 : Read value 5
[242506.475199] In Thread Function2 : Read value 5
[242506.475210] In Thread Function1 : write value 6
[242507.499187] In Thread Function1 : write value 7
[242507.499195] In Thread Function2 : Read value 7
[242508.523207] In Thread Function2 : Read value 7
[242508.523214] In Thread Function1 : write value 8
[242509.547213] In Thread Function1 : write value 9
[242509.547223] In Thread Function2 : Read value 9
[242510.571236] In Thread Function2 : Read value 9
[242510.571244] In Thread Function1 : write value 10
[242511.595242] In Thread Function1 : write value 11
[242511.595250] In Thread Function2 : Read value 11
[242512.619260] In Thread Function2 : Read value 11
[242512.619267] In Thread Function1 : write value 12
[242513.643272] In Thread Function1 : write value 13
[242513.643279] In Thread Function2 : Read value 13
[242514.667298] In Thread Function2 : Read value 13
[242514.667305] In Thread Function1 : write value 14
[242515.691317] In Thread Function1 : write value 15
[242515.691324] In Thread Function2 : Read value 15
[242516.715371] In Thread Function2 : Read value 15
[242517.739428] Seqlock Driver Remove...Done!!