Linux驱动设计——并发控制

四种并发控制机制:原子操作、自旋锁、信号量和完成量。

 

原子变量操作

原子变量操作绝对不会再执行完毕前被任何其他任务或事件打断。原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树中的include/asm/atomic.h文件中,它们都是使用汇编语言实现的。

常用于多个应用程序对同一个共享的值进行操作的情况。

 

自旋锁

自旋锁是实现信号量和完成量的基础。对资源有很好的保护作用。

Linux系统中提供了一些锁机制来避免竞争条件,最简单的一种就是自旋锁。引入锁的机制是因为单独的原子操作已经不能满足复杂的内核设计需要。

Linux中一般可以认为有两种锁:自旋锁和信号量。这两种锁是为了解决内核中不同的问题开发的。

自旋锁的类型(结构体):struct spinlock_t

自旋锁的使用

在驱动程序中,有的设备只允许打开一次,那么就需要一个自旋锁来保护表示设备打开和关闭状态的变量count。

  1. 定义和初始化自旋锁
  2. 锁定自旋锁
  3. 释放自旋锁
  4. 使用自旋锁

自旋锁使用注意事项

自旋锁是一种忙等待,当自旋锁条件不满足时,会一直不断的循环条件是否满足。所以适合短时间锁定的轻量级加锁机制。

自旋锁不能递归使用。因为自旋锁被设计为在不同线程或函数之间同步。(?)

 

信号量

Linux提供了两种信号量,其中一种用于内核程序中,另一种用于用户程序中。

信号量也是一种保护临界资源的一种有用方法。信号量与自旋锁使用方法基本一样。与自旋锁相比,信号量只有当得到信号量的进程或者线程时才能够进入临界区,执行临界代码。

信号量与自旋锁最大的不同:当一个进程试图获取一个锁定的信号量时,进程不会像自旋锁一样在远处忙等待。而是采用如下方式:当获取的信号量没有被释放时,进程会将自身加入一个等待队列中去睡眠,直到拥有信号量的进程释放掉信号量后,处于等待队列的那个进程才被唤醒。然后继续执行。所以这就要求使用信号量的进程是能够睡眠的进程(像中断处理程序就不能使用信号量)。

信号量的结构类型:struct semaphore

struct semaphore{
    spinlock_t     lock;
    unsigned int    count;
    struct list_head    wait_list;
};

信号量的使用

  1. 定义和初始化自旋锁
  2. 锁定信号量
  3. 释放信号量
  4. 使用信号量
  5. 信号量用于同步操作(可以用于保证线程的执行先后顺序)

自旋锁与信号量的选择

当代码在很短的时间内可以执行完成时,那么选择自旋锁;当一个进程对被保护资源占用时间比进程切换的时间长很多时,选择信号量。

 

完成量

针对一个线程要等待另一个线程执行完成某个操作后才能继续执行的情况。虽然信号量也可以完成这项工作,但是效率比完成量要差些。

完成量实现一个线程发送信号通知另一个线程开始完成某个任务。

完成量的实现

完成量的结构:include\linux\completion.h

struct completion{
    unsigned int done;
    wait_queue_head_t wait;
};

完成量的使用

  1. 定义和初始化完成量
  2. 等待完成量
  3. 释放完成量
  4. 使用完成量

 

To be continue...

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。