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

杨斌并 杨斌并     2022-12-02     267

关键词:

自旋锁(spin lock)

简介


  • 原子锁和自旋锁的使用范围
    原子操作是一种很好的避免竞态的方式,使用非常简单。但在某些方面却显得过于简单。例如,有很多数据需要被格式化,被添加到某些数据结构中,然后被分析处理。而这些操作又都要求是原子的。这种情况使用控制原子操作的原则变量很难处理或根本无法处理。因此,处理更复杂的并发和竞态就要使用自旋锁。
  • 自旋锁特点
    自旋锁从本质上讲就是保证代码段(也称为临界区)的操作是原子的。也就是说,如果要保证某段代码在执行期间不会被打断(原子操作),就要在代码段执行之前申请自旋锁,然后开始执行代码段中的代码,在执行完原子操作的代码段后,再释放自旋锁。在自旋锁被占用期间,任何进程将无法申请到自旋锁。而这些正在申请自旋锁的进程会在-一个小循环里不断扫描自旋锁,直到自旋锁被释放(空闲状态)才会被申请到。这种机制之所以叫做自旋锁,也就是指未申请到自旋锁时会不断循环(自己在那循环)来等待自旋锁的释放。
  • 自旋锁使用
    其实自旋锁从使用方法上看和原子操作有些类似,也是通过一个自旋锁类型(spinlock_ t)的变量控制自旋锁的申请和释放。所以从理解的角度可以将自旋锁想象成一一个变量。 当申请到自旋锁时,会为这个变量设置一一个标记,意思是说“我已经申请到自旋锁了,你们都等一会”, 当释放自旋锁时,会为这个变量设置另外-一个空闲标记,意思是说“自旋锁我已经用完了,你们可以用了”。

使用


自旋锁的数据结构的定义

typedef struct spinlock 
	union 
		struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct 
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		;
#endif
	;
 spinlock_t;
  • 其本质上是一个整数值(对该数值的操作需要保证原子性),该数值表示spin lock是否可用。初始化的时候被设定为1。当thread想要持有锁的时候调用spin_lock函数,该函数将spin lock那个整数值减去1,然后进行判断,如果等于0,表示可以获取spin lock,如果是负数,则说明其他thread的持有该锁,本thread需要spin。

定义自旋锁的两种方式:

  • 定义自旋锁相关的头文件
//里面封装了各种api
#include<linux/spinlock.h>
//结构体
#include<linux/spinlock_types.h>
  • 动态的
spinlock_t lock;
spin_lock_init (&lock);
  • 静态的
DEFINE_SPINLOCK(lock);

获取自旋锁

使用spin lock 函数可以获取自旋锁,代码如下:

spin_ lock(&lock) ;
  • 如果使用spin_lock函数成功获取自旋锁,spin_lock函数会立即返回。如果未获取自旋锁,spinlock函数会被阻塞(在那自旋),直到可以获取自旋锁才返回。

如果想不管是否成功获取自旋锁都立刻返回,可以使用spin trylock 函数,代码如下:

  if (spin_trylock(&lock))
            printk("spin trylock available \\n");
   else 
            printk("spin trylock unavailable \\n");
  
  • 如果获取到锁,则返回非0的值,如果没有则返回0

相关的api

函数描述
DEFINE_SPINLOCK(lock)定义和初始化自旋锁变量
void spin_lock_init(spinlock_t *lock)初始化自旋锁变量
void spin_lock(spinlock_t *lock)获取自旋锁。如果无法获取自旋锁,则不断自旋(循环)
int spin_trylock(spinlock_t *lock)来检测自旋锁是否可用获取自旋锁。如果成功获取自旋锁,则立刻返回非0值,如果无法获取自旋锁,则立刻返回0 (并不进行自旋,也就是说该函数不会被阻塞)
void spin_unlock(spinlock_t *lock)释放自旋锁
void spin_lock_irq(spinlock_t *lock)获取自旋锁,并禁止中断。相当于spin _lock + local _irq_disable
int spin_trylock_irq(spinlock_t *lock)获取自旋锁,并禁止中断,果成功获取自旋锁,则立刻返回非0值,如果无法获取自旋锁,则立刻返回0,相当于spin_ trylock + local_irq_disable
void spin_unlock_irq(spinlock_t *lock)释放自旋锁,并允许中断。相当于spin_unlock + local_irq_enable
void spin_lock_bh(spinlock_t *lock)获取自旋锁,并关闭底半部。相当于spin_lock + local_bh_disable
void spin_trylock_bh(spinlock_t *lock)获取自旋锁,并关闭底半部。如果成功获取自旋锁,立刻返回非0値,否則立刻返回0。相当于spin trylock + local_ bh_ disable
void spin_unlock_bh(spinlock_t *lock)释放自旋锁,并打开底半部。相当于spin _unlock +local_bh_enable
int spin_is_locked(spinlock_t *lock)如果自旋锁已被占用,返回非0值,否则返回0

自旋锁实际上是忙等锁,当锁被占用时,再尝试获取自旋锁时,CPU 会一直循环执行“测试自旋锁”动作,直到自旋锁被释放,并成功获取自旋锁为止。CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁时间极短的情况下,使用自旋锁才是合理的。当临界区很大(要执行的代码很多)或有共享设备时,需要较长时间占用锁,使用自旋锁就会降低系统性能。

不恰当地使用自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获取这个自旋锁(也就是说,调用spinlock函数获取自旋锁后,在未释放自旋锁之前又调用了spin_lock 函数再次获取自旋锁),则该CPU将死锁。此外,如果进程获取自旋锁之后被阻塞,也有可能导致死锁的发生。copy_from_user,copy_to_user和kmalloc等函数都有可能引起阻塞,因此在自旋锁的占用期间最好不要调用这些函数。

代码

  • spin_lock_test.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/uaccess.h>
#include<linux/fs.h>
#include<linux/spinlock.h>
#include<linux/spinlock_types.h>
#include<linux/miscdevice.h>
#include<linux/delay.h>

#define DEVICE_NAME "spin_lock"
static char *data = "read\\n";
static int flag = 1;
//定义一个自旋锁
static DEFINE_SPINLOCK(lock);

static int spin_lock_open(struct inode *node,struct file *file)

    printk("spin_lock_open \\n");


    return 0;



static int spin_lock_release(struct inode *node,struct file *file)
    printk("spin_lock_release \\n");

    return 0;


static int spin_lock_read(struct file * file, char __user * buf, size_t size, loff_t * ppos)
    int data_size = strlen(data);
    if (copy_to_user(buf,(void *)data, data_size))
    
        printk("copy_to_user is error\\n");
        return -EINVAL;
    

    if (flag)
    
       flag = 0;
       if (spin_trylock(&lock))
       

           mdelay(20000);
           spin_unlock(&lock);
       else

           return -EBUSY;
       

       return size;
       
    else
        flag = 1;
        return 0;
    


static int spin_lock_write(struct file *file, const char __user *buf, size_t size, loff_t * ppos)

    char write_data[10];
    memset(write_data,0,10);
    if (copy_from_user(write_data, buf, size))
    
        printk("copy_from_user is error\\n");
        return -EINVAL;
    

    if (strcmp("lock\\n", write_data) == 0)
       
        printk("write_data is lock\\n");
        spin_lock(&lock);
        printk("spin lock available \\n");
        spin_unlock(&lock);
    else if (strcmp("trylock\\n", write_data) == 0)
    
        printk("write_data is trylock\\n");
        if (spin_trylock(&lock))
        
            printk("spin trylock available \\n");
            spin_unlock(&lock);
        else 

            printk("spin trylock unavailable \\n");
            return -EBUSY;

        
        
    

    return size;


static struct file_operations dev_fops=
    .owner = THIS_MODULE,
    .open = spin_lock_open,
    .release = spin_lock_release,
    .read = spin_lock_read,
    .write = spin_lock_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");

下面的操作都在20秒内

  • 使用一个终端 在设备下执行

cat /dev/spin_lock

  • 使用另外的两个或者多个终端分别执行如下

echo lock > /dev/spin_lock

echo trylock > /dev/spin_lock

  • 当cat /dev/spin_lock时 会收到如下日志

设备打开
  • echo trylock > /dev/spin_lock 和 echo trylock > /dev/spin_lock 后 会收到一下日志

spin_trylock 不会阻塞下面的代码
  • 20 秒后会收到也就是 当cat /dev/spin_lock 读取到这个节点输入read 时,收到一下日志

spin_lock 会阻塞下面的代码

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

读写自旋锁自旋锁不管读写,都只允许同时只有一个执行单元可以获取自旋锁,即便有多个单元同时读取临界区资源也会被锁住。读写自旋锁,将临界区的读写操作分开,多个执行单元可以同时获取一个读自旋锁&... 查看详情

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

顺序自旋锁顺序锁与读写自旋锁类似,只是为写锁赋予了更高的权限。在读写自旋锁中,读锁和写锁的优先级是相同的。当读锁获取读自旋锁时,写锁必须等待,直到临界区的代码执行完成,并释放读自旋锁... 查看详情

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

顺序自旋锁顺序锁与读写自旋锁类似,只是为写锁赋予了更高的权限。在读写自旋锁中,读锁和写锁的优先级是相同的。当读锁获取读自旋锁时,写锁必须等待,直到临界区的代码执行完成,并释放读自旋锁... 查看详情

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

读写自旋锁自旋锁不管读写,都只允许同时只有一个执行单元可以获取自旋锁,即便有多个单元同时读取临界区资源也会被锁住。读写自旋锁,将临界区的读写操作分开,多个执行单元可以同时获取一个读自旋锁&... 查看详情

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

顺序自旋锁顺序锁与读写自旋锁类似,只是为写锁赋予了更高的权限。在读写自旋锁中,读锁和写锁的优先级是相同的。当读锁获取读自旋锁时,写锁必须等待,直到临界区的代码执行完成,并释放读自旋锁... 查看详情

linux驱动程序中的并发控制-5(信号量(semaphore))-47(代码片段)

信号量(semaphore)信号量是用于保护临界区的一种常用方法,它的使用方式与自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但与自旋锁不同的是,在未获取信号量时,进程不会... 查看详情

linux驱动程序中的并发控制-5(信号量(semaphore))-47(代码片段)

信号量(semaphore)信号量是用于保护临界区的一种常用方法,它的使用方式与自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但与自旋锁不同的是,在未获取信号量时,进程不会... 查看详情

并发控制

目录自旋锁完成量自旋锁 《Linux设备驱动开发详解》=>7.5自旋锁spinlock:在rtlinux(配置了PREEMPT_RT)的时候可能会被抢占         (实际底层可能是使用支持PI(优先级翻转)的mutex)。raw_spinlock:即便是配置了PREEMPT_RT也要... 查看详情

linux设备驱动的并发控制学习笔记(代码片段)

文章目录并发和竞态编译乱序和执行乱序并发控制机制中断屏蔽原子操作整型原子操作位原子操作自旋锁自旋锁的使用读写自旋锁顺序锁读-复制-更新信号量互斥体完成量并发和竞态并发:多个执行单元同时、并行被执行。... 查看详情

linux设备驱动中的并发

参考技术A并发就是多个执行单元或多个进程并行执行,而这多个执行单元对资源进行共享,比如访问同一个变量或同一个硬件资源,这个时候就很容易出现竞态(说简单点就是竞争同一个”女朋友”)。为了处理并发带来的问题,... 查看详情

linux驱动之并发与竞争(代码片段)

...同一片内存区域,这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话可能会导致系统崩溃。linux存在以下并发访问:①、多线程并发访问,Linux是多任务(线程)的... 查看详情

linux驱动程序中的并发控制-6(读写信号量)-48(代码片段)

读写信号量读写信号量和信号量的关系与读写自旋锁和自旋锁的关系类似。读信号量和写信号量是互斥的,但允许N个读执行单元同时访问共享资源(同时获取读信号量),而最多只允许有一个写单元获取写信号量。读写信... 查看详情

linux源码解析16-linux内核常用锁机制总结(代码片段)

首先看看Linux内核中的并发场景;单CPU多进程系统,产生并发访问因素有:中断处理程序可以打断软中断,tasklet和进程上下文;软中断和tasklet之间不会并发,但可以打断进程上下文;在支持抢占的内核中,进程上下文之间会并发... 查看详情

linux驱动开发原子操作自旋锁信号量互斥体(代码片段)

...片内存区域的情况,多个任务可能会相互覆盖掉内存中的数据,造成内存数据混乱。Linux系统并发主要原因多线程并发访问抢占式并发访问中断程序并发访问多核间并发访问通常需要对全局变量、设备结构体这些共享资源... 查看详情

linux并发与竞争(原子操作自旋锁信号量互斥体)(代码片段)

目录并发与竞争原子操作原子操作简介原子整形操作API函数原子位操作API函数自旋锁自旋锁简介自旋锁API函数其他类型的锁自旋锁使用注意事项信号量信号量简介信号量API函数互斥体互斥体简介互斥体API函数Linux是一个多任务操... 查看详情

linux驱动程序中的并发控制-6(读写信号量)-48(代码片段)

读写信号量读写信号量和信号量的关系与读写自旋锁和自旋锁的关系类似。读信号量和写信号量是互斥的,但允许N个读执行单元同时访问共享资源(同时获取读信号量),而最多只允许有一个写单元获取写信号量。读写信... 查看详情

linux驱动程序中的并发控制-6(读写信号量)-48(代码片段)

读写信号量读写信号量和信号量的关系与读写自旋锁和自旋锁的关系类似。读信号量和写信号量是互斥的,但允许N个读执行单元同时访问共享资源(同时获取读信号量),而最多只允许有一个写单元获取写信号量。读写信... 查看详情

linux并发与竞争实验(代码片段)

...试实验程序编写运行测试在上一章中我们学习了Linux下的并发与竞争,并且学习了四种常用的处理并发和竞争的机制:原子操作、自旋锁、信号量和互斥体。本章我们就通过四个实验来学习如何在驱动中使用这四种机制。... 查看详情