`
damengjiejie
  • 浏览: 9851 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

linux设备驱动第五篇:驱动中的并发处理

 
阅读更多

综述

在上一篇介绍了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等多种热门技术文章。

摘自:http://my.oschina.net/haomcu/blog/398905

分享到:
评论

相关推荐

    大华无插件播放项目111

    大华无插件播放项目111

    Oracle 19c 数据库备份恢复与导入导出实战指南

    内容概要:本文详细介绍了Oracle 19c数据库的备份恢复和导入导出操作。首先概述了基本命令,然后分别讲述了三种工作方式(交互式、命令行、参数文件)和三种模式(表、用户、全库)。接着介绍了高级选项,如分割成多个文件、增量导出/导入、以SYSDBA进行导出/导入、表空间传输等。最后讨论了优化技巧,包括加快导出和导入速度的方法。还解决了一些常见问题,如字符集问题和版本问题。 适用人群:Oracle数据库管理员和相关技术人员。 使用场景及目标:适合在日常数据库管理和维护中进行数据备份、恢复、导入和导出操作,提高数据安全性和管理效率。 其他说明:文章内容丰富,涉及多种实用技巧,适用于不同场景下的具体操作,有助于提升工作效率。

    大数据旅游酒店大数据可视化项目

    基于Python Flask开发的旅游酒店大数据可视化项目,可以直接运行。 操作步骤: 1. 解压缩项目文件 2. 使用 pycharm打开项目 3. 运行项目中的app.py文件 注意:需要确保项目的Flask Python相关的环境已经搭建完成。

    模拟立体翻转效果,非Gallery实现.zip

    Android 毕业设计,Android 毕业设计,小Android 程设计,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。

    仿360 浮动小插件效果.zip

    Android 毕业设计,Android 毕业设计,小Android 程设计,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。

    基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告

    基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告,个人高分设计项目、经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的学生和需要项目实战练习的学习者。 基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告基于stm32和openmv的电赛校赛自动泊车题目源码+文档设计报告个人高分设计项目、经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的学生和需要项目实战练习的学习者。 个人高分设计项目、经导师指导并认可通过的高分设

    棉花检测20-YOLO(v5至v9)、COCO、CreateML、Darknet、Paligemma、TFRecord、VOC数据集合集.rar

    棉花检测20-YOLO(v5至v9)、COCO、CreateML、Darknet、Paligemma、TFRecord、VOC数据集合集.rar棉-V2释放 ============================= *与您的团队在计算机视觉项目上合作 *收集和组织图像 *了解非结构化图像数据 *注释,创建数据集 *导出,训练和部署计算机视觉模型 *使用主动学习随着时间的推移改善数据集 它包括406张图像。 以可可格式注释棉花。 将以下预处理应用于每个图像: 没有应用图像增强技术。

    javaweb社区医院挂号系统-lw.zip

    项目包含前后台完整源码。 项目都经过严格调试,确保可以运行! 具体项目介绍可查看博主文章或私聊获取 助力学习实践,提升编程技能,快来获取这份宝贵的资源吧!

    python-3.11.11-amd64.exe

    windwos环境下python 3.11系列64位安装包,仅推荐个人学习、开发、娱乐或者测试环境下使用。

    基于ssm的精品酒销售管理系统+jsp源代码(完整前后端+mysql+说明文档+LW).zip

    使用精品酒销售管理系统的用户分管理员和用户两个角色的权限子模块。 管理员所能使用的功能主要有:主页、个人中心、用户管理、商品分类管理、商品信息管理、系统管理、订单管理等。 用户可以实现主页、个人中心、我的收藏管理、订单管理等。 前台首页可以实现商品信息、新闻资讯、我的、跳转到后台、购物车等。 项目包含完整前后端源码和数据库文件 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/idea Maven包:Maven3.3 服务器:tomcat7

    Video_2024-12-18_000023.wmv

    Video_2024-12-18_000023.wmv

    ppt最終版asasaadd

    ppt最終版asasaadd

    计算机图形学试卷第一套

    计算机图形学期末考试

    springboot-基于SpringBootVue的家具商城系统设计与实现.zip

    springboot-基于SpringBootVue的家具商城系统设计与实现.zip

    PenTablet_5.2.4-5.zip

    PenTablet_5.2.4-5.zip

    基于ssm的企业人力资源管理系统源代码(完整前后端+mysql+说明文档+LW).zip

    考虑了企业管理者的实际工作环境和需求,最终将人力资源系统划分为5个部分,即登录模块、组织发展模块、员工团队模块、合同管理模块、党建管理模块。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/idea Maven包:Maven3.3 服务器:tomcat7

    QT5.12.9音乐播放器MP3 免费下载交流学习

    QT音乐播放器MP3 可点击播放可上一首下一首可调节音量 可暂停可上传音乐

    椅子检测6-YOLO(v5至v9)、COCO、CreateML、Darknet、Paligemma、TFRecord、VOC数据集合集.rar

    椅子检测6-YOLO(v5至v9)、COCO、CreateML、Darknet、Paligemma、TFRecord、VOC数据集合集.rar对象检测实验室-V1 2023-08-21 2:28 PM ============================= *与您的团队在计算机视觉项目上合作 *收集和组织图像 *了解和搜索非结构化图像数据 *注释,创建数据集 *导出,训练和部署计算机视觉模型 *使用主动学习随着时间的推移改善数据集 对于最先进的计算机视觉培训笔记本,您可以与此数据集一起使用 该数据集包括997张图像。 对象以可可格式注释。 将以下预处理应用于每个图像: *像素数据的自动取向(带有Exif-Arientation剥离) *调整大小为640x640(拉伸) 应用以下扩展来创建每个源图像的3个版本: *将盐和胡椒噪声应用于10%的像素

    Python项目-实例-13 截图工具.zip

    Python课程设计,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。

    Altas PF拧紧枪 OP协议,开发协议

    Altas PF拧紧枪 OP协议,开发协议

Global site tag (gtag.js) - Google Analytics