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

Posted 杨斌并

tags:

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

自旋锁(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驱动程序中的并发控制(自旋锁)-44的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

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

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