在Linux内核中,completion是一种简单的同步机制,标志"things may proceed"。
要使用completion,必须在文件中包含<linux/completion.h>,同时创建一个类型为struct completion的变量。
Completion在内核中的实现基于等待队列,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); }可见,两种初始化都将用于同步的done原子量置位了0,后面我们会看到,该变量在wait相关函数中减一,在complete系列函数中加一。
可以看到wait_for_completion的核心函数是do_wait_for_common,action是schedule_timeout,timeout为MAX_SCHEDULE_TIMEOUT,state是TASK_UNINTERRUPTIBLE
#define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk) static inline long __sched do_wait_for_common(struct completion *x, long (*action)(long), long timeout, int state) { if (!x->done) { DECLARE_WAITQUEUE(wait, current);//声明一个wait_queue_t的实例,并且把current赋值给其private成员 __add_wait_queue_tail_exclusive(&x->wait, &wait);//把该实例添加到completion的wait队列上 do { if (signal_pending_state(state, current)) { timeout = -ERESTARTSYS; break; } __set_current_state(state); spin_unlock_irq(&x->wait.lock); timeout = action(timeout); spin_lock_irq(&x->wait.lock); } while (!x->done && timeout);//如果事件没有完成,或者时间还没有到,则继续循环 __remove_wait_queue(&x->wait, &wait);//把wait实例添加到completion的wait if (!x->done) return timeout; } x->done--; return timeout ?: 1; }先设置进程TASK_UNINTERRUPTIBLE状态,可以看到主要函数是在action,即schedule_timeout函数,由于我们这边设置了MAX_SCHEDULE_TIMEOUT,所以schedule_timeout的作用就是直接调用schedule进行调度,然后下次被唤醒,就把MAX_SCHEDULE_TIMEOUT返回,所以其实在do_wait_for_common函数中会一直在while中循环,直到done变为1为止。
curr->func对应哪个函数呢,上面DECLARE_WAITQUEUE(wait, current);的时候会设置该函数:
#define __WAITQUEUE_INITIALIZER(name, tsk) { \ .private = tsk, \ .func = default_wake_function, \ .task_list = { NULL, NULL } } #define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)所以调用default_wake_function
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, void *key) { //private数据结构存储的是要唤醒的那个任务的任务描述符task_struct return try_to_wake_up(curr->private, mode, wake_flags); } static int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) { unsigned long flags; int cpu, success = 0; smp_wmb(); raw_spin_lock_irqsave(&p->pi_lock, flags); if (!(p->state & state)) goto out; success = 1; /* we're going to change ->state */ cpu = task_cpu(p); if (p->on_rq && ttwu_remote(p, wake_flags)) goto stat; #ifdef CONFIG_SMP /* * If the owning (remote) cpu is still in the middle of schedule() with * this task as prev, wait until its done referencing the task. */ while (p->on_cpu) cpu_relax(); /* * Pairs with the smp_wmb() in finish_lock_switch(). */ smp_rmb(); p->sched_contributes_to_load = !!task_contributes_to_load(p); p->state = TASK_WAKING; if (p->sched_class->task_waking) p->sched_class->task_waking(p); cpu = select_task_rq(p, SD_BALANCE_WAKE, wake_flags); if (task_cpu(p) != cpu) { wake_flags |= WF_MIGRATED; set_task_cpu(p, cpu); } #endif /* CONFIG_SMP */ ttwu_queue(p, cpu); stat: ttwu_stat(p, cpu, wake_flags); out: raw_spin_unlock_irqrestore(&p->pi_lock, flags); return success; }try_to_wake_up函数中会把之前等待的进程状态设置为TASK_RUNNING,并移入调度队列,等待调度并唤醒等待线程。