linux设备驱动程序之并发和竞态(二)

其实这blog都是阅读ldd3时的一些总结,巩固自己的学习,也方便后期的使用。大家也可以直接阅读ldd3原文。

锁陷阱

所谓的锁陷阱就是防止死锁。

不明确的规则:

1、不论是信号量还是 自旋锁,都不允许锁拥有者第二次获得这个锁(会死锁)。

2、系统直接调用的那些函数要获得信号量,保护要访问的设备结构。而内部函数的访问则可以根据需要上锁。

锁顺序规则:

1、如果都要获取一系列锁的话,那么可以按照一定顺序规则来获取锁,即:获取多个锁时,锁的顺序一直;

2、如果要获取自己的局部锁和系统的中心锁,则先获取自己的局部锁,然后再去获取中心锁;

3、如果要获取信号量和自旋锁,则需要先获取信号量,因为释放信号量的函数down有可能会休眠;

免锁算法:

有很多的免锁算法,ldd3中提到一种循环缓存区,感觉很有用(工作中也遇到过)。

循环缓冲区:就是比如一个数组A[9]为临界区,可以从A[0]端往A[9]端方向读取数据,从A[9]端往A[0]端写入数据。就是一个互相追逐的算法,这样可以不用锁就能保证数据的一致性;

工作中也遇到过这种缓冲区,把请求结构体往一个循环队列中放,而内核在队列的另外一端读取请求结构体去执行。

原子操作

原子操作在内核源代码中使用非常频繁,其实这个原子操作主要是对变量操作的。在本质上来说这也是一种锁,因为如果我们用一种锁机制去锁住一个变量,这样就有点浪费了。毕竟锁还是要耗费cpu的一些时间的,为了一个小小的变量,动用锁,不值当。所以原子操作就应运而生了。

头文件 <asm/atomic.h>,数据结构为:atomic_t

void  atomic_set(atomic_t  *v,  int  i); // 动态初始化,就是原子赋值:v = i

atomic_t  v  = ATOMIC_INIT(o);// 静态初始化,在编译时初始化

int  atomic_read(atomic_t  *v);// 返回V的当前值

void  atomic_add(int i,  atomic_t  *v);// 将i 加到V指向的原子变量上,返回为void,是因为大多数情况下不需要这个返回值,所以不返回,降低函数成本;

void  atomic_sub(in  i,  atomic_t  *v);// 和上面函数功能相反

void  atomic_inc(atomic_t  *v);// 自增

void  atomic_dec(atomic_t  *v);// 自减

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

执行操作并测试结果:如果操作后,原子值为0,则返回true;否则返回false;

int atomic_add_negative(int  i,  atomic_t  *v);// 将i加到v上,如果结果为负数,返回true,否则为false;

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

执行上面对应的操作,然后返回操作后的新值V

位操作

和原子操作类似,位操作其本质也算是一种锁机制,只是相对来说粒度会小些。信号量和自旋锁是对一段代码,一块内存等进行保护的;原子操作是对变量进行保护的;而位操作是对变量中的各个位进行设置的,当然这也是原子操作。因为原子操作非常快,可以用单个机器指令来执行,所以不用禁止中断。

nr参数一般被定义为int,但也有些不同的系统定位为unsigned long等;

void  set_bit(nr,  void *addr);// 设置addr指向的数据项的第nr位

void  clear_bit(nr,  void *addr);//清楚addr指向的数据项的第nr位

void  change_bit(nr,  void *addr);// 切换指定位

test_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允许读取者自由访问,但要检查是否和写入者发生冲突,当发生冲突时,要重新读取数据;seqlock不保护含有指针的数据结构,因为在写入 者修改该数据的时候,读取者可能会读取一个无效的指针;

头文件 <linux/seqlock.h>

初始化:

seqlock_t   lock1 = SEQLOCK_UNLOCKED;//静态初始化

seqlock_t   lock2;

seqlock_init(&lock2);// 动态初始化

读取者在访问临界区时,先获取一个无符号整数,然后完成自己的操作,退出时再获取下该整数,对比下看是否相等;如果不相等,则需要重新获取个整数==》完成操作==》退出获取参数比较;代码如下:

unsigned  int seq;

do {

seq = read_seqbegin(&the_lock);

/* 完成相应的工作 */

}while (read_seqretry(&the_lock,  seq));

这类锁只是用来保护简单的计算,但是需要数据一致性,所以不一致就必须重新读取数据;

如果在中断处理例程中使用seqlock,则应该使用IRQ安全的版本:

unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned  long flags);

int read_seqretry_irqrestore(seqlock_t *lock,unsigned int seq,  unsigned  long flags);

写入者进入由seqlock保护的临界区时必须获得一个互斥量:

void  write_seqlock(seqlock_t  *lock);

void  write_sequnlock(seqlock_t  *lock);

由于写入锁使用自旋锁实现,自旋锁控制写入访问,所以自旋锁的常见变种都可以使用:

void  write_seqlock_irqsave(seqlock_t  *lock, unsigned long  flags);

void  write_seqlock_irq(seqlock_t  *lock);

void  write_seqlock_bh(seqlock_t  *lock);

void  write_sequnlock_irqrestore(seqlock_t  *lock, unsigned long  flags);

void  write_sequnlock_irq(seqlock_t  *lock,);

void  write_sequnlock_bh(seqlock_t  *lock);

转载地址:http://blog.csdn.net/yuzhihui_no1/article/details/46762723

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-30 18:42:27

linux设备驱动程序之并发和竞态(二)的相关文章

linux设备驱动系列:如何处理竞态关系

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争. 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时.并行被执行.而并发的执行单元对共享资源(硬件资源和软件上的全局.静态变量)的访问则容易导致竞态(race conditions).可能导致并发和竟态的情况有: SMP(Symmetric Multi-Processing),对称多处理结构.SMP是一种紧耦合.共享存储的系统模型,它的特点是多个CPU使用共

LINUX设备驱动程序笔记(四)并发和竞态

       <一>.并发及其管理 大部分竞态可通过使用内核的并发控制原语,并应用几个基本的原理来避免.第一个规则是,只要可能,就应该避免资源的共享,这种思想的明显应用就是避免使用全局变量.但硬件资源本质上就是共享的,软件资源经常需要对其他执行线程可用.全局变量并不是共享数据的唯一途径,只要我们的代码将一个指针传递给了内核的其他部分,一个新的共享就可能建立.在单个执行线程之外共享硬件或软件资源的任何时候,因为另外一个线程可能产生对该资源的不一致观察,因此必须显示地管理对该资源的访问.访问管理的

Hasen的linux设备驱动开发学习之旅--linux设备驱动中的并发与竞态

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:linux设备驱动中的并发与竞态 * Date:2014-11-04 */ 1.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(软件上的全 局变量,静态变量等)的访问则很容易导致竞态(race conditions). 主要的竞态发生在以下几种情况: (1)对称多处理(SMP)

Linux内核分析(七)----并发与竞态

Linux内核分析(七) 这两天家里的事好多,我们今天继续接着上一次的内容学习,上次我们完善了字符设备控制方法,并深入分析了系统调用的实质,今天我们主要来了解一下并发和竞态. 今天我们会分析到以下内容: 1.      并发和竞态简介 2.      竞态解决办法 3.      为我们的虚拟设备增加并发控制 在前几次博文我们已经实现了简单的字符设备,看似完美但我们忽视了一个很严重的问题,即并发问题,那么什么是并发,又如何解决并发呢,我们下面进行分析. l  并发和竞态简介 1.       并

LDD3阅读笔记之内核中的并发和竞态

内核中的并发和竞态 一般驱动程序运行在内核态中,只能调用内核空间中提供的函数,因此在处理并发和竞态时不能使用用户空间提供的库,如pthread库,内核有着自己的一套内部实现机制. 并发及其管理 现代Linux系统中存在大量的并发来源,因此会导致可能的竞态.SMP系统甚至可能在不同的处理器上同时执行我们的代码.内核代码是可抢占的,因此,我们的驱动程序代码可能在任何时候丢失对处理器的独占,而拥有处理器的进程可能正在调用我们的驱动程序代码.设备中断是异步事件,也会导致代码的并发执行.内核还提供了很多可

转:《Linux设备驱动程序3》源码目录结构和源码分析经典链接

转自:http://blog.csdn.net/geng823/article/details/37567557 [原创][专栏]<Linux设备驱动程序>--- LDD3源码目录结构和源码分析经典链接 [专栏]Linux设备驱动程序学习(总目录) [专栏]LDD3源码分析链接(总目录) 1. LDD3源码分析之hello.c与Makefile模板 2. LDD3源码分析之字符设备驱动程序 其他错误: 我的Linux内核为 3.2.0-65-generic-pae,在scull目录下make时

LINUX设备驱动程序(第3版)pdf高清版免费下载

下载地址:网盘下载 备用地址:网盘下载 内容简介编辑<LINUX设备驱动程序(第3版)>已针对Linux内核的2610版本彻底更新过了.内核的这个版本针对常见任务完成了合理化设计及相应的简化,如即插即用.利用sysfs文件系统和用户空间交互,以及标准总线上的多设备管理等等.要阅读并理解本书,您不必首先成为内核黑客:只要您理解C语言并具有Unix系统调用的一些背景知识即可.您将学到如何为字符设备.块设备和网络接口编写驱动程序.为此,<LINUX设备驱动程序(第3版)>提供了完整的示例

LINUX设备驱动程序(第3版)下载 &#113638;

下载地址: http://www.gqylpy.com/di/16 <LINUX设备驱动程序(第3版)>PDF高清完整版-下载 内容简介 编辑 <LINUX设备驱动程序(第3版)>已针对Linux内核的2610版本彻底更新过了.内核的这个版本针对常见任务完成了合理化设计及相应的简化,如即插即用.利用sysfs文件系统和用户空间交互,以及标准总线上的多设备管理等等.要阅读并理解本书,您不必首先成为内核黑客:只要您理解C语言并具有Unix系统调用的一些背景知识即可.您将学到如何为字符设

LDD3之并发和竞态-completion(完成量)的学习和验证

LDD3之并发和竞态-completion(完成量)的学习和验证 首先说下测试环境: Linux2.6.32.2 Mini2440开发板 一开始难以理解书上的书面语言,这里<linux中同步例子(完成量completion)>举了一个公交车上司机和乘客的例子还不错,转过来: 这是一个公交司机和售票员之间的线程调度,用于理解完成量,完成量是对信号量的一种补充,主要用于多处理器系统上发生的一种微妙竞争.在这里两个线程间同步,只有当售票员把门关了后,司机才能开动车,只有当司机停车后,售票员才能开门.