在驱动程序开发中,一种常见的情况是:一个线程需要等待另一个线程执行完某个操作后,才能继续执行。这个工作其实信号量也可以完成,但其效率比Linux中专门针对这种情况的完成量机制要差些。
一、完成量概述
Linux中提供了一种机制,实现一个线程发送一个信号通知另一个线程开始完成某个任务,这种机制就是完成量。完成量的目的是告诉一个线程某个事件已经发生,可以在此事件基础上做你想做的另一个事件了。
二、定义
完成量由struct completion结构体表示
定义于#include<linux/complete.h>
1 2 3 4
| struct completion { unsigned int done; wait_queue_head_t wait; };
|
done 成员
done成员用来维护一个计数。当初始化一个完成量时,done成员被初始化为0。由done的类型可以知道这是一个无符号类型,其值永远大于等于0.当done等于0时,会将拥有完成量的线程置于等待状态;当done的值大于0时,表示等待完成量的函数可以立刻执行,而不需要等待。
wait成员
wait是一个等待队列的链表头,这个链表将所有等待该完成量的进程组成一个链表结构。在这个链表中,存放了正在睡眠的进程链表。
三、初始化完成量
一个完成量必须初始化才能被使用,init_completion()函数用来初始化完成量。其定义如下
1 2 3 4 5
| static inline void init_completion(struct completion *x) { x->done = 0; init_waitqueue_head(&x->wait); //初始化等待队列头 }
|
还可以使用宏DECLARE_COMPLETION定义和初始化一个完成量,定义如下:
1 2 3 4 5
| #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()函数等待一个完成量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| 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()函数之后的代码才能继续执行。这两个函数的定义如下
1 2 3 4 5 6 7 8 9 10
| 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);
|
1 2 3 4 5 6 7 8
| 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中的一个线程。
六、完成量使用示例
