【嵌入式Linux驱动程序-进程间通信】-完成量

2019-07-13 00:34发布

完成量

1 完成量概述

很多情况下,我们需要让某一个线程等待另一个线程执行完某个操作后,才能继续执行后续操作。Linux内核中提供完成量这种机制可实现这个同步过程。当然,信号量也是可以完成同步操作。但是完成量比信号量效率更高。此处引用《Linux设备驱动程序第三版》的一段程序,如下: struct semaphore sem; init_MUTEX_LOCKED(&sem); start_external_task(&sem); // 启动一个外部任务,用于释放信号量 down(&sem); 当外部任务调用up(&sem)后,上述代码才能继续运行down(&sem)之后的代码。   如果采用信号量替换完成量,确保信号量的竞争在较少的情况下。如果信号量竞争过多的话,性能会受损并且加锁方案需要重新设计。因此,采用完成量是最有效的方案。  

1.1 完成量的实现

完成量是实现两个或多任务之间同步的最简方式,内核采用struct completion结构来描述完成量。该结构定义在includelinuxcompletion.h文件中,其定义如下: struct comletion{ unsigned int done; wait_queue_head_t wait; };
  • done:done来用维护一个计数,当初始化一个完成量时,done成员被初始化为1。done为无符号整型数,其值必定大于等于0。当done为0时,获取完成量的线程置于等待状态;当done的值大于0时,表示等待完成量的函数可以立刻执行而不需要等待。
  • wait:wait是一个等待队列的链表头,这个链表将所有等待该完成量的进程组成一个链表结构。这个链表中,存放了正在睡眠的进程链表。

1.2 完成量的定义和初始化

在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) }

1.3 等待完成量

请求同步时,使用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函数的线程会一直等待西区,线程将不可以退出。

1.4 释放完成量

当需要同步的任务完成后,可以使用下面的两个函数唤醒等待完成量的进程。当唤醒之后,进程才能执行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);