很多情况下,我们需要让某一个线程等待另一个线程执行完某个操作后,才能继续执行后续操作。Linux内核中提供完成量这种机制可实现这个同步过程。当然,信号量也是可以完成同步操作。但是完成量比信号量效率更高。此处引用《Linux设备驱动程序第三版》的一段程序,如下:
struct semaphore sem;
init_MUTEX_LOCKED(&sem);
start_external_task(&sem); // 启动一个外部任务,用于释放信号量
down(&sem);
当外部任务调用up(&sem)后,上述代码才能继续运行down(&sem)之后的代码。
如果采用信号量替换完成量,确保信号量的竞争在较少的情况下。如果信号量竞争过多的话,性能会受损并且加锁方案需要重新设计。因此,采用完成量是最有效的方案。
完成量是实现两个或多任务之间同步的最简方式,内核采用struct completion结构来描述完成量。该结构定义在include\linux\completion.h文件中,其定义如下:
struct comletion{
unsigned int done;
wait_queue_head_t wait;
};
在Linux中,定义完成量的方法和定义普通结构体的方法相同。定义如下:
struct completion com;
一个完成量必须初始化才能使用,int_completion()函数用来初始化完成量。
动态初始化完成量:
struct inline void int_completion(struct completion *x)
{
x->done = 0;
init_waitqueue_head(&x->wait);
}
静态初始化完成量:
#define DECLARE_COMPLETION(work)\
struct completion work = COMPLETION_INITIALIZER(work)
#define COMPLETION_INITIALIZER(work)\
{
0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait)
}
请求同步时,使用wait_for_completion()函数等待一个完成量,其函数如下:
/**
* wait_for_completion: - waits for completion of a task
* @x: holds the state of this particular completion
*
* This waits to be signaled for completion of a specific task. It is NOT
* interruptible and there is no timeout.
*
* See also similar routines (i.e. wait_for_completion_timeout()) with timeout
* and interrupt capability. Also see complete().
*/
void __sched wait_for_completion(struct completion *x)
{
wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
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;
}
这个函数执行一个不会被信号打断且不会超时的等待。如果没有一个进程发送完成量,则调用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);
}
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);
}
前者只唤醒一个等待的线程,而complete_all唤醒所有等待的进程。在大部分情况下,只有一个等待者的情况下,这两个函数产生的效果是一样的。
comletion机制的典型使用是在模块退出时与内核线程的终止一起。在这个原型例子里,一些驱动的内部工作是通过一个内核线程在一个while(1)循环中进行的,当模块准备好被清理时,exit函数告知线程退出并且等待结束。为此,内核应当包含如下的特殊函数给线程使用:
void complete_and_exit(struct completion *c, long reval);