什么是等待队列?
在软件开发中任务经常由于某种条件没有得到满足而不得不进入睡眠状态,然后等待条件得 到满足的时候再继续运行,进入运行状态。这种需求需要等待队列机制的支持。 Linux 中提供了等待队列的机制,该机制在 内核中应用很广泛。
在 Linux 内核中使用等待队列的过程很简单, 首先定义一个 wait_queue_head ,然后如果一个 task 想等待某种事件,那么调用 wait_event (等待队列,事件)就可以了。
Linux 中等待队列的实现
等待队列应用广泛,但是内核实现却十分简单。其涉及到两个比较重要的数据结构:
1) __wait_queue_head ,该结构 描述了等待队列的链头,其包含一个链表和一个原子锁,结构定义如下:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
2) __wait_queue ,该结构是对一个等 待任务的抽象。每个等待任务都会抽象成一个 wait_queue ,并且挂载到 wait_queue_head 上。该结构定 义如下:
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
Linux 中等待队列的实现思想如下图所示,当一个任务需要在某个 wait_queue_head 上睡眠时,将自己的进程控制块信息封装到 wait_queue 中,然后挂载到 wait_queue 的链表中,执行调度睡眠。当某些事件发生后,另一个任务 (进程)会唤醒 wait_queue_head 上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设 置为可调度的状态,并且从队列中删除。
使用等待队列时首先需要定义一个 wait_queue_head ,这可以通过 DECLARE_WAIT_QUEUE_HEAD 宏来完成,这是静态定义的方法。该宏会定义一个 wait_queue_head ,并且初始化结构中的锁以及等待队列。当然,动态初始化的方法也很简单,初始化一下锁及队列就可以 了。
一个任务需要等待某一事件的发生时,通常调用 wait_event ,该函数会定义一个 wait_queue ,描述等待任务,并且用当前的进程描述块初始化 wait_queue ,然后将 wait_queue 加入到 wait_queue_head 中。函数实现流程说明如下:
1、 用当前的进程描述 块( PCB )初始化一个 wait_queue 描述的等待任务。
2、 在等待队列锁资源 的保护下,将等待任务加入等待队列。
3、 判断等待条件是否 满足,如果满足,那么将等待任务从队列中移出,退出函数。
4、 如果条件不满足, 那么任务调度,将 CPU 资源交与其它任务。
5、 当睡眠任务被唤醒 之后,需要重复( 2 )、( 3 )步骤,如果确认条件满足,退出等待事件函数。
等待队列编程接口
序号 | 编程接口 | 使用说明 |
1 | wait_event | 这是一个宏,让当前任务处于等待事件状态。 输入参数如下: @wq :等待队列 @conditions :等待条件 |
2 | wait_event_timeout | 功能与 wait_event 类似,多了一个超时机制。参数中多了一项超时时间。 |
3 | wait_event_interruptible | 这是一个宏,与前两个宏相比,该宏定义的等 待能够被消息唤醒。如果被消息唤醒,那么返回 - ERESTARTSYS 。输入参数如下: @wq :等待队列 @condition :等待条件 @rt :返回值 |
4 | wait_event_interruptible_timeout | 与( 3 )相 比,多了超时机制 |
5 | wake_up | 唤醒等待队列中的一个任务 |
6 | wake_up_all | 唤醒等待队列中的所有任务 |