Linux设备驱动运行在内核中,一般只有一份,但是应用程序调用该驱动程序却可能不止一个,这就引入了一个资源共享、并发和竞态的问题。
在Linux驱动程序中(Windows也类似)尽量不要使用全局变量等资源,一旦使用了,必须控制好竞态条件。常用的控制并发方式有:信号量、读写信号量、completion、自旋锁、读写自旋锁、原子变量、顺序锁(seqlog)、RCU等等。
1.信号量
包括一个变量及对它进行的两个原语操作,此变量就称之为信号量,针对临界区。如果无法获取信号量,则休眠(不占用CPU资源),直到获取信号量继续执行。
特点:
(1)可以长期加锁;
(2)只能用于进程上下文,不能用于中断上下文;
(3)在持有自旋锁的同时,不能持有信号量;
2.读写信号量
读写信号量对访问者进行了细分,允许任意多个读者同时拥有一个读写信号量,但是只允许一个写者独立写改信号量,一旦写者写信号量,所有读者不许读。即:防写不防读。
3.completion
completion完成变量一般用在等待线程完成,即:一个线程等待另一个线程完成,比如模块创建一个内核线程,在卸载模块时,要等待线程结束了在删除内存,这时候就用wait_for_completion来等待线程完成再往下工作。
4.自旋锁
使用忙等待锁来确保互斥锁的一种特别方法,针对临界区。如果无法获取信号量,则忙等待(占用CPU资源),直到获取自旋锁继续执行。
特点:
(1)被自旋锁保护的临界区代码执行时不能进入休眠。
(2)被自旋锁保护的临界区代码执行时是不能被被其他中断中断。
(3)被自旋锁保护的临界区代码执行时,内核不能被抢占。
从这几个特性可以归纳出一个共性:被自旋锁保护的临界区代码执行时,它不能因为任何原因放弃处理器。
5.读写自旋锁
读写自旋锁(rwlock)是一种比自旋锁粒度更小的自旋锁机制,它保留了“自旋”的概念。但是在写操作方面,只能最多有一个写进程,在读方面,同时可拥有多个执行单元,当然读和写也不能同时进行。即:防写不防读。
6.原子变量
原子操作(分为原子整型操作和原子位操作),需要CPU硬件支持,还好现在硬件基本都支持。一般被编译成独立的一条汇编指令,所以不会被任何其他程序打断(包括中断)。
原子操作的优点编写简单;缺点是功能太简单,只能做计数操作,保护的东西太少。
7.顺序锁(seqlog)
顺序锁是对读写自旋锁的一种优化。
特点:
允许读和写操作之间的并发,也允许读与读操作之间的并发,但写与写操作之间只能是互斥的、串行
8.RCU
RCU(Read-Copy Update)是数据同步的一种方式。RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作。
RCU的原理并不复杂,应用也很简单。但代码的实现确并不是那么容易,难点都集中在了宽限期的检测上