当前位置: 首页 > 编程日记 > 正文

linux设备驱动第五篇:驱动中的并发与竟态


目录[-]

  • 综述
  • 信号量与互斥锁
  • Completions 机制
  • 自旋锁
  • 其他的一些选择
  • 不加锁算法
  • 原子变量与位操作
  • seqlock(顺序锁)
  • 读取-拷贝-更新(RCU)
  • 小结

综述

在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。

首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时、并行被执行。而并发的执行单元对共享资源(硬件资源和软件上的全局、静态变量)的访问则容易导致竞态(race conditions)。可能导致并发和竟态的情况有:

  • SMP(Symmetric Multi-Processing),对称多处理结构。SMP是一种紧耦合、共享存储的系统模型,它的特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。

  • 中断。中断可 打断正在执行的进程,若中断处理程序访问进程正在访问的资源,则竞态也会发生。中断也可能被新的更高优先级的中断打断,因此,多个中断之间也可能引起并发而导致竞态。

  • 内核进程的抢占。linux是可抢占的,所以一个内核进程可能被另一个高优先级的内核进程抢占。如果两个进程共同访问共享资源,就会出现竟态。

以上三种情况只有SMP是真正意义上的并行,而其他都是宏观上的并行,微观上的串行。但其都会引发对临界共享区的竞争问题。而解决竞态问题的途径是保证对共享资源的互斥访问,即一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。那么linux内核中如何做到对对共享资源的互斥访问呢?在linux驱动编程中,常用的解决并发与竟态的手段有信号量与互斥锁,Completions 机制,自旋锁(spin lock),以及一些其他的不使用锁的实现方式。下面一一介绍。

信号量与互斥锁

信号量其实就是一个整型值,其核心是一个想进入临界区的进程将在相关信号量上调用 P; 如果信号量的值大于零, 这个值递减 1 并且进程继续. 相反,,如果信号量的值是 0 ( 或更小 ), 进程必须等待直到别人释放信号量. 解锁一个信号量通过调用 V 完成; 这个函数递增信号量的值,,并且, 如果需要, 唤醒等待的进程。而当信号量的初始值为1的时候,就变成了互斥锁。

信号量的典型使用形式:

//声明信号量
struct semaphore sem;//初始化信号量
void sema_init(struct semaphore *sem, int val)//常用下面两种形式
#define init_MUTEX(sem) sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)//以下是初始化信号量的快捷方式,最常用的
DECLARE_MUTEX(name)    //初始化name的信号量为1
DECLARE_MUTEX_LOCKED(name) //初始化信号量为0//常用操作
DECLARE_MUTEX(mount_sem);
down(&mount_sem); //获取信号量
...
critical section    //临界区
...
up(&mount_sem);    //释放信号量

常用的down操作还有

// 类似down(),因为down()而进入休眠的进程不能被信号打断,而因为down_interruptible()而进入休眠的进程能被信号打断, 
// 信号也会导致该函数返回,此时返回值非0
int down_interruptible(struct semaphore *sem);
// 尝试获得信号量sem,若立即获得,它就获得该信号量并返回0,否则,返回非0.它不会导致调用者睡眠,可在中断上下文使用
int down_trylock(struct semaphore *sem);

Completions 机制

完成量(completion)提供了一种比信号量更好的同步机制,它用于一个执行单元等待另一个执行单元执行完某事。

</pre></div><div><pre name="code" class="cpp">// 定义完成量
struct completion my_completion;// 初始化completion
init_completion(&my_completion);// 定义和初始化快捷方式:
DECLEAR_COMPLETION(my_completion);// 等待一个completion被唤醒
void wait_for_completion(struct completion *c);// 唤醒完成量
void cmplete(struct completion *c);
void cmplete_all(struct completion *c);

自旋锁

若一个进程要访问临界资源,测试锁空闲,则进程获得这个锁并继续执行;若测试结果表明锁扔被占用,进程将在一个小的循环内重复“测试并设置”操作,进行所谓的“自旋”,等待自旋锁持有者释放这个锁。自旋锁与互斥锁类似,但是互斥锁不能用在可能睡眠的代码中,而自旋锁可以用在可睡眠的代码中,典型的应用是可以用在中断处理函数中。自旋锁的相关操作:

// 定义自旋锁 
spinlock_t spin; // 初始化自旋锁
spin_lock_init(lock);// 获得自旋锁:若能立即获得锁,它获得锁并返回,否则,自旋,直到该锁持有者释放
spin_lock(lock); // 尝试获得自旋锁:若能立即获得锁,它获得并返回真,否则立即返回假,不再自旋
spin_trylock(lock); // 释放自旋锁: 与spin_lock(lock)和spin_trylock(lock)配对使用
spin_unlock(lock); 自旋锁的使用:
// 定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);spin_lock(&lock);  // 获取自旋锁,保护临界区
...  // 临界区
spin_unlock();  // 解锁


自旋锁持有期间内核的抢占将被禁止。自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。为防止这种影响,需要用到自旋锁的衍生:

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()

其他的一些选择

以上是linux驱动编程中经常用到的锁机制,下面讲一些内核中其他的一些实现。

不加锁算法

有时, 你可以重新打造你的算法来完全避免加锁的需要.。许多读者/写者情况 -- 如果只有一个写者 -- 常常能够在这个方式下工作.。如果写者小心使数据结构,由读者所见的,是一直一致的,,有可能创建一个不加锁的数据结构。在linux内核中就有一个通用的无锁的环形缓冲实现,具体内容参考<linux/kfifo.h>。

原子变量与位操作

原子操作指的是在执行过程中不会被别的代码路径所中断的操作。原子变量与位操作都是原子操作。以下是其相关操作介绍。

// 设置原子变量的值
void atomic_set(atomic_t *v, int i);  // 设置原子变量的值为i
atomic_t v = ATOMIC_INIT(0);  // 定义原子变量v,并初始化为0// 获取原子变量的值
atomic_read(atomic_t *v);  // 返回原子变量的值// 原子变量加/减
void atomic_add(int i, atomic_t *v);  // 原子变量加i
void atomic_sub(int i, atomic_t *v);  // 原子变量减i// 原子变量自增/自减
void atomic_inc(atomic_t *v);  // 原子变量增加1
void atomic_dec(atomic_t *v);  // 原子变量减少1// 操作并测试:对原子变量进行自增、自减和减操作后(没有加)测试其是否为0,为0则返回true,否则返回false
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);// 操作并返回: 对原子变量进行加/减和自增/自减操作,并返回新的值
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位取反// 测试位
test_bit(nr, void *addr); // 返回addr地址的第nr位// 测试并操作:等同于执行test_bit(nr, void *addr)后再执行xxx_bit(nr, void *addr)
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);

seqlock(顺序锁)

使用seqlock锁,读执行单元不会被写执行单元阻塞,即读执行单元可以在写执行单元对被seqlock锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才去进行写操作。写执行单元之间仍是互斥的。若读操作期间,发生了写操作,必须重新读取数据。seqlock锁必须要求被保护的共享资源不含有指针。

// 获得顺序锁
void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock, flags)
write_seqlock_irq(lock)
write_seqlock_bh()// 释放顺序锁
void write_sequnlock(seqlock_t *sl);
write_sequnlock_irqrestore(lock, flags)
write_sequnlock_irq(lock)
write_sequnlock_bh()// 写执行单元使用顺序锁的模式如下:
write_seqlock(&seqlock_a);
...  // 写操作代码块
write_sequnlock(&seqlock_a);读执行单元操作:
// 读开始:返回顺序锁sl当前顺序号
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags)// 重读:读执行单元在访问完被顺序锁sl保护的共享资源后需要调用该函数来检查,在读访问期间是否有写操作。若有写操作,重读
int read_seqretry(const seqlock_t *sl, unsigned iv);
read_seqretry_irqrestore(lock, iv, flags)// 读执行单元使用顺序锁的模式如下:
do{seqnum = read_seqbegin(&seqlock_a);// 读操作代码块 ...
}while(read_seqretry(&seqlock_a, seqnum));

读取-拷贝-更新(RCU)

读取-拷贝-更新(RCU) 是一个高级的互斥方法,在合适的时候可以取得非常高的效率。RCU可以看作读写锁的高性能版本,相比读写锁,RCU的优点在于既允许多个读执行单元同时访问被保护的数据,又允许多个读执行单元和多个写执行单元同时访问被保护的数据。但是RCU不能替代读写锁,因为如果写比较多时,对读执行单元的性能提高不能弥补写执行单元导致的损失。由于平时应用较少,所以不做多说。

小结

以上就是linux驱动编程中涉及的并发与竞态的内容,下面做一个简单的小结。

现在的处理器基本上都是SMP类型的,而且在新的内核版本中,基本上都支持抢占式的操作,在linux中很多程序都是可重入的,要保护这些数据,就得使用不同的锁机制。而锁机制的基本操作过程其实大同小异的,声明变量,上锁,执行临界区代码,然后再解锁。不同点在于,可以重入的限制不同,有的可以无限制重入,有的只允许异种操作重入,而有的是不允许重入操作的,有的可以在可睡眠代码中使用,有的不可以在可睡眠代码中使用。而在考虑不同的锁机制的使用时,也要考虑CPU处理的效率问题,对于不同的代码长度,不同的代码执行时间,选择一个好的锁对CPU的良好使用有很大的影响,否则将造成浪费。

之前在linux设备驱动第三篇:写一个简单的字符设备驱动中介绍了简单的字符设备驱动,下一篇将介绍一些字符设备驱动中得高级操作。

第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注个人微信公众平台:程序员互动联盟(coder_online),扫一扫下方二维码或搜索微信号coder_online即可关注,阅读android,chrome等多种热门技术文章。

转载于:https://www.cnblogs.com/yistn/p/4418538.html

相关文章:

Ubuntu16.04运行.run文件

QT配置ROS环境,运行.run文件—参考链接: https://blog.csdn.net/have_fun_/article/details/88242536

终极AutoCAD大师班:成为AutoCAD专家

Ultimate AutoCAD Masterclass: Become an Expert in AutoCAD 流派:电子学习| MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09;|大小:6.39 GB |时长:9h 58m 使用AutoCAD学习…

《深入理解Java虚拟机》(第二版)学习1:JVM的内存划分

运行时数据区 先来一张图描述一下 JVM 的内存划分 PS&#xff1a;自己画的&#xff0c;丑是难免丑了点… 程序计数器&#xff08;Program Counter Register&#xff09; 程序计数器&#xff08;Program Counter Register&#xff09;是一块较小的内存空间&#xff0c;它可以…

下一个亿万市场:企业级SaaS服务谁能独领风骚

注&#xff1a;SaaS是Software-as-a-Service(软件即服务)的简称&#xff0c;一种完全创新的软件应用模式&#xff0c;简单来说SaaS即为提供商基于互联网为企业提供软件服务。 ​对中小型企业来说&#xff1a;SaaS是采用先进技术&#xff0c;它消除了企业购买、构建和维护基础设…

inline-block在ie6中的经典bug

众所周知&#xff0c;给元素设置 inline-block &#xff0c;可以让ie下的元素出发layout:1。 但是&#xff0c;当给元素设置 inline-block 后&#xff0c;在另外一个class 样式&#xff08;非设置inline-block的class样式&#xff09;重置为inline或者block。对于ie6下&#xf…

各系统QT安装ROS后不显示src问题

刚创建的文件显示如下&#xff1a; 接下来修改这里&#xff1a; 将对勾去掉 之后就可以正常显示&#xff0c;可以添加自己的工作空间以及功能包了

使用脚本完成AutoCAD自动化任务课程

The complete AutoCAD Automation tasks course Using Script MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz&#xff0c;2 Ch 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; |时长:42节课(4h 25m) |大小:3.35 GB 含课…

学生管理系统(C语言版)

学生管理系统 这个是大一学习C语言的时候做的一个小项目&#xff0c;代码部分基本都是自己一人完成&#xff0c;没用到什么高大上的技术&#xff0c;在图形化方面用了EasyX&#xff08;一个C的图形库&#xff09;&#xff0c;其他都是C语言的基础内容。 项目介绍 项目总体功…

jsp,图片显示

问题&#xff1a;jsp中显示项目中image文件夹中的图片 1&#xff0c;项目中image文件夹中有对应的图片 2&#xff0c;<img ,src"/项目名/image/图片名.jpg">&#xff0c;用其他变量获取这个路径也可以&#xff0c;但本质还是这个路径 3,对于jsp和HTML等文件的修…

Java基础系列——IO流

2019独角兽企业重金招聘Python工程师标准>>> ---恢复内容开始--- Java对数据的操作都是通过流的方式&#xff0c;数据的输入和输出是相对内存来说的&#xff0c;将外设的数据读到内存&#xff1a;输入流&#xff1b;将内存的数据写到外设&#xff1a;输出流。 流按…

Visual Studio Code / Roboware Studio调整字体大小,跳转等操作

调大字体:Ctrl 调小字体:Ctrl _- 跳转:F12 跳转返回:Ctrl Alt _- 打开内部终端:Ctrl ~

C4D和Redshift:2D矢量到三维渲染 Cinema 4D and Redshift: 2D vector to 3D render

C4D和Redshift:2D矢量到三维渲染 时长:1h 4m |视频:. MP4 1280720&#xff0c;30 fps(r) |音频:AAC&#xff0c;48000 Hz&#xff0c;2ch |大小解压后:633 MB 含课程文件 语言&#xff1a;英语中英文字幕(机译) 本课程涵盖了诸如将矢量转换为3d场景的主题。您将了解哪些数据可以…

《深入理解Java虚拟机》(第二版)学习2:垃圾收集算法

对象存活判断 在对堆进行回收之前虚拟机需要判断这些对象中那些是“存活”的。 引用计数算法&#xff08;Reference Counting&#xff09; 原理&#xff1a;给对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效…

Android 知识杂记(MVP模式)

MVP的模式在于将原来activity中业务逻辑的部分剥离出来&#xff0c;代码示例如下&#xff1a; Accountpublic class Account {private String mUsername;private String mPassword;public String getmUsername() {return mUsername;}public void setmUsername(String mUsername…

玩转百度即用API(2)——身份证查询

2019独角兽企业重金招聘Python工程师标准>>> 第二个即用API&#xff0c;身份证查询 示例代码&#xff1a; #-*- coding: utf-8 -*- #version&#xff1a;0.1 #note:该即用API接口简单&#xff0c;输入身份证只能查到3个信息&#xff1a;大概地址&#xff0c;性别&am…

解决Ubuntu环境下不能QT不能输入中文

参考链接: https://blog.csdn.net/baidu_33850454/article/details/81212026?utm_mediumdistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_sourcedistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2…

UE5虚幻引擎5中的实时特效学习 Introduction to real time FX in Unreal Engine 5

MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz&#xff0c;2 Ch 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; |时长:40节课(3h 36m) |大小解压后:2.65 GB 含课程文件 从零开始使用尼亚加拉在虚幻引擎中学习实时外汇…

《深入理解Java虚拟机》(第二版)学习3:垃圾收集器

垃圾收集器 如果说收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。 我们这里讨论的收集器主要是基于JDK 1.7 Update 14之后的 Hotspot VM 。 Serial 收集器 Serial 收集器是最基本、发展历史最悠久的收集器&#xff0c;曾经&#xff08;在 …

2016寒假训练3

题目链接 A CodeForces 362A Two Semiknights Meet 题意&#xff1a;在一个棋盘中有两个定义了特殊走法的棋子&#xff0c;同时移动他们&#xff0c;问是否会相遇&#xff08;只能在合法的位置&#xff09;。 做法&#xff1a;直接暴力dfs处理出这两个棋子到达各个位置的时间&a…

2015化妆品行业如何进行微信公众号营销

对比其他行业发展特点发现化妆品行业具有以下特点&#xff1a; 1、化妆品行业呈现节日、电商促销影响明显、口碑评价关注度高、女性网民占比高冲动消费多、整体用户年轻化等特点。 2、化妆品行业即使在各行业发展低迷期依然保持良好发展势头。 3、化妆品商家的顾客回头率高。 4…

在新建好的ROS空间里面添加功能包

第一步:创建功能包 cd catkin_ws打开src ~/catkin_ws/src新建文件夹名字 catkin_create_pkg (文件加名字) roscpp rospy std_msgs打开新建文件夹中的src ~/catkin_ws/src/(新建文件夹名字)/src输入: gedit 文件.cpp返回工作空间: 执行catkin build 编译后即可执行

Revit的Enscape基本培训(2021) Enscape Essential Training for Revit (2021)

MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz&#xff0c;2 Ch 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; |时长:2h 53m |大小解压后:2.23 GB 含课程练习文件 如果您使用Revit&#xff0c;您可能需要学习Enscape&…

设计模式学习2:单例模式

单例模式 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法(静态方法)。 比如Hibernate的SessionFactory&#xff0c;它充当数据存储源的代理&#xf…

数据库插入时,标识列插入显式值

当 IDENTITY_INSERT 设置为 OFF 时&#xff0c;不能为表 CU_GiftExchange 中的标识列插入显式值。SET IDENTITY_INSERT [dbo].[CU_GiftExchange] ONGO转载于:https://blog.51cto.com/hezun/1631849

Ubuntu终端显示文本让选择确定,OK等等

问题1:首先按下"TAB"键,会看到选中文本"确定","OK"然后按下回车键,即可!

Unity安卓游戏开发:打造7款2D 3D游戏 Unity Android Game Development : Build 7 2D 3D Games

流派:电子学习| MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; |大小:15.4 GB |时长:32h 55m Unity游戏开发与设计&#xff0c;用C# & Unity学习Unity安卓游戏开发(更…

唯一索引和普通索引的选择

前言&#xff1a;最近在研究阿里的开发手册中关于 MySQL 的一些规定&#xff0c;所以来记录一下学习中的心得 唯一索引和普通索引的选择 【强制】业务上具有唯一特性的字段&#xff0c;即使是组合字段&#xff0c;也必须建成唯一索引。 说明&#xff1a;不要以为唯一索引影响了…

Springboot整合HBase——大数据技术之HBase2.x

Apache HBase 是以hdfs为数据存储的,一种分布式、可扩展的noSql数据库。是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase使用与BigTable(BigTable是一个稀疏的、分布式的、持久化的多维排序map)非常相似的数据模型。用户将数据行存储在带标签的表中。数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列。

一个合格的Java选手必须要掌握的并发锁知识

Java内置锁:基于Java语法层面(关键词)实现的锁,主要是根据Java语义来实现,最典型的应用就是synchronized。Java显式锁:基于JDK层面实现的锁,主要是根据基于Lock接口和ReadWriteLock接口,以及统一的AQS基础同步器等来实现,最典型的有ReentrantLock。使用方式:synchronized关键字互斥锁主要有作用于对象方法上面,作用于类静态方法上面,作用于对象方法里面,作用于类静态方法里面等4种方式。

终于有人把Web 3.0和元宇宙讲明白了

分散的数据网络使个人数据(例如个人的健康数据、农民的作物数据或汽车的位置和性能数据)出售或交换成为可能,与此同时,不会失去对数据的所有权控制、放弃数据隐私或依赖第三方平台来管理数据。Web 3.0的目标是在创作者经济中取得更好的平衡。互联网第二次迭代(Web 2.0)的缺陷,加上公有区块链技术的诞生,帮助我们朝着更加去中心化的Web 3.0 迈进,元宇宙和更广泛的去中心化网络都是关于现实世界和虚拟世界的融合。此时的网络中不再是静态内容,而是动态的内容,用户现在可以与发布在网络上的内容进行交互。