Linux序列锁

一、序列锁与其他锁的关系

In our previous tutorials, we have seen some locking methods like mutexspinlock, etc. In short, When you want to treat both write and reader operation equally, then you have to use spinlock. In some situations, we may have to give importance to readers. In such a case, we can use read-write spinlock.

Likewise, is there any mechanism that gives importance to writers? Yeah, it is there in Linux. Seqlock is the one that gives importance to writers. We’ll continue to see about seqlock.

二、Seqlock的机制

  1. When no one is in a critical section then one writer can enter into a critical section by acquiring its lock. Once it took its lock then the writer will increment the sequence number by one. Currently, the sequence number is an odd value. Once done with the writing, again it will increment the sequence number by one. Now the number is an even value. So, when the sequence number is an odd value, writing is happening. When the sequence number is an even value, writing has done. Only one writer thread will be allowed in the critical section. So other writers will be waiting for the lock.
  2. When the reader wants to read the data, first it will read the sequence number. If it is an even value, then it will go to a critical section and reads the data. If it is an odd value (the writer is writing something), the reader will wait for the writer to finish (the sequence number becomes an even number). The value of the sequence number while entering into the critical section is called an old sequence number.
  3. After reading the data, again it will check the sequence number. If it is equal to the old sequence number, then everything is okay. Otherwise, it will repeat step 2 again. In this case, readers simply retry (using a loop) until they read the same even sequence number before and after. The reader never blocks, but it may have to retry if a write is in progress.
  4. When only the reader is reading the data and no writer is in the critical section, any time one writer can enter into a critical section by taking lock without blocking. This means the writer cannot be blocked for the reader and the reader has to re-read the data when the writer is writing. This means seqlock is giving importance to a writer, not the reader (the reader may have to wait but not the writer).

三、Seqlock的使用场景

We cannot use this seqlock in any situations like normal spinlock or mutex. Because this will not be effective in such situations other than the situations mentioned below.

  • where read operations are more frequent than write.
  • where write access is rare but must be fast.
  • That data is simple (no pointers) that needs to be protected. Seqlocks generally cannot be used to protect data structures involving pointers, because the reader may be following a pointer that is invalid while the writer is changing the data structure.

四、Seqlock in Linux Kernel – API

初始化API:

1、seqlock_init(seqlock_t *lock); —— Init Seqlock

写API:

2、void write_seqlock(seqlock_t *lock);——write_seqlock

3、int write_tryseqlock(seqlock_t *lock); —— write_tryseqlock

4、void write_seqlock_irqsave(seqlock_t *lock, long flags); —— This will save whether interrupts were ON or OFF in a flags word and grab the lock. This API is used in an interrupt context.

5、void write_seqlock_irq(seqlock_t *lock); —— This will disable interrupts on that CPU, and take the lock while writing. This API is used in an interrupt context.

6、void write_seqlock_bh(seqlock_t *lock); —— This is similar to write_seqlock, but when you try to write from the bottom halves you can use this call.

7、void write_sequnlock(seqlock_t *lock); 

8、void write_sequnlock_irqrestore(seqlock_t *lock, long flags); —— This will release the lock and restores the interrupts using the flags argument. This API is used in an interrupt context.

9、void write_sequnlock_irq(seqlock_t *lock); —— This will release the lock and re-enable interrupts on that CPU, which is disabled by write_seqlock_irq call. This API is used in an interrupt context.

10、void write_sequnlock_bh(seqlock_t *lock);—— This will be used from the bottom halves while reading.

读API:

There is no locking needed for reading the protected data. But we have to implement the below steps in our code.

     1)Begin the read and get the initial sequence number.

     2)Read the data.

     3)Once the reading is done, compare the current sequence number with an initial sequence number. If the current sequence number is an odd value or the current sequence number is not matching with the initial sequence number means writing is going on. So the reader has to retry, which means the reader has to again go to step 1 and do the process again.

1、 unsigned int read_seqbegin(seqlock_t *lock); —— read_seqbegin

2、unsigned int read_seqbegin_irqsave(seqlock_t *lock, long flags); —— this will save whether interrupts were ON or OFF in a flags word and return the sequence number.

3、int read_seqretry(seqlock_t *lock, unsigned int seq_no); —— This API will compare the current sequence number with the provided sequence number (argument 2). If the current sequence number is an odd value or the current sequence number is not matching with the initial sequence number (argument 2) means writing is going on. So it will return 1. Otherwise, it will return 0.

4、int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq_no, long flags); —— This will restore the interrupt using flags, and work like read_seqretry.

5、使用示例

This example is for the user context. Use other variants based on the context(bottom half or IRQ). An example reading snippet is given below.

unsigned int seq_no;
do {
    seq_no = read_seqbegin(&lock);
    /* Read the data */
} while ( read_seqretry(&lock, seq_no) );

五、顺序锁的内存屏障

顺序锁————sequence locks:

typedef struct {	
	unsigned long seq;
	spinlock_t lock;
} seqlock_t;
							
static inline void seqlock_init(seqlock_t *slp)
{
	slp->seq = 0;
	spin_lock_init(&slp->lock);
}

static inline unsigned long read_seqbegin(seqlock_t *slp)
{
	unsigned long s;

	s = READ_ONCE(slp->seq);
	smp_mb();				
	return s & ~0x1UL;		
}							

static inline int read_seqretry(seqlock_t *slp, unsigned long oldseq)
{
	unsigned long s;

	smp_mb();					/*
								 * 这个内存屏障有什么用?smp_mb()暗含了barrier()。如果去掉,编译器和CPU都有可能将
								 * read_seqretry()之前的临界区(指令)移到该函数之后,使得顺序锁无法保护临界区。
								 * smp_mb()防止了这种重排。
								 */
	s = READ_ONCE(slp->seq);
	return s != oldseq;		
}							

static inline void write_seqlock(seqlock_t *slp)
{
	spin_lock(&slp->lock);		//暗含内存屏障
	++slp->seq;
	smp_mb();
}

static inline void write_sequnlock(seqlock_t *slp)
{
	smp_mb();									
	++slp->seq;				
	spin_unlock(&slp->lock);	//暗含内存屏障
}							


/**
 * 读示例
 */
do {
	seq = read_seqbegin(&test_seqlock);
	/* 读临界区 */
} while(read_seqretry(&test_seqlock, seq));


/**
 * 写示例
 */
write_seqlock(&test_seqlock)
/* 写临界区 */
write_sequnlock(&test_seqlock)

总结,结合读示例和写示例可知:
smp_mb()的主要作用就是将临界区和seqlock实现逻辑分开,避免编译器和CPU将临界区代码重排到seqlock实现里面去。

六、示例代码

 This code snippet explains how to create two threads that access a global variable (etx_gloabl_variable). Thread 1 is for writing and Thread 2 is for reading. Before writing to the variable, the writer should take the seqlock. After that, it will release the seqlock.  The reader will check the sequence number. If it is not a valid sequence number, then again the reader will retry.

/***************************************************************************//**
*  \file       driver.c
*
*  \details    Simple Linux device driver (Seqlock)
*
*  \author     EmbeTronicX
*
*  \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include<linux/slab.h>                 //kmalloc()
#include<linux/uaccess.h>              //copy_to/from_user()
#include <linux/kthread.h>             //kernel threads
#include <linux/sched.h>               //task_struct 
#include <linux/delay.h>
#include <linux/seqlock.h>
 
//Seqlock variable
seqlock_t etx_seq_lock;
 
unsigned long etx_global_variable = 0;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
 
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
 
static struct task_struct *etx_thread1;
static struct task_struct *etx_thread2; 
 
/*************** Driver functions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, 
                char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, 
                const char *buf, size_t len, loff_t * off);
 /******************************************************/
 
int thread_function1(void *pv);
int thread_function2(void *pv);
 
//Thread used for writing
int thread_function1(void *pv)
{
    while(!kthread_should_stop()) {  
        write_seqlock(&etx_seq_lock);
        etx_global_variable++;
        write_sequnlock(&etx_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(&etx_seq_lock);
        read_value = etx_global_variable;
    } while (read_seqretry(&etx_seq_lock, seq_no));
        pr_info("In EmbeTronicX Thread Function2 : Read value %lu\n", read_value);
        msleep(1000);
    }
    return 0;
}

//File operation structure 
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .release        = etx_release,
};


/*
** This function will be called when we open the Device file
*/ 
static int etx_open(struct inode *inode, struct file *file)
{
        pr_info("Device File Opened...!!!\n");
        return 0;
}

/*
** This function will be called when we close the Device file
*/ 
static int etx_release(struct inode *inode, struct file *file)
{
        pr_info("Device File Closed...!!!\n");
        return 0;
}

/*
** This function will be called when we read the Device file
*/ 
static ssize_t etx_read(struct file *filp, 
                char __user *buf, size_t len, loff_t *off)
{
        pr_info("Read function\n");
 
        return 0;
}

/*
** This function will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, 
                const char __user *buf, size_t len, loff_t *off)
{
        pr_info("Write Function\n");
        return len;
}

/*
** Module Init function
*/ 
static int __init etx_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
                pr_info("Cannot allocate major number\n");
                return -1;
        }
        pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
 
        /*Creating cdev structure*/
        cdev_init(&etx_cdev,&fops);
 
        /*Adding character device to the system*/
        if((cdev_add(&etx_cdev,dev,1)) < 0){
            pr_info("Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
            pr_info("Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
            pr_info("Cannot create the Device \n");
            goto r_device;
        }
 
        
        /* Creating Thread 1 */
        etx_thread1 = kthread_run(thread_function1,NULL,"eTx Thread1");
        if(etx_thread1) {
            pr_err("Kthread1 Created Successfully...\n");
        } else {
            pr_err("Cannot create kthread1\n");
             goto r_device;
        }
 
         /* Creating Thread 2 */
        etx_thread2 = kthread_run(thread_function2,NULL,"eTx Thread2");
        if(etx_thread2) {
            pr_err("Kthread2 Created Successfully...\n");
        } else {
            pr_err("Cannot create kthread2\n");
             goto r_device;
        }
 
        //Initialize the seqlock
        seqlock_init(&etx_seq_lock);
        
        pr_info("Device Driver Insert...Done!!!\n");
        return 0;
 
 
r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev,1);
        cdev_del(&etx_cdev);
        return -1;
}

/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
        kthread_stop(etx_thread1);
        kthread_stop(etx_thread2);
        device_destroy(dev_class,dev);
        class_destroy(dev_class);
        cdev_del(&etx_cdev);
        unregister_chrdev_region(dev, 1);
        pr_info("Device Driver Remove...Done!!\n");
}
 
module_init(etx_driver_init);
module_exit(etx_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple device driver - Seqlock");
MODULE_VERSION("1.28");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

denglin12315

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值