内核提供的Completion机制用于多线程之间的数据同步。类似于信号量,但是比信号量要安全。其工作原理如下:
假设我们有两个线程(A和B)以及一个共享的数据Buffer。线程A往Buffer中写入数据,线程B从Buffer中读取数据。那么线程B需要等待线程A写入完成之后才能从Buffer中读取数据。
(1)定义/声明一个completion结构的实例。即,用于挂起各线程的等待队列。分为静态声明和动态初始化:
struct completion {
unsigned int done;
wait_queue_head_t wait;
};
静态声明:
#define COMPLETION_INITIALIZER(work) \
{ 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }
#define DECLARE_COMPLETION(work) \
struct completion work = COMPLETION_INITIALIZER(work)
动态初始化:
static inline void init_completion(struct completion *x)
{
x->done = 0;
init_waitqueue_head(&x->wait);
}
(2)线程B从Buffer中读取数据前,通过 wait_for_completion(struct completion *c) 将自身挂载到 completion 队列上。wait_for_completion最终调用 do_wait_for_common来实现核心等待功能:
static inline long __sched
do_wait_for_common(struct completion *x,
long (*action)(long), long timeout, int state)
{
if (!x->done) { // done=0,表示需要挂载到等待队列
DECLARE_WAITQUEUE(wait, current); // 声明wait调度实体,代表当前线程
__add_wait_queue_tail_exclusive(&x->wait, &wait); // x->wait是等待队列头,wait代表当前线程,需要加入x->wait等待队列中
do {
if (signal_pending_state(state, current)) {
timeout = -ERESTARTSYS;
break;
}
__set_current_state(state); // 当前线程置成不可中断状态
spin_unlock_irq(&x->wait.lock); // 解自旋锁(上层调用函数已经上锁,锁定completion)
timeout = action(timeout); // action()其实是 schedule_timeout(),执行实际的线程切换,当前线程睡眠,CPU调度其他线程执行
spin_lock_irq(&x->wait.lock); // 当前线程从睡眠中恢复运行,首先上锁
} while (!x->done && timeout); // 如果done=0,并且也未超时,那么继续睡眠
__remove_wait_queue(&x->wait, &wait); // 否则,要么已经完成,要门已经超时。两种情况下都需要将wait从 x->wait等待队列移除
if (!x->done)
return timeout;
}
x->done--;
return timeout ?: 1;
}
(3)线程A往Buffer中写入数据后,通过complete(struct completion *c) 唤醒挂载在completion上的等待线程(本例中是线程B):
void complete(struct completion *x)
{
unsigned long flags;
spin_lock_irqsave(&x->wait.lock, flags); // 上自旋锁
x->done++; // 初始done=0,完成一次+1
__wake_up_locked(&x->wait, TASK_NORMAL, 1); // 唤醒 x->wait 等待队列上的线程
spin_unlock_irqrestore(&x->wait.lock, flags); // 解自旋锁
}
从上面completion的简单使用方式和API来看,completion的机制还是比较简单的,本质上就是一个等待队列。