Linux内核之seqlock机制

本文介绍了seqlock,一种偏向于写者的读写锁机制。seqlock通过维护一个sequence number来避免写者的饥饿现象,并允许读者在非阻塞的方式下重试读取操作。适用于读操作远多于写操作的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

互斥的方法有mutex、spinlock等,如果要偏向于reader,有read-write spinlock,那么如果要偏向于writer呢?怎么办。

使用seqlock,seqlock是sequential lock的简写

seqlock是读写自旋锁的一种辅助解决方案,偏向于写者,避免写者的饥饿

概要

工作机制

顺序锁是一种偏向于写者的读写锁机制。

  1. 没有写者在临界区内时,writer可以获得写锁,进入临界区,此时,它会将sequence number 加1,这样,sequence number就是奇数值;当它完成写操作退出时,再次加sequence number加1,此时sequence number变为偶数值;所以,如果sequence number是奇数,在进行写操作,sequence number为偶数,写操作完成
  2. 当读者需要读取数据时,首先读取sequence number值,如果是偶数,则读者进入临界区读取数据,如果是奇数,则读者等待,等待sequence number变为偶数值(不阻塞等待);当读者进入临界区时,会记录下当前的sequence number值
  3. 读取完数据后,读者会再次比较现在的sequence number与进入时记录的sequence number,如果相同,则一切正常,如果不同,则说明有人进行了写操作,读者会再次循环第二步,直至读取完毕读者不会阻塞,如果有写者在操作,读者便一直retry尝试下去
  4. 当临界区中只有读者时,写者可以获得写锁随时进入;这意味着读者不能阻塞写者,当写者操作时,读者只能重复尝试读取,seqlock是一种偏向于写者的读写锁机制。
使用场景

seqlock不是任何场景都有效果的,且在以下一些场景中有效果:

  1. 当读操作频率明显多于写操作频率;
  2. 写操作较少且必须快速的场景;
  3. 共享的数据结构比较简单,结构中最好不包含指针,因为读者读指针时,该指针可能正在被写者修改。
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!!

reference

Linux Device Driver Tutorials – ch31

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值