brintos

brintos / linux-shallow public Read only

0
0
Text · 11.6 KiB · bc82185 Raw
257 lines · plain
1.. include:: ../disclaimer-zh_CN.rst2 3:Original: Documentation/scheduler/completion.rst4 5:翻译:6 7 司延腾 Yanteng Si <siyanteng@loongson.cn>8 9:校译:10 11 唐艺舟 Tang Yizhou <tangyeechou@gmail.com>12 13=======================================14完成 - "等待完成" 屏障应用程序接口(API)15=======================================16 17简介:18-----19 20如果你有一个或多个线程必须等待某些内核活动达到某个点或某个特定的状态,完成可以为这21个问题提供一个无竞争的解决方案。从语义上讲,它们有点像pthread_barrier(),并且使22用的案例类似23 24完成是一种代码同步机制,它比任何滥用锁/信号量和忙等待循环的行为都要好。当你想用yield()25或一些古怪的msleep(1)循环来允许其它代码继续运行时,你可能想用wait_for_completion*()26调用和completion()来代替。27 28使用“完成”的好处是,它们有一个良好定义、聚焦的目标,这不仅使得我们很容易理解代码的意图,29而且它们也会生成更高效的代码,因为所有线程都可以继续执行,直到真正需要结果的时刻。而且等30待和信号都高效的使用了低层调度器的睡眠/唤醒设施。31 32完成是建立在Linux调度器的等待队列和唤醒基础设施之上的。等待队列中的线程所等待的33事件被简化为 ``struct completion`` 中的一个简单标志,被恰如其名地称为‘done’。34 35由于完成与调度有关,代码可以在kernel/sched/completion.c中找到。36 37 38用法:39-----40 41使用完成需要三个主要部分:42 43 - 'struct completion' 同步对象的初始化44 - 通过调用wait_for_completion()的一个变体来实现等待部分。45 - 通过调用complete()或complete_all()实现发信端。46 47也有一些辅助函数用于检查完成的状态。请注意,虽然必须先做初始化,但等待和信号部分可以48按任何时间顺序出现。也就是说,一个线程在另一个线程检查是否需要等待它之前,已经将一个49完成标记为 "done",这是完全正常的。50 51要使用完成API,你需要#include <linux/completion.h>并创建一个静态或动态的52``struct completion`` 类型的变量,它只有两个字段::53 54	struct completion {55		unsigned int done;56		wait_queue_head_t wait;57	};58 59结构体提供了->wait等待队列来放置任务进行等待(如果有的话),以及->done完成标志来表明它60是否完成。61 62完成的命名应当与正在被同步的事件名一致。一个好的例子是::63 64	wait_for_completion(&early_console_added);65 66	complete(&early_console_added);67 68好的、直观的命名(一如既往地)有助于代码的可读性。将一个完成命名为 ``complete``69是没有帮助的,除非其目的是超级明显的...70 71 72初始化完成:73-----------74 75动态分配的完成对象最好被嵌入到数据结构中,以确保在函数/驱动的生命周期内存活,以防76止与异步complete()调用发生竞争。77 78在使用wait_for_completion()的_timeout()或_killable()/_interruptible()变体79时应特别小心,因为必须保证在所有相关活动(complete()或reinit_completion())发生80之前不会发生内存解除分配,即使这些等待函数由于超时或信号触发而过早返回。81 82动态分配的完成对象的初始化是通过调用init_completion()来完成的::83 84	init_completion(&dynamic_object->done);85 86在这个调用中,我们初始化 waitqueue 并将 ->done 设置为 0,即“not completed”或87“not done”。88 89重新初始化函数reinit_completion(),只是将->done字段重置为0(“not done”),而90不触及等待队列。这个函数的调用者必须确保没有任何令人讨厌的wait_for_completion()91调用在并行进行。92 93在同一个完成对象上调用init_completion()两次很可能是一个bug,因为它将队列重新初始94化为一个空队列,已排队的任务可能会“丢失”--在这种情况下使用reinit_completion(),但95要注意其他竞争。96 97对于静态声明和初始化,可以使用宏。98 99对于文件范围内的静态(或全局)声明,你可以使用 DECLARE_COMPLETION()::100 101	static DECLARE_COMPLETION(setup_done);102	DECLARE_COMPLETION(setup_done);103 104注意,在这种情况下,完成在启动时(或模块加载时)被初始化为“not done”,不需要调用105init_completion()。106 107当完成被声明为一个函数中的局部变量时,那么应该总是明确地使用108DECLARE_COMPLETION_ONSTACK()来初始化,这不仅仅是为了让lockdep正确运行,也是明确表109名它有限的使用范围是有意为之并被仔细考虑的::110 111	DECLARE_COMPLETION_ONSTACK(setup_done)112 113请注意,当使用完成对象作为局部变量时,你必须敏锐地意识到函数堆栈的短暂生命期:在所有114活动(如等待的线程)停止并且完成对象完全未被使用之前,函数不得返回到调用上下文。115 116再次强调这一点:特别是在使用一些具有更复杂结果的等待API变体时,比如超时或信号117(_timeout(), _killable()和_interruptible())变体,等待可能会提前完成,而对象可118能仍在被其他线程使用 - 从wait_on_completion*()调用者函数的返回会取消分配函数栈,如119果complete()在其它某线程中完成调用,会引起微小的数据损坏。简单的测试可能不会触发这120些类型的竞争。121 122如果不确定的话,使用动态分配的完成对象, 最好是嵌入到其它一些生命周期长的对象中,长到123超过使用完成对象的任何辅助线程的生命周期,或者有一个锁或其他同步机制来确保complete()124不会在一个被释放的对象中调用。125 126在堆栈上单纯地调用DECLARE_COMPLETION()会触发一个lockdep警告。127 128等待完成:129---------130 131对于一个线程来说,要等待一些并发活动的完成,它要在初始化的完成结构体上调用132wait_for_completion()::133 134	void wait_for_completion(struct completion *done)135 136一个典型的使用场景是::137 138	CPU#1					CPU#2139 140	struct completion setup_done;141 142	init_completion(&setup_done);143	initialize_work(...,&setup_done,...);144 145	/* run non-dependent code */		/* do setup */146 147	wait_for_completion(&setup_done);	complete(setup_done);148 149这并不意味着调用wait_for_completion()和complete()有任何特定的时间顺序--如果调150用complete()发生在调用wait_for_completion()之前,那么等待方将立即继续执行,因为151所有的依赖都得到了满足;如果没有,它将阻塞,直到complete()发出完成的信号。152 153注意,wait_for_completion()是在调用spin_lock_irq()/spin_unlock_irq(),所以154只有当你知道中断被启用时才能安全地调用它。从IRQs-off的原子上下文中调用它将导致难以检155测的错误的中断启用。156 157默认行为是不带超时的等待,并将任务标记为“UNINTERRUPTIBLE”状态。wait_for_completion()158及其变体只有在进程上下文中才是安全的(因为它们可以休眠),但在原子上下文、中断上下文、IRQ159被禁用或抢占被禁用的情况下是不安全的--关于在原子/中断上下文中处理完成的问题,还请看下面的160try_wait_for_completion()。161 162由于wait_for_completion()的所有变体都可能(很明显)阻塞很长时间,这取决于它们所等163待的活动的性质,所以在大多数情况下,你可能不想在持有mutex锁的情况下调用它。164 165 166wait_for_completion*()可用的变体:167---------------------------------168 169下面的变体都会返回状态,在大多数(/所有)情况下都应该检查这个状态--在故意不检查状态的情170况下,你可能要做一个说明(例如,见arch/arm/kernel/smp.c:__cpu_up())。171 172一个常见的问题是不准确的返回类型赋值,所以要注意将返回值赋值给适当类型的变量。173 174检查返回值的具体含义也可能被发现是相当不准确的,例如,像这样的构造::175 176	if (!wait_for_completion_interruptible_timeout(...))177 178...会在成功完成和中断的情况下执行相同的代码路径--这可能不是你想要的结果::179 180	int wait_for_completion_interruptible(struct completion *done)181 182这个函数在任务等待时标记为TASK_INTERRUPTIBLE。如果在等待期间收到信号,它将返回183-ERESTARTSYS;否则为0::184 185	unsigned long wait_for_completion_timeout(struct completion *done, unsigned long timeout)186 187该任务被标记为TASK_UNINTERRUPTIBLE,并将最多超时等待“timeout”个jiffies。如果超时发生,则188返回0,否则返回剩余的时间(但至少是1)。189 190超时最好用msecs_to_jiffies()或usecs_to_jiffies()计算,以使代码在很大程度上不受191HZ的影响。192 193如果返回的超时值被故意忽略,那么注释应该解释原因194(例如,见drivers/mfd/wm8350-core.c wm8350_read_auxadc()::195 196	long wait_for_completion_interruptible_timeout(struct completion *done, unsigned long timeout)197 198这个函数传递一个以jiffies为单位的超时,并将任务标记为TASK_INTERRUPTIBLE。如果收到199信号,则返回-ERESTARTSYS;否则,如果完成超时,则返回0;如果完成了,则返回剩余的时间200(jiffies)。201 202更多的变体包括_killable,它使用TASK_KILLABLE作为指定的任务状态,如果它被中断,将返203回-ERESTARTSYS,如果完成了,则返回0。它也有一个_timeout变体::204 205	long wait_for_completion_killable(struct completion *done)206	long wait_for_completion_killable_timeout(struct completion *done, unsigned long timeout)207 208wait_for_completion_io()的_io变体的行为与非_io变体相同,只是将等待时间计为“IO等待”,209这对任务在调度/IO统计中的计算方式有影响::210 211	void wait_for_completion_io(struct completion *done)212	unsigned long wait_for_completion_io_timeout(struct completion *done, unsigned long timeout)213 214 215对完成发信号:216-------------217 218一个线程想要发出信号通知继续的条件已经达到,就会调用complete(),向其中一个等待者发出信219号表明它可以继续::220 221	void complete(struct completion *done)222 223... or calls complete_all() to signal all current and future waiters::224 225	void complete_all(struct completion *done)226 227即使在线程开始等待之前就发出了完成的信号,信号传递也会继续进行。这是通过等待者228“consuming”(递减)“struct completion” 的完成字段来实现的。等待的线程唤醒的顺序229与它们被排队的顺序相同(FIFO顺序)。230 231如果多次调用complete(),那么这将允许该数量的等待者继续进行--每次调用complete()将232简单地增加已完成的字段。但多次调用complete_all()是一个错误。complete()和233complete_all()都可以在IRQ/atomic上下文中安全调用。234 235在任何时候,只能有一个线程在一个特定的 “struct completion”上调用 complete() 或236complete_all() - 通过等待队列自旋锁进行序列化。任何对 complete() 或237complete_all() 的并发调用都可能是一个设计错误。238 239从IRQ上下文中发出完成信号 是可行的,因为它将正确地用240spin_lock_irqsave()/spin_unlock_irqrestore()执行锁操作241 242 243try_wait_for_completion()/completion_done():244--------------------------------------------245 246try_wait_for_completion()函数不会将线程放在等待队列中,而是在需要排队(阻塞)线247程时返回false,否则会消耗一个已发布的完成并返回true::248 249	bool try_wait_for_completion(struct completion *done)250 251最后,为了在不以任何方式改变完成的情况下检查完成的状态,可以调用completion_done(),252如果没有发布的完成尚未被等待者消耗,则返回false(意味着存在等待者),否则返回true::253 254	bool completion_done(struct completion *done)255 256try_wait_for_completion()和completion_done()都可以在IRQ或原子上下文中安全调用。257