linux设备驱动编程之并发控制

1.什么是并发?

(1)什么是并发?
所谓的并发控制便是多个进程同时进行,并行的对内核资源(全局变量,静态变量等)访问而出现竞态。竞态简单的说就是两个或两个以上的进程同时访问一个资源,同时引起资源的错误.
例如:如下图,按照进程1代码逻辑设计意图,赋值进程变量a为100,并且执行代码x,但是就在这时候进程1不知道进程2也在并发访问全局变量a并赋值为200,这样以来那么进程1的代码并不会执行.程序出现意想不到的错误.而这就是并发访问导致的错误.
在这里插入图片描述

2.并发的原因?

并发的原因有种,主要包含如下
(1)对称多处理器(smp)多个cpu
如下图,当多个cpu同时访问一个全局变量的时候便会出现静态
在这里插入图片描述
(2)单个cpu抢占其他进程
当一个进程资源在自己的时间戳片消耗完成或者被其他高优先级进程抢占后访问其共享资源变回类似与smp.因为linux内核有着"宏观串行,微观串行"的执行流程.
在这里插入图片描述
(3)中断
当进程正在访问共享资源的时候,由于中断打断了,并且中断服务程序也对共享资源进行访问导致了竞态.
(4)编译乱序以及执行乱系(这个后续比较复杂,后续有时间再做笔记解说)

3.并发控制的方法

为了避免上面的并发访问导致了竟态,我们控制并发的方法很简单,当一个进程访问共享资源之前,需要加一把锁或者一个访问状态,告诉其他进程当前进程正在访问,需要排队等待,等当前进程访问共享资源完成的时候再释放该锁或者清空该访问状态,其他进程才可以访问.y因此并发控制的方法有:
(1)中断屏蔽(针对中断造成的并发竞态)
local_irq_disable()//失能中断
共享资源临界区…
local_irq_enable()//使能中断

local_irq_save(flags);
共享资源临界区…
local_irq_restore(flags);
该操作是除了屏蔽中断外,还保留了屏蔽中断前的状态,等再次打开中断时恢复屏蔽前的状态.

local_bh_disable()//屏蔽底半部中断
local_bh_enable()//打开底半部中断
注意:屏蔽中断只是屏蔽本cpu的中断,无法屏蔽smp其他cpu上的中断.

(2)原子操作(针对位以及整数的全局变量访问)
原子操作基本上都是利用ldr,str,ldrex,strex,DMB,DSB等操作.具体后续可以说明利用汇编指令以及汇编内嵌实现原理.这里我们直说接口应用.

//定义原子变量
atomic_t *v = ATOMIC_INIT(0);

//设置原子变量的值为i
void atomic_set(atomic_t *v,int  i)

//获取原子的值
atomic_read(atomic *v)

//原子值增减i
void atomic_add(int i,atomic_t *v);
void atomic_sub(int i,atomic_t *v);

//原子自增减1
atomic_inc(atomic_t *v);
atomic_dec(atomic_t *v);

//测试以及操作
atomic_inc_and_test(atomic_t *v);//原子自增1后,原子的值为0则返回true,否则返回false
atomic_dec_and_test(atomic_t *v);//原子自减1后,原子的值为0则返回true,否则返回false
atomic_sub_and_test(int i,atomic_t *v);//原子增加i后,原子的值为0则返回true,否则返回false注意:
//没有增加i后并测试的函数
位原子操作
//设置nr位
viod bit_set(unsigned long, void *addr);
//清除nr位
void clear_bit(unsigned long,void *addr);
//对第nr位取反
void change_bit(unsigned long,void *addr);

//返回addr的第nr位
test_bit(unsigned long,void *addr);

//测试并操作位
void test_and_set_bit(unsigned long,void  *addr);//返回addr的nr位后再设置nr位
void test_and_clear_bit(unsigned long,void *addr);//返回addr的nr位后再清除nr位
void test_and_change_bit(unsigned long,void *addr);//返回addr的nr位后再取反nr位

(3)自旋锁
自旋锁(Spin Lock)通过原子操作,访问临界资源的时候执行操作测试并设置(Test-AndSet)某个内存变量,如果测试结果表示空闲,那么程序便继续执行并获得该锁,假如测试结果表示被占用,那么程序需要不断重新操作测试并设置(Test-AndSet)某个内存变量,直到空闲为止.也就是相当于程序在不停循环等待空闲状态.

spinlock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
临界区域
spin_unlock(&lock)

//自旋锁能够防止单cpu抢占或者其他cpu并发访问共享资源,但是不能屏蔽中断.为此有了能屏蔽中断以//及cpu抢占或者多cpu并发访问的接口
spin_lock_irq(spinlock_t *lock);
spin_unlock_irq(spinlock_t *lock);

spin_lock_irqsave(spinlock_t *lock);
spin_unlock_irqrestore(spinlock_t *lock);

spin_lock_bh(spinlock_t *lock);
spin_unlock_bh(spinlock_t *lock);

还有一种锁,为了不让cpu资源在空转
spintrylock(&lock)
该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回true,否则立即返回false,实际上不“在原地打转”

(4)互斥量

struct mutex mutex;
mutex_init(&mutex);
mutex_lock(&mutex);
临界区...
mutex_unlock(&mutex);

(5)信号量与完成量,但是一般这两是用来进程同步用的.

4.区分以及总结

中断主要是屏蔽中断服务程序访问共享资源临界区的时候用的,但是其不能用防止单cpu抢占以及多cpu并发访问.spin_lock_irq可以实现禁止所有并发可能性.
那么很多时候我们应用场景如下:
(1)原子操作:共享资源仅仅为bit或者整数变量的话
(2)自旋锁:中断服务程序访问共享资源的话,或者共享资源访问时间十分短暂
(3)互斥量:共享资源当中有阻塞的话,禁止用自旋锁,要用mutex互斥量.因为一旦阻塞不能释放锁而另一进程需要访问的时候那么整个cpu便会不断在自旋,很容易导致内核崩溃.享资源访问开销比较大.

5.代码实例

//原子操作与互斥量

#include "globalmem.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/completion.h>
#include <linux/wait.h>
#include <linux/signal.h>
#include <linux/sched/signal.h>

#define GLOBALMEM_BUFFER_SIZE 50
#define GLOBALMEM_MAJOR 255

struct globalmem_dev
{
	dev_t devno;
	struct cdev dev;
	unsigned char globalmem_buf[GLOBALMEM_BUFFER_SIZE];
	struct mutex globalmem_mutex;
	//wait_queue_head_t r_wait;
	//wait_queue_head_t w_wait;
	//unsigned long current_len;
	atomic_t v;
	//struct completion globalmem_complet;
};

struct globalmem_dev *globalmem_devp = NULL;

static int globalmem_open(struct inode *inode, struct file *filp)
{
	if(!globalmem_devp)
		return -ENODEV;

	//判断是否打开过一次,打开过一次后v的值为0
	if(!atomic_dec_and_test(&globalmem_devp->v)){
		//判断的时候自减一次,没有成功打开必须再加1才能维持原来的值.
		atomic_inc(&globalmem_devp->v);
		return -EBUSY;
	}

	filp->private_data = globalmem_devp;

	return 0;
}

static int globalmem_release(struct inode *inode, struct file *filp)
{
	if(filp->private_data)
		filp->private_data = NULL;

	if(!globalmem_devp)
		return -ENODEV;

	//释放的时候,恢复原来1的值
	atomic_inc(&globalmem_devp->v);

}

static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	int ret = 0;
	unsigned long count = size;
	struct globalmem_dev *dev = filp->private_data;
	long long p = *ppos;

	//判断opps是否大于最大值
	if(p >= GLOBALMEM_BUFFER_SIZE)
		return 0;

	if(count > GLOBALMEM_BUFFER_SIZE - p)
		count = GLOBALMEM_BUFFER_SIZE -p;

	//互斥量
	mutex_lock(&dev->globalmem_mutex);
	if(copy_to_user(buf,dev->globalmem_buf,count)){
		ret = -EFAULT;
	}else{
		*ppos = +count;
		ret = count;
	}
	mutex_unlock(&dev->globalmem_mutex);
	return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
	int ret = 0;
	unsigned long count = size;

	struct globalmem_dev *dev = filp->private_data;
	long long p = *ppos;

	if(p >= GLOBALMEM_BUFFER_SIZE)
		return 0;

	if(count > GLOBALMEM_BUFFER_SIZE - p)
		count = GLOBALMEM_BUFFER_SIZE - p;

	mutex_lock(&dev->globalmem_mutex);
	if(copy_from_user(dev->globalmem_buf,buf,count)){
		ret = -EFAULT;
	}else{
		*ppos += count;
		ret = count; 
	}
	//释放互斥来量
	mutex_unlock(&dev->globalmem_mutex);
	return ret;
}

struct file_operations globalmem_fops = {
	.open = globalmem_open,
	.read = globalmem_read,
	.write = globalmem_write,
	.release = globalmem_release
};



static int __init globalmem_init(void)
{
	int ret ;

	globalmem_devp = (struct globalmem_dev *)kzalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
	if(!globalmem_devp)
		return -ENOMEM;

	globalmem_devp->devno = MKDEV(GLOBALMEM_MAJOR,0);

	if(GLOBALMEM_MAJOR)
		ret = register_chrdev_region(globalmem_devp->devno,1,"globalmem");
	else
		ret = alloc_chrdev_region(&globalmem_devp->devno,0,1,"globalmem");
	if(ret < 0)
		return ret;

	cdev_init(&globalmem_devp->dev,&globalmem_fops);
	globalmem_devp->dev.owner = THIS_MODULE;
	ret = cdev_add(&globalmem_devp->dev,globalmem_devp->devno,1);
	if(ret < 0 ){
		unregister_chrdev_region(globalmem_devp->devno,1);
		kfree(globalmem_devp);
		return ret;
	}
	mutex_init(&globalmem_devp->globalmem_mutex);
	//定义原子的值为1
	atomic_t v_avalible = ATOMIC_INIT(1);
	globalmem_devp->v = v_avalible;


	return 0;
}

static void __exit globalmem_exit(void)
{
	cdev_del(&globalmem_devp->dev);
	unregister_chrdev_region(globalmem_devp->devno,1);
	kfree(globalmem_devp);
}

module_init(globalmem_init);
module_exit(globalmem_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("gentle");

其实上面打开一次就不需要互斥量了,如果允许打开一次的话,那么其他进程或者线程都不会并发访问buf缓冲区,那么共享资源也无须加互斥量了.但是主要是演示了原子操作以及互斥量的用法.例子为什么不使用spinlock_t呢?答案很简单copy_to_user或者copy_from_user都是有可能阻塞的操作.本例子的代码完全是参考来自宋宝华老师的linux设备驱动编程的并发控制实例.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值