linux驱动程序中的并发控制-4(顺序自旋锁)-46

Posted 杨斌并

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux驱动程序中的并发控制-4(顺序自旋锁)-46相关的知识,希望对你有一定的参考价值。

顺序自旋锁


  • 顺序锁与读写自旋锁类似,只是为写锁赋予了更高的权限。在读写自旋锁中,读锁和写锁的优先级是相同的。
  • 当读锁获取读自旋锁时,写锁必须等待,直到临界区的代码执行完成,并释放读自旋锁为止,反之亦然。
  • 顺序锁在获取读锁的时候,仍然可以获取写锁,并继续执行写临界区中的
    代码。也就是说,写锁永远不会被读锁阻塞(当然,写锁仍然可以被写锁阻塞)。
  • 顺序锁需要定义seqlock_ t 变量。seqlock t结构体的代码如下(从seqlock_ _t 结构体的代码可以看出,顺序锁是通过自旋锁扩展而来的):
#include <linux/seqlock.h>
typedef struct {
	struct seqcount seqcount;
	spinlock_t lock;
} seqlock_t;
  • 当获取顺序锁(write_seqlock函数)时,seqlock_t.sequence 变量会被加1。释放写顺序锁(write_sequnlock函数)时,seqlock_t.sequence 变量仍然会被加1。因此,在获取顺序锁,但并未释放顺序锁的情况下(正在执行写临界区的代码),seqlock_t.sequence 变量的值是奇数。释放写顺序后,seqlock_t.sequence 变量的值是偶数。
  • 读取共享资源可以通过read_seqbegin 和read_seqretry 函数配合来完成。标准的执行读临界区的代码如下:
 unsigned seq;
    
    do {
        //获取顺序号,如果顺序号未被释放,read_seqbegin 函数会被阻塞
        seq = read_seqbegin(&seqlock);
        //如果单前的顺序号和seq一致则退出循环,否则继续共享资源
    } while (read_seqretry(&seqlock, seq));
  • read_seqbegin 函数用于获取顺序锁的顺序号,也就是seqlock_ t.sequence 变量的值。成功获取顺序号后,就会执行读临界区代码。
  • read_seqretry 函数用于判断在执行读临界区的过程中顺序号是否发生变化。从read_seqretry 函数的源代码也可以看出这一点。
static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
	smp_rmb();
	return __read_seqcount_retry(s, start);
}

static inline int __read_seqcount_retry(const seqcount_t *s, unsigned start)
{
   //比较通过read_seqbegin 获取的顺序号和当前的顺序号是否一致
	return unlikely(s->sequence != start);
}
  • 如果在执行临界区代码的过程中顺序号并未发生改变(也就是说在读过程中并未发生写操作),read_seqretry 函数返回0,否则返回非0。
  • 在执行读临界区的代码时,如果发生写操作,read_seqretry 函数会返回非0值, do…while循环将继续执行。当再次执行到read_seqbegin函数时,会检测顺序锁是否执行完,如果顺序锁未执行完, read_seqbegin 函数将被阻塞,直到顺序
    锁释放。也就是说,read_seqbegin 函数返回的顺序号一定是一个偶数(顺序锁已被释放)。

相关的api

函数描述
DEFINE_SEQLOCK(lock)定义和初始化顺序锁变量
void seqlock_init(seqlock_t *lock)初始化顺序锁变量
void write_seqlock(seqlock_t *lock)获取顺序锁
int write_tryseqlock(seqlock_t *lock)获取顺序锁。如果成功获取顺序锁,函数立刻返回非0值,否则立刻返回0
write_seqlock_irq_save(seqlock_t *lock, flags)= local_irq_save + write_seqlock
write_seqlock_irq(seqlock_t *lock)local_irq_disable + write_seqlock
write_seqlock_bh(seqlock_t *lock)local_bh_disable + write_seqlock
void write_sequnlock(seqlock_t *lock)释放顺序锁
write_sequnlock_irqrestore(seqlock_t *lock, flags)write_sequnlock + local_irq_restore
write_sequnlock_irq(seqlock_t * lock)write_sequnlock + local_irq_enable
write_sequnlock_bh(seqlock_t * lock)write_sequnlock + local_bh_enable
unsigned read_seqbegin(const seqlock_t * lock)获取顺序锁的顺序号。如果lock指定的顺序锁未被释放,该函数会等待顺序锁释放后才能获取顺序锁的顺序号
read_seqbegin_irqsave(const seqlock_t *lock, flags)local_irq_save + read_seqbegin
int read_seqretry(const seqlock_t *lock, unsigned iv)检测read _seqbegin 函数获取的顺序号是否与执行完读临界区的顺序号-致。如果不一致,返回非0值,否则返回0
read_seqretry_irqrestore(const seqlock_t *lock, iv, flags)read_seqretry + local_irq_restore

实例

  • seq_lock.c
//
//  seq_lock_test.c
//  seqlock_demo
//
//  Created by lianfei on 2021/7/12.
//

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/seqlock.h>
#include <linux/delay.h>
#define  DEVICE_NAME "seq_lock"
static DEFINE_SEQLOCK(seqlock);
static char *data = "data";

static ssize_t  demo_read(struct file *file, char __user * buf, size_t count, loff_t *ppos){
    
    unsigned long data_size = strlen(data);
    unsigned seq;
    
    do {
        //获取顺序号,如果顺序号未被释放,read_seqbegin 函数会被阻塞
        seq = read_seqbegin(&seqlock);
        
        if (copy_to_user(buf, (void *)data, data_size))
        {
                return -EINVAL;
        }
            
        mdelay(1000);
        //如果单前的顺序号和seq一致则退出循环,否则继续共享资源
    } while (read_seqretry(&seqlock, seq));
    
    printk("顺序号不一致退出循环\\n");
    
    return  data_size;
}

static ssize_t demo_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){
    struct timeval tv;
    do_gettimeofday(&tv);
    printk("write: start sec:%ld\\n", tv.tv_sec);
    write_seqlock(&seqlock);
    do_gettimeofday(&tv);
    mdelay(10000);
    printk("write: end sec:%ld\\n", tv.tv_sec);
    write_sequnlock(&seqlock);
    return count;
}

static int demo_release (struct inode *node, struct file *file){
    return 0;
}

static int demo_open (struct inode *node, struct file *file){
    return 0;
}

static struct file_operations dev_fops={
    .owner = THIS_MODULE,
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write
};

static struct miscdevice misc={
    .minor=MISC_DYNAMIC_MINOR,
    .name=DEVICE_NAME,
    .fops=&dev_fops
};


static int demo_init(void){

    int ret=misc_register(&misc);
    if(ret < 0 ){
        printk("atomic_init is error\\n");
        return -1;
    }
    printk("demo_init_success\\n");
    return ret;
}

static void demo_exit(void){
    printk("ademo_exit_success\\n");
    misc_deregister(&misc);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("binbing.Yang");
  • 测试脚本
  • seq_test.sh

#!/system/bin/sh
cat /dev/seq_lock &
echo data > /dev/seq_lock
  • 测试seq lock 驱动时首先读取/dev/seq lock 文件的内容(这时会调用dermo_ read 函数来延迟10秒),然后再向/dev/seq lock 文件写入数据。如果在日志中输出的两个毫秒数相等,说明write_ seqlock函数并未被阻塞。为了方便测试,本例提供了一个用于测试的脚本文件(seq_test.sh),

以上是关于linux驱动程序中的并发控制-4(顺序自旋锁)-46的主要内容,如果未能解决你的问题,请参考以下文章

linux驱动程序中的并发控制-4(顺序自旋锁)-46

linux驱动程序中的并发控制-3(读写自旋锁)-45

linux驱动程序中的并发控制-3(读写自旋锁)-45

linux驱动程序中的并发控制(自旋锁)-44

linux驱动程序中的并发控制-2(自旋锁)-44

Linux 设备驱动的并发控制 学习笔记