Linux内核中的“等待队列”是一种数据结构,用于管理正在等待某种条件变为真的线程。它们是线程在内核空间中阻塞(或“睡眠”)的正常方法。多年来,等待队列机制已经发展成为一个相当复杂和复杂的内核子系统。但是,现在可以使用为实时树开发的等待队列变体来简化该代码。结果可能是内核中大量的代码搅动。
看一下<linux/wait.h> from the 2.0 kernel揭示了一个简单的数据结构:等待线程的基本链接列表。等待队列上的wake_up()
调用将遍历列表,从而使每个线程进入可运行状态。没有比这更多的东西了。然后,在1999年,臭名昭著的Mindcraft研究指出了Linux的一些性能缺陷。其中之一是“雷声成群”问题,其中多个过程将被唤醒并争夺一种资源,而只有其中一个可以获得。结果,添加了“排他的等待”功能-只有可能在许多等待线程中的第一个唤醒。然后在2.5系列中添加了回调机制,以便在其他情况下可以阻塞时,新的异步I / O工具可以介入。等等。
最终结果是,数据结构比2.0天的数据结构更大,更复杂。但是,对于实时树而言,最有问题的是回调功能。由于这些回调可以进入睡眠状态,因此它们阻止使用“原始”自旋锁来保护等待队列本身。为解决此问题,Thomas Gleixner创建了一种新的“简单等待队列”机制,该机制将免除大多数添加的功能,因此适合在实时内核中使用。
2013年Realtime Linux Workshop将这一代码确定为相对容易进入主线的候选对象。作为回应,Paul Gortmaker提取了简单的等待队列工具,并将发布的补丁系列发布以供审查。
该代码看起来很像回到2.0内核。在此期间,等待队列获得的许多功能已被剥夺,留下了看上去很熟悉的等待线程链接列表。没有独占的唤醒功能,没有回调功能,并且没有太多其他功能。但是,有一种等待队列机制,足以满足内核中大多数等待队列用户(其中有很多)的需求。
该API与现有的等待队列相似。等待队列条目和等待队列头定义为:
#include <linux/swait.h>
DEFINE_SWAITER(name);
DEFINE_SWAIT_HEAD(name);
需要直接调用schedule()以使调用线程进入睡眠状态的低级API如下所示:
void swait_prepare(struct swait_queue_head *head, struct swaiter *w, int state);
void swait_finish(struct swait_queue_head *head, struct swaiter *w);
swait_prepare()
调用用于将进程添加到给定的等待队列头,并将其置于适当的睡眠状态。在执行了所有必要的检查并调用了schedule()
之后,新唤醒的线程将调用swait_finish()
将其从队列中删除并进行清理。
当前的等待队列实现具有大量宏,以简化等待条件的任务。对于简单的等待队列,有一个类似的集合,但是要小得多:
wait_event_cmd(wq, condition, cmd1, cmd2);
wait_event_hrtimeout(wq, condition, timeout);
wait_event_killable(wq, condition);
wait_event_lock_irq_cmd(wq, condition, lock, cmd);
wait_event_lock_irq(wq, condition, lock);
wait_event_interruptible_hrtimeout(wq, condition, timeout);
wait_event_interruptible_exclusive(wq, condition);
wait_event_interruptible_locked(wq, condition);
wait_event_interruptible_locked_irq(wq, condition);
wait_event_interruptible_exclusive_locked(wq, condition);
wait_event_interruptible_exclusive_locked_irq(wq, condition);
wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd);
wait_event_interruptible_lock_irq(wq, condition, lock);
wait_event_interruptible_lock_irq_timeout(wq, condition, lock, timeout);
如果需要,添加上述大多数宏的“简单”版本几乎没有什么障碍;有趣的是,未来几年将有多少人出现。不用说,也没有什么像过时的sleep_on()
接口。可以肯定地说,没有人会尝试添加该版本。
Paul的帖子指出,即使仅在几个地方使用它们,添加简单的等待队列也会使内核更小。考虑到接口的尺寸减小和相对简单,毫无疑问,到目前为止,没有人反对添加此代码。唯一真正的问题是如何进行添加。
Christoph Hellwig建议,简单的等待队列可以简单地替换当前的实现,只需更改一些稀奇古怪的功能以使用新名称使用旧代码即可。保罗担心这种大范围的改变会造成一个卖旗日,而问题与以奇怪的方式与等待队列的改变有关。
没有人想要这种情况,因此简单的等待队列似乎更有可能保留其“等待”命名方案。内核可能会看到现有等待队列的整体命名更改,以明确表明现在可以进行选择了。因此,我们可能会看到一个很大的补丁,将wait_event()
更改为cwait_event()
,依此类推,而没有更改功能。之后,可以将各个呼叫站点随意更改为简单的等待队列。结果将是相当数量的代码搅动,但是这种搅动应该留下一个更小,更简单的内核。