引入completion的原因
参考http://lkml.iu.edu/hypermail/linux/kernel/0107.3/0674.html
在2.4.7版本内核中引入completion时,semaphore没有锁保护。semaphore在SMP架构下会遇到竞态,虽然情况很少(从理论和实现上都是)。当时没有选择修复的原因是:
- semaphore主要是用在无竞态的情况下,而“wait for completion”默认是用在竞态的情况下。
- 因为其设计目的(非竞态情况),semaphore相当复杂而且跟体系结构有关,很难修改。
所以引入了“wait for completion”的概念:
struct completion event;
init_completion(&event);
.. pass of event pointer to waker ..
wait_for_completion(&event);
这样,改变条件的线程只需要“complete(event)”就行了。
completion有两个优势:
- 在使用模式为两种状态,完成和未完成的情况下,比semaphore用起来简便些。
- 只关注是否完成,比semaphore高效一些。
对上面提到的竞态的详细解释如下:
semaphore的竞态在于仅仅保护了semaphore内的访问,而semaphore本身可能被同时访问,可以给semaphore本身加锁,如下:
cpu #1 cpu #2
DECLARE_MUTEX_LOCKED(sem);
..
down(&sem); up(&sem);
return;
wake_up(&sem.wait) /*BOOM*/
但是waker在sleeper被唤醒并释放了semaphore的空间以后,仍然能访问semaphore数据结构,导致程序崩溃。
completion vs semaphore
现在semaphore已经没有问题了。甚至有人还提议用semaphore重新实现completion:http://lwn.net/Articles/277621/ 。
现在高版本的semaphore已经不存在问题了。只是从概念层面,completion 和semaphore 应该被用来不同的场合,completion是用来等待一个条件成力,而semaphore应该是等待一个资源。条件是没有数量的,而资源是有数量的。
上面也提到了,completion的模式更高效一些。参见:http://www.makelinux.net/ldd3/chp-5-sect-4
在实际的使用中,使用completion而非semaphore有两个原因:
- 多个线程等待某个条件,如果另一个线程调用了complete_all(),那么这些线程都会被唤醒,但是用semaphore唤醒未知数量的线程会难一些。
- waiter可能会被唤醒并释放semaphore数据结构,而唤醒它的线程还未调用up(),对于completion来说不存在这个问题。