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

完成量

1 完成量概述

很多情况下,我们需要让某一个线程等待另一个线程执行完某个操作后,才能继续执行后续操作。Linux内核中提供完成量这种机制可实现这个同步过程。当然,信号量也是可以完成同步操作。但是完成量比信号量效率更高。此处引用《Linux设备驱动程序第三版》的一段程序,如下:

struct semaphore sem;
init_MUTEX_LOCKED(&sem);
start_external_task(&sem);    // 启动一个外部任务,用于释放信号量
down(&sem);

当外部任务调用up(&sem)后,上述代码才能继续运行down(&sem)之后的代码。

如果采用信号量替换完成量,确保信号量的竞争在较少的情况下。如果信号量竞争过多的话,性能会受损并且加锁方案需要重新设计。因此,采用完成量是最有效的方案。

1.1 完成量的实现

完成量是实现两个或多任务之间同步的最简方式,内核采用struct completion结构来描述完成量。该结构定义在include\linux\completion.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);

猜你喜欢

转载自blog.csdn.net/santapasserby/article/details/81772304