Linux系统驱动(六)内核中并发和竞态的解决方法

一、概念

(一)概念

1. 什么是竞态

竞态:当多个进程通过驱动同时访问临界资源时,竞态就会产生。

  • 注:什么是临界资源
  • 多个进程可以同时访问的相同的可以产生竞态的代码区,就称之为临界资源

2. 产生竞态的根本原因

对于单核处理器,如果内核支持抢占(一个进程(高优先级)抢走了另一个进程(低优先级)的时间片),就会产生竞态

对于多核处理器,核与核之间本身就是并行执行,会产生竞态

中断和进程间也会产生竞态,中断优先级高于进程

  • 注:arm架构下,中断和中断之间不允许嵌套,因此中断和中断之间会产生竞态 是错误的说法
  • M4核允许中断嵌套

(二)解决竞态的方法

中断屏蔽

中断屏蔽就是将中断临时关闭掉,只针对单核有效

抢占就是通过中断实现的,关闭中断就不会再存在抢占

中断屏蔽保护的临界区比较小,中断屏蔽的时间要 尽可能短,即不能执行任何延时,耗时,休眠操作。
如果时间长就会导致用户数据丢失或者内核崩溃。因此一般不使用这种方法

local_irq_disable()  //关中断
//临界区
local_irq_enable()   //开中断
自旋锁

当一个进程获取自旋锁后,如果此时有另一个进程也想获取这把锁,后一个进程处于自旋状态

自旋状态会持续消耗CPU资源
自旋锁保护的临界区域要尽可能的小,不能够有延时、耗时、休眠操作,此外不能有copy_to_user和copy_from_user、schedule函数等类似可以自动放弃cpu的函数
可以在中断上下文使用

信号量

当一个进程获取到信号量之后,如果此时有另一个进程想要获取这个信号量,后一个进程将进入休眠状态。

休眠状态不会消耗CPU资源

互斥体

当一个进程获取到互斥体之后,如果此时有另外一个进程也想获取这个互斥体,互斥体在获取不到资源时,会适当等一会再进入休眠状态,如果再等的期间有资源了就不会休眠了。
互斥体本身具备排它性,所以互斥体又叫做排它锁。

原子操作

原子操作主要强调的原子性,原子性就是将整个操作看成一个不可分割的整体,如果执行就必须一次性执行完成,过程中不能被打断。

二、自旋锁

(一)概念

当一个进程获取自旋锁后,如果此时有另一个进程也想获取这把锁,后一个进程处于自旋状态(相当于后一个进程会一直询问自旋锁是否解锁)

  • 注:自旋状态会消耗CPU资源
    因此更适用于临界区比较小的情况

(二)特点

  1. 自旋锁是针对多核设计的
  2. 自旋锁的自旋状态需要消耗CPU资源
  3. 自旋锁保护临界区比较小,里面不能有延时、耗时或者休眠的操作,在自旋锁的临界区中也不能有copy_to_user、copy_from_user等操作
  • 注:copy_to_user、copy_from_user函数是允许被打断的,因此可能会在某个时间片放弃CPU,而使用自旋锁目的就是在上锁期间一直持有CPU,不允许被人打断,因此自旋锁的临界区内不允许加类似的会放弃CPU的函数
  1. 自旋锁可能会产生死锁,自旋锁死锁会导致进程一直占用CPU,可能会造成死机
  • 注:
  • 多线程死锁,会处于休眠态,其他进程可以正常运行
  • 自旋锁死锁,不会处于休眠,CPU一直被占用,会产生死机的状态
  1. 自旋锁可以工作在中断上下文(进程->中断->进程),可以在中断处理函数中加锁
  • 补:
  • 进程上下文:进程状态切换的过程
  • 中断上下文:有中断参与的状态的切换就称为中断上下文
  1. 自旋锁在上锁前会关闭抢占(并没有把所有中断关闭)
    即使高优先级进程进入也不会抢占CPU

(三)自旋锁API

1. 定义自旋锁
	spinlock_t lock;
2. 初始化锁
	spin_lock_init(&lock)
3. 上锁
	void spin_lock(spinlock_t *lock); 		//上锁,可以被中断打断
	void spin_lock_irq(spinlock_t *lock);	//上锁,不可以被中断打断
4. 解锁
	void spin_unlock(spinlock_t *lock);
	void spin_unlock_irq(spinlock_t *lock);

(四)使用示例


三、信号量

(一)概念

当一个进程获取到信号量之后,如果此时有另一个进程想要获取这个信号量,后一个进程将进入休眠状态。

注意,是进程进入休眠状态,而非CPU休眠。

(二)特点

  1. 信号量获取不到资源时会进入休眠状态,不消耗cpu资源
  2. 信号量对多核有效
  3. 信号量保护的临界区可以很大,里面可以有延时,耗时,甚至休眠的状态
  4. 信号量工作在进程上下文,不能工作在中断中
  5. 信号量不会产生死锁(在内核层面,进程会永久处于休眠态,但是CPU可以执行其他进程,不会死锁)
  6. 信号量在上锁前不会关闭抢占

(三)信号量的API

1. 定义信号量
	struct semaphore mysem;

2. 初始化信号量
	void sema_init(struct semaphore *sem, int val)
    //第二个参数val在使用的时候值一般设置为1,代表只能被获取一次,再次获取就阻塞

3. 获取信号量
	void down(struct semaphore *sem);
 	int down_trylock(struct semaphore *sem)
    //尝试获取信号量,获取成功返回0,获取失败返回1,不会阻塞

4. 释放信号量
	void up(struct semaphore *sem);

(四)信号量使用示例


四、互斥体

(一)概念

当一个进程获取到互斥体之后,如果此时有另外一个进程也想获取这个互斥体,后一个进程处于休眠状态。互斥体本身具备 排它性,所以互斥体又叫做排它锁。

信号量只有在 val 设置为1时,才具有排他性。

(二)特点

  1. 互斥体获取不到资源时不消耗cpu资源
  2. 互斥体保护的临界区可以很大,里面可以有延时,耗时,甚至休眠的状态
  3. 互斥体工作在进程上下文
  4. 互斥体不会产生死锁(在内核层面,信号量只会锁一个进程)
  5. 互斥体在上锁前不会关闭抢占
  6. 互斥体在获取不到资源时,会适当等一会再进入休眠状态,如果再等的期间有资源了就不会休眠了,所以进程上下文不确定临界区大小时,优先选择互斥体

因此临界区小的时候,没有获取到互斥体的进程就不会休眠,因此临界区小的时候效率高于信号量

(三)互斥体的API

1. 定义互斥体
	struct mutex lock;
2. 初始化互斥体
	mutex_init(&lock);
3. 上锁
	void mutex_lock(struct mutex *lock);
	void mutex_trylock(struct mutex *lock); //尝试上锁,成功返回1,失败返回0
4. 解锁
	void mutex_unlock(struct mutex *lock)

(四)互斥体使用示例


五、原子操作

(一)概念

原子操作根据它的原子特性来命名的,强调原子性。
原子性就是将整个操作看成一个不可分割的整体,如果执行就必须一次性执行完成,过程中不能被打断。

(二)特点

原子操作内部通过两个步骤保证原子性:
1.关闭多核执行,保证只有 一个核 能够操作原子变量
2.对原子变量的修改通过 内联汇编(在C语言中运行汇编代码)完成

C语言定义变量,首先会放到内存中,然后被加载到cache中,之后才会被取到寄存器中; 汇编定义变量会直接放到寄存器中

本质就是对变量的操作,没有阻塞、自旋、休眠等特性

(三)原子操作的API

typedef struct {
	volatile int counter;
} atomic_t;
注意必须使用接口进行赋值,而不能自己定义结构体赋值
自己赋值的操作就是使用C语言方式进行赋值,没有了内联汇编的特性了

1. 定义并初始化原子变量
	atomic_t atm = ATOMIC_INIT(1);
2. 上锁
	atomic_dec_and_test(&atm); 
	将原子变量的值减1然后和0比较,
	如果结果为0,说明获取资源成功,成功返回真;否则返回假
3. 解锁
	atomic_inc(&atm);
	将原子变量的值加1
-----------------------------------------------------------
1.定义并初始化原子变量
    atomic_t atm = ATOMIC_INIT(-1);
2.上锁
 	int atomic_inc_and_test(&atm)
    让原子变量的值加1,然后和0比较如果结果为0,表示获取资源成功,成功返回真
3.解锁
    atomic_dec(&atm)
    将原子变量的值减1 
  • 注:在使用原子变量时,如果获取资源失败,在退出程序之前,需要重新加回或减回原子变量,否则后续无法正常释放资源。

(四)原子操作使用示例


  • 15
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值