设备驱动中的并发控制 (2)

信号量

信号量也是一种保护临界资源的一种方法。信号量与自旋锁的使用方法基本一样。与与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。

信号量的实现

在linux中,信号量的定义如下

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

成员变量
1. lock自旋锁
对count变量起保护作用
2. count变量
是信号量中一个非常重要的成员,可能有下面3种值
(1) 等于0的值:如果这个值等于0,表示信号量被其他进程使用,现在不可以使用这个信号量,但是在wait_list队列中没有进程等待信号量。
(2) 小于0的值:如果这个值小于0,那么表示至少有一个进程在wait_list队列中等待信号量的释放。
(3)大于0的值:如果这个值大于0,表示这个信号量是空闲的,程序可以使用这个信号量。

从此看出与自旋锁的一点不同是,自旋锁只能允许一个进程持有自旋锁,而信号量可以根据count的值,设定有多少个进程持有这个信号量。根据count的取值,可以将信号量分为二值信号量和计数信号量。

二值信号量,就是count初始化时,被设置成1时的使用量,这种类型的信号量可以强制二者同一时刻只有一个运行。

计数信号量,允许一个时刻有一个或者多个进程同时持有信号量。具体个数取决于count的取值

  1. 等待队列
    wait 是一个等待队列的链表头,这个链表将所有等待该信号量的进程组成一个链表结构,在这个链表中,存放了正在睡眠的进程链表

信号量的使用

内核提供了一系列函数对semaphore进行操作
1.定义和初始化自旋锁

struct semaphore sema;

信号量必须初始化后才能被使用,sema_init()函数用来初始化信号量,并设置sem中的count的值为val,函数原型为

 static inline void sema_init(struct semaphore *sem,int val)

相关初始化方法

void init_MUTEX(struct semaphore *sem); 

该函数用于初始化一个用于互斥的信号量,它把信号量 sem 的值设置为 1,等同于sema_init (struct semaphore *sem, 1)。

void init_MUTEX_LOCKED (struct semaphore *sem); 

该函数也用于初始化一个信号量,但它把信号量 sem 的值设置为 0,等同于sema_init (struct semaphore *sem, 0)。

此外,下面两个宏是定义并初始化信号量的“快捷方式”。

DECLARE_MUTEX(name) 
DECLARE_MUTEX_LOCKED(name) 

前者定义一个名为 name 的信号量并初始化为 1,后者定义一个名为 name 的信号量并初始化为 0。
2. 锁定信号量
进入临界区前,使用down()函数获得信号量

void down(struct semaphore *sem);

该函数会导致睡眠,不能在中断上下文中使用
另一个函数与down()函数相似

int down_interrupttible(struct semaphore *sem);

此函数进入睡眠后可以被信号唤醒,如果被信号唤醒,返回非0值,在调用down_interruptible()函数时,一般会检查返回值,判断被唤醒的原因

if (down_interruptible(&sem)) 
{ 
   return - ERESTARTSYS;
}

如果非 0,通常立即返回-ERESTARTSYS
3. 释放信号量

void up(struct semaphore *sem);

4.使用信号量
定义信号量->初始化->获得信号量->释放信号量

struct semaphore sem;

int xxx_init(void)
{
    ...
    int_MUTEX(&lock);
    ...
}

//文件打开函数
int xxx_open(struct inode *inode,strct file *filp)
{
    ...
    down(&sem);
    //不允许其他进程访问这个程序的临界资源
    ...
    return 0;
}

//文件分释放函数
int xxx_release(struct inode *inode,strct file *filp)
{
    ...
    up(&sem);
    ...

    return 0;
}

5.信号量用于同步
如果信号量被初始化为 0,则它可以用于同步,同步意味着一个执行单元的继续执行需等待另一执行单元完成某事,保证执行的先后顺序。如下图所示,线程A执行到被保护代码A之前,一直处于睡眠状态,直到线程B执行完被保护代码B并调用up()函数后,才会执行被保护代码A。

设备驱动中的并发控制 (2)_第1张图片

信号量与自旋锁的对比

自旋锁只是进行忙等待,不会休眠,信号量用于多个进程之间互斥时可能引起进程的睡眠,所以只有在一个进程对被保护的资源占用时间比进程切换时间长很多时,信号量才是一个更好的选择,否则会降低系统执行效率

完成量

一种linux中的机制,用于实现一个线程发送一个信号通知另一个线程并开始执行完某个任务。

完成量的实现

linux中完成量用 struct completion 结构体表示,定义在 include\linux\completion.h中

struct completion{
    unsigned int done;
    wait_queue_head_t wait;
};
  • done成员
    用来维护一个计数,当初始化一个完成量时,done成员被初始化为1,其值永远大于0。done等于0时,会将拥有完成量的线程置于等待状态;大于0时,表示等待完成量的函数可以立刻执行。
  • wait成员
    wait 是一个等待队列的链表头,这个链表将所有等待该完成量的进程组成一个链表结构,这个链表中存放了正在睡眠的进程链表。

完成量的使用

1.定义和初始化

struct completion com;

一个完成量必须初始化才能被使用,init_completion()

static inline void init_completion(struct completion *x)
{
    x->done=0;
    init_waitqueue_head(&x->wait);
}

也可以使用宏 DECLARE_COMPLETION 定义和初始化一个完成量

#define DECLARE_COMPLETION(work) \
    struct completion work = COMPLETION_INITIALIZER(work)
#define COMPLETION_INITIALIZER(work)  \
    { 0,__WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }

2.等待完成量
使用wait_for_completion()函数等待一个完成量

void __sched wait_for_completion(struct completion *x)

调用这个函数以后,没有一个线程完成这个完成量,那么执行wait_for_completion()函数的线程会一直等待下去,线程将不可以退出
3.释放完成量
当需要同步的任务完成后,可以使用下面的两个函数唤醒完成量。
两个函数定义如下

void complete(struct completion *x)
void complete_all(struct completion *x)

前者只唤醒一个等待的进程或者线程,后者将唤醒所有等待的进程或者线程。

4.使用完成量
定义、初始化、获得、释放

struct completion com;

int xxx_init(void)
{
    ...
    init_completion(&com);
    ...
}

int xxx_A()
{
    ...
    /*代码A*/
    wait_for_completion(&com);
    /*代码B*/
    return 0;
}

int xxx_B()
{
    ...
    /*代码C*/
    complete(&com);
    ...
}

你可能感兴趣的:(arm)