一、completion完成量

completion是一种内核线程同步机制. 如果有一个或多个线程必须等待某个内核活动达到某个点或某个特定状态,那么completion完成量可以为这个问题提供一个无竞争的解决方案。当想使用yield()或msleep(1)循环来允许其他事情(线程)继续执行时,可以使用wait_for_complete()和complete()来代替。

使用完成量可以提高代码更容易阅读。

二、使用方法

通常completion的使用为一下三步:

(1)初始化struct completion同步对象。

        init_completion(&data->bl_completion);  /* for fw update in bootloader */

(2)通过调用wait_for_completion()函数等待完成量。

        mxt_wait_for_completion(data, &data->bl_completion,MXT_FW_CHG_TIMEOUT);

(3)调用complete()complete_all发送完成信号。

        

注意,虽然首先必须进行完成量的初始化,但等待和发送信号是可以以任何顺序进行的。例如:一个线程在另一个线程中检查它是否需要等待它之前,将一个完成标记为“完成”是完全正常的。

如果需要使用完成量,需要#include 头文件并创建一个静态或动态的类型struct completion变量。该结构变量定义如下:

struct completion {
        unsigned int done;
        wait_queue_head_t wait;
};

wait等待队列用来放置任务等待(如果存在),done用来指示任务是否完成。完成量的命名应该引用正在同步的事件,例如:

wait_for_completion(&early_console_added);

complete(&early_console_added);

early_console_added这个完成量的命名就比较直观和意图清晰。

2-1 初始化完成量

1. 动态初始化完成量

动态分配的完成对象最好将其嵌入到在函数/驱动程序的生命周期内是活的数据结构中,防止发生与异步complete()调用的竞争。

在使用wait_for_completion()的_timeout()或_killable()/_interruptible()变体时应特别注意,因为必须确保在所有相关活动(complete()或reinit_completion())发生之前不会发生内存回收。

通过调用init_completion()来初始化动态分配的完成对象,,如以下代码:

init_completion(&dynamic_object->done);

在这个调用中,初始化了等待队列,并将done设置为0,即“未完成”。

也可以使用reinit_completion来初始化完成量。但是reinit_completion()只是将done字段重置为0(“未完成”),而不设置等待队列。因此,这个使用这个函数时必须确保没有并发的、紧急wait_for_completion()调用。

在同一个完成对象上调用两次init_completion()很可能会出现bug,因为该操作将队列重新初始化为空队列,进入队列的任务可能会“丢失”。在这种情况下使用reinit_completion()比较合适,但要注意其他竞争。

2. 静态声明和初始化完成量

对于文件范围内的静态(或全局)的完成量声明,可以使用DECLARE_COMPLETION():

static DECLARE_COMPLETION(setup_done);
DECLARE_COMPLETION(setup_done);

注意,在这种情况下,完成量是在linux内核启动时(或模块加载时)初始化为“未完成”,不需要调用init_completion()。

当一个完成量被声明为函数中的局部变量时,应该总是显式地使用DECLARE_COMPLETION_ONSTACK()来初始化,这不仅是为了适配lockdep,也是为了表明设计者考虑到了有限的作用域:

DECLARE_COMPLETION_ONSTACK(setup_done)

【注意】当将完成对象作为局部变量时,必须敏感地意识到函数栈的生命周期很短:在所有活动(如等待线程)停止并且完成对象完全未使用之前,函数是不能返回到调用上下文。

【特别注意】当使用一些具有更复杂结果的等待API变体时,例如超时或信号(_timeout(), _killable()和_interruptible())的变体,当对象仍被其他线程使用时,等待可能会提前完成—wait_on_completion()调用函数的返回将释放函数堆栈,如果在其他线程中完成complete(),则会导致数据损坏。在开发中,这类竞争问题将会很难发现和测试。

2-2 等待完成量

为了让线程等待并发操作的完成,该线程应该对初始化的完成结构调用wait_for_completion():

void wait_for_completion(struct completion *done)

并不意味着wait_for_completion()和对complete()的调用有任何特定的顺序—如果对complete()的调用发生在对wait_for_completion()的调用之前,那么等待端将直接继续,因为所有依赖都满足了;如果没有,它将阻塞,直到complete()发出完成信号。

请注意,wait_for_completion()正在调用spin_lock_irq()/spin_unlock_irq(),所以只有当知道中断已启用时才能安全地调用它。从irq -off原子上下文调用它将导致难以检测的虚假中断启用。

默认行为是不超时地等待,并将任务标记为不可中断,wait_for_completion()及其变体只在进程上下文中是安全的(因为它们可以睡眠),但在原子上下文中、中断上下文中则不是安全的,在禁用IRQs或禁用抢占的情况下也是不安全的。这时候使用try_wait_for_completion()函数来处理原子/中断上下文中的完成。

2-3 发送信号量完成

一个线程想要通知某个条件已经达到,就会调用complete()来通知其中一个等待者它可以继续执行操作:

void complete(struct completion *done)

或调用complete_all()来通知所有当前和未来的等待者:

void complete_all(struct completion *done)

即使在线程开始等待之前发出完成信号,信号也将按照预期工作。这是通过递减struct completion '的done字段来实现的。等待线程的唤醒顺序与它们进入队列时相同(FIFO顺序)。

如果complete()被多次调用,那么将允许该数量的等待者继续-每次对complete()的调用只会增加done字段。但是多次调用complete_all()是一个bug。complete()和complete_all()都可以在IRQ/atomic上下文中安全地调用。

在任何时候都只能有一个线程对特定的' struct completion '调用complete()或complete_all()—通过等待队列自旋锁序列化。对complete()或complete_all()的任何此类并发调用都可能是一个设计错误。

从中断上下文发送完成信号是可以的,因为它将使用spin_lock_irqsave()/spin_unlock_irqrestore()进行锁定,并且永远不会睡眠。

try_wait_for_completion()/completion_done()

try_wait_for_completion()函数不会将线程放到等待队列中,但如果它需要排队(阻塞)线程,则返回false,否则它使用已提交的完成量并返回true:

bool try_wait_for_completion(struct completion *done)

最后,要在不改变完成状态的情况下检查完成状态,可以使用completion_done()函数,如果没有提交的完成未被等待者使用(意味着有等待者),则返回false,否则返回true。该函数原型如下:

bool completion_done(struct completion *done)

try_wait_for_completion()和completion_done()在IRQ或原子上下文中调用都是安全的。

三、代码示例分析

这个是Atmel在github上的代码, 

// 完成量初始化

init_completion(&data->bl_completion);  /* for fw update in bootloader */


// 该函数中进行对应的completion使用.
static int mxt_check_bootloader(struct mxt_data *data, unsigned int state,
				bool wait)
{
	struct device *dev = &data->client->dev;
	u8 val;
	int ret;

recheck:
	if (wait) {
		/*
		 * In application update mode, the interrupt
		 * line signals state transitions. We must wait for the
		 * CHG assertion before reading the status byte.
		 * Once the status byte has been read, the line is deasserted.
		 */
		ret = mxt_wait_for_completion(data, &data->bl_completion, // -----> 内核线程等在这个完成量上.
					      MXT_FW_CHG_TIMEOUT);
		if (ret) {
			/*
			 * TODO: handle -ERESTARTSYS better by terminating
			 * fw update process before returning to userspace
			 * by writing length 0x000 to device (iff we are in
			 * WAITING_FRAME_DATA state).
			 */
			dev_err(dev, "Update wait error %d\n", ret);
			return ret;
		}
	}

	ret = mxt_bootloader_read(data, &val, 1);
	
    .......
	
	return 0;
}

//中断处理函数 中进行complete的通知

static irqreturn_t mxt_interrupt(int irq, void *dev_id)
{
	struct mxt_data *data = dev_id;



	if (data->in_bootloader) {   //  -------------------> 判断如果还是在bootloader中.
		/* bootloader state transition completion */
		complete(&data->bl_completion);  //-------------> bl_completion.
		return IRQ_HANDLED;
	}

	if (!data->object_table)
		return IRQ_HANDLED;

	if (data->irq_processing) {
		if (data->T44_address || data->T144_address) {
			return mxt_process_messages_t44_t144(data);
		} else {
			return mxt_process_messages(data);
		}
	}

	return IRQ_HANDLED;
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐