Linux内核之seqlock机制

互斥的方法有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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核中,有多种锁机制可用于实现同步和互斥操作。以下是一些常见的锁机制: 1. 自旋锁(Spinlock):自旋锁是一种基于忙等待的锁机制。当一个进程或线程尝试获取自旋锁时,如果锁已被占用,它会一直自旋等待,直到锁被释放。 2. 互斥锁(Mutex):互斥锁是一种基于阻塞的锁机制。当一个进程或线程尝试获取互斥锁时,如果锁已被占用,它会被阻塞,直到锁被释放。 3. 读写锁(ReadWrite Lock):读写锁允许多个读操作同时进行,但只有一个写操作可以进行。读操作之间不会互斥,而写操作会独占资源。 4. 原子操作(Atomic Operations):原子操作是一种不可中断的操作,可以确保在多线程环境下对共享变量的原子性访问。原子操作可以用于实现简单的同步和互斥。 5. 信号量(Semaphore):信号量是一种计数器,用于控制对共享资源的访问。它可以用于限制同时访问资源的进程或线程数目。 6. 屏障(Barrier):屏障是一种同步机制,它可以使一组进程或线程在某个点上等待,直到所有进程或线程都到达该点,然后再继续执行。 除了上述常见的锁机制Linux内核中还提供了其他更高级的锁机制,如读写自旋锁(Read-Write Spinlock)、顺序锁(Seqlock)等,用于满足不同场景下的同步需求。 这些锁机制Linux内核中被广泛应用于实现同步和互斥操作,确保共享资源的正确访问和保护。选择适当的锁机制取决于具体的需求和性能要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值