Yuhang Zheng

完成量(转载)

N 人看过

在驱动程序开发中,一种常见的情况是:一个线程需要等待另一个线程执行完某个操作后,才能继续执行。这个工作其实信号量也可以完成,但其效率比Linux中专门针对这种情况的完成量机制要差些。

一、完成量概述

Linux中提供了一种机制,实现一个线程发送一个信号通知另一个线程开始完成某个任务,这种机制就是完成量。完成量的目的是告诉一个线程某个事件已经发生,可以在此事件基础上做你想做的另一个事件了。

二、定义

完成量由struct completion结构体表示
定义于#include<linux/complete.h>

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

done 成员
done成员用来维护一个计数。当初始化一个完成量时,done成员被初始化为0。由done的类型可以知道这是一个无符号类型,其值永远大于等于0.当done等于0时,会将拥有完成量的线程置于等待状态;当done的值大于0时,表示等待完成量的函数可以立刻执行,而不需要等待。

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

三、初始化完成量

一个完成量必须初始化才能被使用,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) }

这个宏的功能跟init_completion()函数的实现是一样的,只是定义合初始化一个完成量的简单实现而已

四、等待完成量

当要实现同步时,可以使用wait_for_completion()函数等待一个完成量。

void __sched wait_for_completion(struct completion *x)
{
wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);


static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{
might_sleep();

spin_lock_irq(&x->wait.lock);
timeout = do_wait_for_common(x, timeout, state);
spin_unlock_irq(&x->wait.lock);
return timeout;
}


static inline long __sched
do_wait_for_common(struct completion *x, long timeout, int state)
{
if (!x->done) {
DECLARE_WAITQUEUE(wait, current);

__add_wait_queue_tail_exclusive(&x->wait, &wait);
do {
if (signal_pending_state(state, current)) {
timeout = -ERESTARTSYS;
break;
}
__set_current_state(state);
spin_unlock_irq(&x->wait.lock);
timeout = schedule_timeout(timeout);
spin_lock_irq(&x->wait.lock);
} while (!x->done && timeout);
__remove_wait_queue(&x->wait, &wait);
if (!x->done)
return timeout;
}
x->done--;
return timeout ?: 1;
}

分析函数的实现可以知道,wait_for_completion函数中,如果complete->done的值等于0,那么线程会进入睡眠。如果此时的值大于0,那么wait_for_completion()函数会将complete->done的值减1,然后继续向下执行。前面说过complete->done的值永远大于等于0。

wait_for_completion()函数会执行一个不会被信号中断的等待。如果调用这个函数之后,没有一个线程完成这个完成量,那么执行wait_for_completion()函数的线程会一直等待下去,线程将不可以退出。

五、释放完成量

当需要同步的任务完成后,可以使用下面的两个函数唤醒完成量。当唤醒之后,wait_for_completion()函数之后的代码才能继续执行。这两个函数的定义如下

void complete(struct completion *x)
{
unsigned long flags;

spin_lock_irqsave(&x->wait.lock, flags);
x->done++;
__wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL);
spin_unlock_irqrestore(&x->wait.lock, flags);
}
EXPORT_SYMBOL(complete);
void complete_all(struct completion *x)
 {
     unsigned long flags;spin_lock_irqsave(&x->wait.lock, flags);
     x->done += UINT_MAX/2;
     __wake_up_common(&x->wait, TASK_NORMAL, 0, 0, NULL);
     spin_unlock_irqrestore(&x->wait.lock, flags);
}
EXPORT_SYMBOL(complete_all);

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

complete函数会将complete->done的值加1,然后唤醒complete->wait中的一个线程。

六、完成量使用示例