内核提供的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的机制还是比较简单的,本质上就是一个等待队列。