驱动程序中的原子变量、自旋锁、互斥锁mutex的介绍

在介绍这几个概念之前,我们先了解一下上下文

一、上下文和并发场合

执行流:有开始有结束总体顺序执行的一段代码 又称上下文

应用编程:任务上下文 内核编程:

  1. 任务上下文:五状态 可阻塞 a. 应用进程或线程运行在用户空间 b. 应用进程或线程运行在内核空间(通过调用syscall来间接使用内核空间) c. 内核线程始终在内核空间

  2. 异常上下文:不可阻塞 中断上下文

竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态

共享资源:可能会被多个任务同时使用的资源

临界区:操作共享资源的代码段

为了解决竞态,需要提供一种控制机制,来避免在同一时刻使用共享资源,这种机制被称为并发控制机制

并发控制机制分类:

  1. 原子操作类

  2. 忙等待类

  3. 阻塞类

二、原子变量

原子变量:

       存取不可被打断的特殊整型变量,他是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰,从而避免竞态条件(race condition)和死锁(deadlock)等问题。原子变量可以看作是一种特殊的类型,它具有类似于普通变量的操作,但是这些操作都是原子级别的,即要么全部完成,要么全部未完成。

在驱动中的一些基本使用函数:   

  a.设置原子量的值

  •  void atomic_set(atomic_t *v,int i); //设置原子量的值为i ,需在驱动初始化中将其打开

   b.获取原子量的值

  • atomic_read(atomic_t *v); //返回原子量的值

   c.原子变量加减

  •  void atomic_add(int i,atomic_t *v);//原子变量增加i
  •  void atomic_sub(int i,atomic_t *v);//原子变量减少i

   d.原子变量自增自减

  •  void atomic_inc(atomic_t *v);//原子变量增加1
  •  void atomic_dec(atomic_t *v);//原子变量减少1

   e.操作并测试:运算后结果为0则返回真,否则返回假

  • int atomic_inc_and_test(atomic_t *v); //返回原子变量自增后的值
  • int atomic_dec_and_test(atomic_t *v);//返回原子变量自减后的值
  • int atomic_sub_and_test(int i,atomic_t *v);//返回原子变量减去i后的值

原子位操作方法:

a.设置位 void set_bit(nr, void *addr); //设置addr的第nr位为1

b.清除位 void clear_bit(nr , void *addr); //清除addr的第nr位为0

c.改变位 void change_bit(nr , void *addr); //改变addr的第nr位为1

d.测试位 void test_bit(nr , void *addr); //测试addr的第nr位是否为1

适用场合:共享资源为单个整型变量的互斥场合

代码展示:在同一时间只能打开一次的代码

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<asm/atomic.h>
#include<linux/wait.h>
#define BUF_LEN 100
int major = 12;//主设备号
int minor = 0; //次设备号
int openonce_num = 1;//设备数量

struct openonce_dev{
	struct cdev mydev;//自定义字符驱动结构体

	atomic_t openflag;//初始化原子变量的标志位,1为程序能开,0不能开

}pmydev;

int openonce_open(struct inode *pnode,struct file *pfile){
	
	pfile->private_data = (void *)(container_of(pnode->i_cdev,struct openonce_dev,mydev));
	struct openonce_dev *gmydev = NULL;
	gmydev = (struct openonce_dev*)pfile->private_data;

    /*在程序被打开时判断标志位的值,
    因为此函数运算结果为0时返回真,我们设定这个标志位为1时程序可以被打开,
    所以当其自减后的值等于0时表示标志位为1,程序可以被打开,这时标识符的值由1减为0,
    所以之后此程序将不能再被打开,若原来的状态就为不能打开,则函数的运算值0-1=-1返回假,
    就会执行else中的语句,但此时的标志位已经变成了-1而不是我们所设定的判断值0和1
    所以需要执行自增函数使其变回0来继续表示程序无法被打开。*/
	if(atomic_dec_and_test(&gmydev->openflag)){
		return 0;
	}else{
		atomic_inc(&gmydev->openflag);
		printk("can not open\n");
		return -1;
	
	}

}

    //程序结束时只需要将标志位的值设定为可以打开即可
int openonce_close(struct inode *pnode,struct file *pfile){
	struct openonce_dev *gmydev = (struct openonce_dev *)pfile->private_data;
	atomic_set(&gmydev->openflag,1);
	return 0;
}



struct file_operations myops = {
	.owner = THIS_MODULE,//该结构体对象属于哪个内核模块
	.open = openonce_open,//打开设备
	.release = openonce_close,//关闭设备 

};

int __init openonce_init(void){
	/* 将主设备号和次设备号组合成32位完整的设备号*/
	dev_t devno = MKDEV(major,minor);
	int ret = 0;
	//申请设备号,方式为手动申请
	ret = register_chrdev_region(devno,openonce_num,"openonce");
	//手动申请失败则使用动态申请
	if(ret != 0){
		ret = alloc_chrdev_region(&devno,minor,openonce_num,"openonce");
		if(ret != 0){
			printk("get chrdev failed\n");
			return -1;
		}
		major = MAJOR(devno);//自动申请的主设备号需要重新赋值

	}
	
		//给struct cdev对象制定操作函数集
		cdev_init(&pmydev.mydev,&myops);

		//将其添加到内核对应的数据结构里
		pmydev.mydev.owner = THIS_MODULE;
		
		//将指定的字符设备添加到内核中
		cdev_add(&pmydev.mydev,devno,openonce_num);
		
        //将标志位的值初始化为可以打开
		atomic_set(&pmydev.openflag,1);
}

void __exit openonce_exit(void){
	//获取设备的32位设备号
	dev_t devno = MKDEV(major,minor);
	//移除对应的设备
	cdev_del(&pmydev.mydev);
	//将申请的设备号释放
	unregister_chrdev_region(devno,openonce_num);
	
}

module_init(openonce_init);
module_exit(openonce_exit);

三、自旋锁:基于忙等待的并发控制机制

自旋锁:他的机制就是他的名字——自旋,也就是一直在获得自旋锁的位置停留直到他被释放。

a.定义自旋锁 spinlock_t lock;

b.初始化自旋锁 spin_lock_init(spinlock_t *);

c.获得自旋锁 spin_lock(spinlock_t *); //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放

spin_trylock(spinlock_t *); //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”

d.释放自旋锁 spin_unlock(spinlock_t *);

#include <linux/spinlock.h>
定义spinlock_t类型的变量lock
spin_lock_init(&lock)后才能正常使用spinlock
​
​
spin_lock(&lock);
临界区
spin_unlock(&lock);

适用场合:

  1. 异常上下文之间或异常上下文与任务上下文之间共享资源时

  2. 任务上下文之间且临界区执行时间很短时

  3. 互斥问题

用自旋锁实现在同一时间只能被打开一次的程序:

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<asm/atomic.h>
#include<linux/wait.h>
#define BUF_LEN 100
int major = 12;//主设备号
int minor = 0; //次设备号
int openonce_num = 1;//设备数量

struct openonce_dev{
	struct cdev mydev;//自定义字符驱动结构体

	spinlock_t openflag;

	int oo;//自定义自旋锁的标志位,1能开0不能开

}pmydev;


int openonce_open(struct inode *pnode,struct file *pfile){
	
	pfile->private_data = (void *)(container_of(pnode->i_cdev,struct openonce_dev,mydev));
	struct openonce_dev *gmydev = NULL;
	gmydev = (struct openonce_dev*)pfile->private_data;

    //打开之前上锁,然后判断自定义标志位的值,若能开则将标志位设置为0,不能开则打印提示语句
	spin_lock(&gmydev->openflag);
	if(gmydev->oo){
		spin_unlock(&gmydev->openflag);
		gmydev->oo = 0;
		return 0;
	}else{
		spin_unlock(&gmydev->openflag);
		printk("can not open\n");
		return -1;
	
	}

}
int openonce_close(struct inode *pnode,struct file *pfile){
	struct openonce_dev *gmydev = (struct openonce_dev *)pfile->private_data;
	gmydev->oo = 1;
	spin_unlock(&gmydev->openflag);
	return 0;
}



struct file_operations myops = {
	.owner = THIS_MODULE,//该结构体对象属于哪个内核模块
	.open = openonce_open,//打开设备
	.release = openonce_close,//关闭设备 

};

int __init openonce_init(void){
	/* 将主设备号和次设备号组合成32位完整的设备号*/
	dev_t devno = MKDEV(major,minor);
	int ret = 0;
	//申请设备号,方式为手动申请
	ret = register_chrdev_region(devno,openonce_num,"openonce");
	//手动申请失败则使用动态申请
	if(ret != 0){
		ret = alloc_chrdev_region(&devno,minor,openonce_num,"openonce");
		if(ret != 0){
			printk("get chrdev failed\n");
			return -1;
		}
		major = MAJOR(devno);//自动申请的主设备号需要重新赋值

	}
	
		//给struct cdev对象制定操作函数集
		cdev_init(&pmydev.mydev,&myops);

		//将其添加到内核对应的数据结构里
		pmydev.mydev.owner = THIS_MODULE;
		
		//将指定的字符设备添加到内核中
		cdev_add(&pmydev.mydev,devno,openonce_num);
		
        //初始化自旋锁并将自定义标志位设置为可以打开
		pmydev.oo = 1;
		spin_lock_init(&pmydev.openflag);
}

void __exit openonce_exit(void){
	//获取设备的32位设备号
	dev_t devno = MKDEV(major,minor);
	//移除对应的设备
	cdev_del(&pmydev.mydev);
	//将申请的设备号释放
	unregister_chrdev_region(devno,openonce_num);
	
}

module_init(openonce_init);
module_exit(openonce_exit);

四、互斥锁:基于阻塞的互斥机制

a.初始化

  • 在自定义设备结构体中定义:struct mutex my_mutex
  • 在初始化函数中初始化: mutex_init(&my_mutex);

b.获取互斥体 void mutex_lock(struct mutex *lock);

c.释放互斥体 void mutex_unlock(struct mutex *lock);

  1. 定义对应类型的变量

  2. 初始化对应变量

适用场合:任务上下文之间且临界区执行时间较长时的互斥问题

五、选择并发控制机制的原则

  1. 不允许睡眠的上下文需要采用忙等待类,可以睡眠的上下文可以采用阻塞类。在异常上下文中访问的竞争资源一定采用忙等待类。

  2. 临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。

  3. 中断屏蔽仅在有与中断上下文共享资源时使用。

  4. 共享资源仅是一个简单整型量时用原子变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值