日期:2014-05-16  浏览次数:20652 次

Linux设备驱动开发详解总结(二)之并发与竞争

       Linux设备驱动中必须解决一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态,在当今的Linux内核中,支持SMP与内核抢占的环境下,更是充满了并发与竞态。幸运的是,Linux 提供了多钟解决竞态问题的方式,这些方式适合不同的应用场景。例如:中断屏蔽、原子操作、自旋锁、信号量等等并发控制机制。

 1.1 并发与竞态

      并发是指多个执行单元同时、并发被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态。

      临界区概念是为解决竞态条件问题而产生的,一个临界区是一个不允许多路访问的受保护的代码,这段代码可以操纵共享数据或共享服务。临界区操纵坚持互斥锁原则(当一个线程处于临界区中,其他所有线程都不能进入临界区)。然而,临界区中需要解决的一个问题是死锁。

1.2 中断屏蔽

     在单CPU 范围内避免竞态的一种简单而省事的方法是进入临界区之前屏蔽系统的中断。CPU  一般都具有屏蔽中断和打开中断的功能,这个功能可以保证正在执行的内核执行路径不被中断处理程序所抢占,有效的防止了某些竞态条件的发送,总之,中断屏蔽将使得中断与进程之间的并发不再发生。

中断屏蔽的使用方法:
local_irq_disable()  /*屏蔽本地CPU 中断*/

.....

critical section  /*临界区受保护的数据*/

.....

local_irq_enable()  /*打开本地CPU  中断*/

 

      由于Linux 的异步I/O、进程调度等很多重要操作都依赖于中断,中断对内核的运行非常重要,在屏蔽中断期间的所有中断都无法得到处理,因此长时间屏蔽中断是非常危险的,有可能造成数据的丢失,甚至系统崩溃的后果。这就要求在屏蔽了中断后,当前的内核执行路径要尽快地执行完临界区代码。

      与local_irq_disable() 不同的是,local_irq_save(flags) 除了进行禁止中断的操作外,还保存当前CPU 的中断状态位信息;与local_irq_enable() 不同的是,local_irq_restore(flags) 除了打开中断的操作外,还恢复了CPU 被打断前的中断状态位信息。

 

1.3  原子操作

      原子操作指的是在执行过程中不会被别的代码路径所中断的操作,Linux 内核提供了两类原子操作——位原子操作和整型原子操作。它们的共同点是在任何情况下都是原子的,内核代码可以安全地调用它们而不被打断。然而,位和整型变量原子操作都依赖于底层CPU 的原子操作来实现,因此这些函数的实现都与 CPU 架构密切相关。

1.3.1 整型原子操作

1、设置原子变量的值

void  atomic_set(atomic *v,int i);  /*设置原子变量的值为 i  */

atomic_t  v = ATOMIC_INIT(0);  /*定义原子变量 v 并初始化为 0  */

2、获取原子变量的值

int  atomic_read(atomic_t  *v)  /*返回原子变量 v 的当前值*/

3、原子变量加/减

void  atomic_add(int  i,atomic_t  *v) /*原子变量增加 i  */

void  atomic_sub(int  i,atomic_t  *v) /*原子变量减少 i  */

4、原子变量自增/自减

void atomic_inc(atomic_t  *v)  /*原子变量增加 1 */

void atomic_dec(atomic_t  *v)  /*原子变量减少 1 */

5、操作并测试

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 ,若为 0 返回true,否则返回false。注意:没有atomic_add_and_test(int i, atomic_t  *v)。

6、操作并返回

int atomic_add_return(int i, atomic_t  *v);

int atomic_sub_return(int i, atomic_t  *v);