Linux设备驱动的并发控制

一、概述

Linux设备驱动中必须解决的一个问题就是多个进程对共享资源(如全局变量、静态变量、硬件资源等)的并发访问,会导致竟态,如可能会出现以下情况:导致执行单元C独处的数据不符合预期在这里插入图片描述
导致竟态发生有如下几种情况:
对称多处理器(SMP)的多个CPU:由于多个CPU使用共同的系统总线,因此可以访问共同的外设和存储器,CPU0和CPU的进程、中断都会可能造成相互之间的竟态
单CPU进程之间的抢占:由于Linux内核支持抢占调度,所有当前进程执行时可能会被其他进程抢占资源,产生竞态
中断与进程之间的抢占:中断打断正在执行的进程,如果中断服务程序访问临界资源,也会产生竞态

为了避免竞态的发生,必须保证共享资源的互斥访问,Linux提供了中断屏蔽、原子操作、自旋锁、信号量、互斥锁等途径来解决。

二、解决方法

1. 中断屏蔽

中断屏蔽是一种简单粗暴有效的方法,可以保证正在执行的内核程序不被中断处理程序所抢占,而且由于进程调度也依赖于中断,所以内核进程之间的抢占问题夜避免了。
以下函数只能屏蔽当前CPU的中断,并不能解决SMP多CPU之间的竞态。
API

local_irq_disable();	//屏蔽本CPU中断
local_irq_enable();	
local_irq_save();		//除了禁止中断外,保存当前CPU的中断信息
local_irq_restore();
local_bh_disable();		//禁止底半部中断
local_bh_disable();		

使用

local_irq_disable();	//屏蔽中断
...						//临界区资源
local_irq_enable();		//开中断

其本质就是让CPU不再响应中断,即将ARM处理器的CPSR寄存器的I位清零。但由于Linux的异步IO和进程调度等操作都依赖于中断,所以长时间屏蔽中断可能会造成数据丢失乃至系统崩溃后果,需要尽快执行完临界区的代码

2. 原子操作

原子操作是为了保证对一个整型数据修改的排他性,分为针对位操作和整形变量的原子操作。当发生两个任务同时调用原子操作时,先调用的会失败,后调用的会成功
在这里插入图片描述

整型原子操作

atomic_t v = ATOMIC_INIT(0);	//定义原子变量v并初始化为0
void atomic_set(atomic_t *v, int i); //设置原子变量v的值为 i
int atomic_read(atomic_t *v);	//返回原子变量v的值
void atomic_add(int i, atomic_t *v);	//原子变量v增加i
void atomic_sub(int i, atomic_t *v);	//原子变量v减少i
void atomic_inc(atomic_t *v);	//原子变量自增1
void atomic_dec(atomic_t *v);	//原子变量自减1
//尝试自增/自减/减i,成功返回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/减i/自增/自减,成功返回新值
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);

位原子操作

void set_bit(nr, void *addr);	//将addr的nr位 置1
void clear_bit(nr, void *addr);	//将addr的nr位 清0
void change_bit(nr, void *addr);	//将addr的nr位 反转
int test_bit(nr, void *addr);	//返回addr的nr位
//尝试进行置位/清位/反转,返回新值
int test_and_set_bit(nr, void *addr);	
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);

3. 自旋锁

自旋锁是一种对临界资源进行互斥访问的方式,本质上就是在访问临界资源前一直执行Test-And-Set某个内存变量的原子操作,成功则表示锁空闲可以访问,失败则表示锁被其他进程占用,一直循环获取这个锁知道成功。

spinlock_t lock;	//定义自旋锁
spin_lock_init(lock);	//初始化自旋锁
spin_lock(lock);	//获取自旋锁,失败则一直获取
spin_trylock(lock);		//尝试获取自旋锁,失败则直接返回
spin_unlock(lock);	//释放自旋锁

自旋锁只能保证进程间抢占时的互斥访问,但中断依然可以打扰,所以自旋锁提供了与中断屏蔽相结合的接口

spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_lock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()

注意:

  1. 上述的接口也不能屏蔽另一个核的中断,因此需要在中断中也调用spin_lock();
  2. 只有在临界资源比较小,占用锁处理时间极短的情况下使用自旋锁才合理,否则会导致其它进程一直处于等待过程中影响系统性能
  3. 自旋锁使用时应注意避免陷入死锁,如递归使用自旋锁等情况
  4. 使用自旋锁访问临界资源期间也不能调用可能引起进程调度的函数如copy_from_user()等,否则可能导致内核崩溃

读写锁
读写锁是自旋锁的扩展,其特点是读时共享,写时互斥,即读时允许并发,写时最多只能有1个进程访问临界资源
读锁, 而且读和写操作互斥

rwlock_t my_rwlock;	//定义一个读写锁
rwlock_init(&my_rwlock);	//初始化读写锁
//读锁定
void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
//读解锁
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);

写锁

//写锁定
void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
//写解锁
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);

尝试解锁:立即返回

spin_trylock();
write_trylock();

顺序锁
顺序锁是对读写锁的扩展,特点是在读写锁基础上解除了读和写之间的互斥,即读时也可以写,写时也可以读,但写时不能写。在读操作之前检测到了写操作,读写锁的读操作会阻塞,顺序锁会一直检测
获取顺序锁

void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock, flags)
write_seqlock_irq(lock)
write_seqlock_bh(lock)write_seqlock_irqsave() = loal_irq_save() + write_seqlock()
write_seqlock_irq() = local_irq_disable() + write_seqlock()
write_seqlock_bh() = local_bh_disable() + write_seqlock()

释放顺序锁

void write_sequnlock(seqlock_t *sl);
write_sequnlock_irqrestore(lock, flags)
write_sequnlock_irq(lock)
write_sequnlock_bh(lock)
write_sequnlock_irqrestore() = write_sequnlock() + local_irq_restore()
write_sequnlock_irq() = write_sequnlock() + local_irq_enable()
write_sequnlock_bh() = write_sequnlock() + local_bh_enable()

4. 信号量

信号量是操作系统中最典型的用于同步的手段,通过PV操作来实现互斥,即P:S–,V:S++,当S>0时唤醒队列中的等待信号量的进程,最典型的就是生产消费者模式,当获取不到信号量时,进程会进入休眠。适用方式如下:

struct semaphore sem;	//定义信号量
void sema_init(&sem, 0);	//初始化信号量初始值为0
//获取信号量
down(&sem);	//获得信号量,若获取不到会睡眠,不能在中断中使用
down_interruptible(&sem);	//与down()类似,该函数在睡眠中可以被信号打断
down_trylock(&sem);	//尝试获取信号量,会立刻返回,成功返回0
//释放信号量,唤醒等待者
void up(&sem);

5. 互斥锁

常用API

struct mutex my_mutex;
mutex_init(&my_mutex);
void mutex_unlock(struct mutex *lock);
int mutex_lock_interruptiable(struct mutex *lock);
int mutex_trylock(struct mutex *lock);
void mutex_unlock(struct mutex *lock);

使用方式

struct mutex my_mutex;
mutex_init(&my_mutex);
mutex_lock(&my_mutex);
xxx
mutex_unlock(&my_mutex);

自旋锁和互斥锁的区别:当得不到锁时,自旋锁会占用CPU一直等待,互斥锁会睡眠。所以自旋锁适用于临界资源比较小时,其他进程可以快速执行完当前进程可以快速获取;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/45132.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

int类型变量表示范围的计算原理

文章目录 1. 了解2. 为什么通常情况下int类型整数的取值范围是-2147483648 ~ 21474836473. int类型究竟占几个字节4. 推荐 1. 了解 通常情况下int类型变量占4个字节,1个字节有8位,每位都有0和1两种状态,所以int类型变量一共可以表示 2^32 种状…

【全面介绍Oracle】

🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 目录 🎥前言🎥基本概念和安装🎥SQL语言🎥PL/SQL编程🎥数据库…

【计算机组成原理 | 第三篇】各个硬件的组成部分

前言: 在前面的文章中,我们介绍了计算机架构的基本组成。可以知道计算机的基本架构由“存储器”,“运算器”,“控制器”,“输入设备”,“输出设备”这五部分组成。 在这片文章中,我们来深入的了…

【斯坦福因果推断课程全集】2_无混淆和倾向分1

目录 Beyond a single randomized controlled trial Aggregating difference-in-means estimators Continuous X and the propensity score 随机试验的一个最简单的扩展是无约束下的干预效果估计。从定性上讲,当我们想估计一种并非随机的治疗效果,但一…

数列分块<2>

本期是数列分块入门<2>。该系列的所有题目来自hzwer在LOJ上提供的数列分块入门系列。 Blog:http://hzwer.com/8053.html sto hzwer orz %%% [转载] 好像上面的链接↑打不开&#xff0c;放一个转载:https://www.cnblogs.…

tensorflow卷积层操作

全连接NN&#xff1a; 每个神经元与前后相邻层的每一个神经元都有全连接关系。输入是特征&#xff0c;输出为预测结果。 参数个数(前层*后层后层&#xff09; 实际应用时&#xff0c;会先对原始图像进行特征提取&#xff0c;再把提取到的特征送给全连接网络 会先进行若干层提…

C嘎嘎类与对象上篇

类的定义 1. class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后⾯分号不能省略 。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量; 类中的函数称为类的⽅法或者成员函数。 2. C中struct也可以…

【C语言】C语言-学生籍贯信息记录系统(源码+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

泛微e-cology getFileViewUrl接口存在SSRF漏洞复现 [附POC]

文章目录 泛微e-cology getFileViewUrl接口存在SSRF漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现0x06 修复建议泛微e-cology getFileViewUrl接口存在SSRF漏洞复现 [附POC] 0x01 前言 免责声明:请勿利用文章…

【密码学】哈希函数与加密算法的关系

一、哈希函数的定义 哈希函数&#xff08;Hash Function&#xff09;&#xff0c;也被称为散列函数或杂凑函数&#xff0c; 是一种将任意长度的输入数据&#xff08;通常称为“预映射”或“消息”&#xff09;转换为固定长度输出&#xff08;通常称为“哈希值”、“散列值”、“…

PHP红包拓客微信小程序系统源码

&#x1f389;红包狂欢&#xff0c;客源滚滚来&#xff01;红包拓客微信小程序&#xff0c;营销新利器&#x1f680; &#x1f9e7;一、创意红包&#xff0c;吸引眼球 你还在为如何吸引顾客而烦恼吗&#xff1f;红包拓客微信小程序来帮你&#xff01;&#x1f381; 它以创意红…

基于jeecgboot-vue3的Flowable流程-集成仿钉钉流程(六)仿钉钉流程的转bpmn流程图

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、转bpmn流程图接口 /*** 转为bpmn xml格式* param processModel* throws IOException*/PostMapping("/ddtobpmnxml")public Result<?> ddToBpmnXml(RequestBody Proce…

TIA博途Wincc中画箭头和管道的具体方法示例

TIA博途Wincc中画箭头和管道的具体方法示例 如何画箭头? 如下图所示,在右侧的工具箱中找到基本对象—线,点击选中, 如下图所示,在画面中添加一条线段,然后右键进入该线段的属性,设置线的外观: 宽度、样式、颜色; 线端:这里可以选择起始和结束的样式,选择“箭头”,则…

自闭症孩子能否正常上普校:一场充满挑战与希望的探讨

在教育的舞台上&#xff0c;自闭症孩子​​​​​​​能否正常融入普通学校&#xff0c;是一个备受关注且充满争议的话题。 支持自闭症孩子上普校的观点认为&#xff0c;普通学校能为他们提供更接近真实社会的环境。在普校中&#xff0c;自闭症孩子有机会与不同类型的同学交流互…

IDEA实现热部署

什么是热部署&#xff1f; 热部署&#xff08;Hot Deployment&#xff09;是指在应用程序运行过程中&#xff0c;无需停止整个应用程序或重新启动服务器&#xff0c;就能够部署新的代码、资源或配置文件&#xff0c;使其立即生效。这种部署方式有助于提高开发效率和系统的可用性…

【系统架构设计】计算机组成与体系结构(三)

计算机组成与体系结构&#xff08;三&#xff09; 计算机系统组成存储器系统主存储器辅助存储器Cache存储器Cache 基本原理映射机制直接映射全相联映射组相联映射 替换算法写操作 流水线&#xff08;计算&#xff09;流水线周期流水线执行时间流水线的吞吐率流水线的加速比 计算…

卷积是如何计算的

使用代码&#xff0c;看卷积是如何计算的。 torch.nn torch.nn.functional srtide 的用法&#xff0c;代表卷积核的步幅 import torch import torch.nn.functional as F # 这个是输入的一个二维矩阵 input torch.tensor([[1,2,0,3,1],[0,1,2,3,1],[1,2,1,0,0],[5,2,3,1,1],…

Javascript[ECMAScript] 新特性—1

背景 JS1.1&#xff08;1997&#xff09; 第一版基于Netscape Navigator 3.0中实现的JAVASCRIPT 1.1 JS1.2&#xff08;1999&#xff09; 基于Netscape Navigator 4.0中实现的JavaScript 1.2。添加了正则表达式、更好的字符串处理、新的控制语句、Try/Catch异常处理、更严格…

推荐3个提升工作效率的实用电脑工具

PearOCR PearOCR是一款基于网络的光学字符识别&#xff08;OCR&#xff09;工具&#xff0c;旨在通过先进的图像处理和机器学习技术&#xff0c;快速、准确地从图片中提取文字。该工具的所有功能完全免费&#xff0c;这主要得益于其所有文件都在本地进行识别&#xff0c;不需要…

完美解决:MySQL8报错:Public Key Retrieval is not allowed

在配置数据源的时候直接将属性allowPublicKeyRetrieval设置为true即可 &AutoReconnecttrue