一、等待队列的由来
Linux 内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。 等待队列头和等待队列项中都包含一个 list_head 类型的域作为”连接件”。它通过一个双链表和把等待 task的头,和等待的进程列表链接起来。
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
二、数据结构
2.1、等待队列头
等待队列头使用结构体wait_queue_head_t 表示 , wait_queue_head_t 结构体定义在文件 include/linux/wait.h 中,结构体内容如下所示:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
lock:自旋锁,用来对tasklist链表起保护作用。当要向task list链表中加入或者删除元素时,内核内部就会锁定lock锁,当修改完成后,会释放lock锁。也就是说, lock自旋锁在对task_list与操作的过程中,实现了对等待队列的互斥访问。
task_list:所有等待的队列都挂在这上面
2.2、等待队列项(等待队列成员)
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
三、相关操作
3.1、等待队列头定义与初始化
wait_queue_head_t testa_wq;
init_waitqueue_head(&testa_wq);
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化。
DECLARE_WAIT_QUEUE_HEAD (test_wq);
3.2、等待队列项头定义与初始化
使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项,宏的内容如下:
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
name 就是等待队列项的名字
tsk 表示这个等待队列项属于哪个任务(进程),一般设置为 current , 在Linux内核中current 相当于一个全局变量 , 表示当前进程 。
3.3、从等待队列头中添加/删除队列
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
3.4、设置等待队列
#define wait_event(wq, condition)
#define wait_event_timeout(wq, condition, timeout)
#define wait_event_interruptible(wq, condition)
#define wait_event_interruptible_timeout(wq, condition, timeout)
#define wait_event(wq, condition)
do {
if (condition) break;
__wait_event(wq, condition);
} while (0)
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule();//进程调度 \
} \
finish_wait(&wq, &__wait); \
} while (0)
wait event宏的功能是,在等待队列中睡眠直到condition为真。在等待的期间,进程会被置为TASK _UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值。
wait event timeont宏与wait event宏类似,不过如果所给的睡眠时间为负数则立即返回。如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0。
wait event interruptible宏与wait event宏的区别是,调用该宏在等待的过程中当前进程会被设置为TASK INTERRUPTIBLE状态。在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回;否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码。如果是condition为真,则返回0.
wait event interruptible timeout宏与wait event timeout宏类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码。
3.5、唤醒等待队列
调用 wake_up 去唤醒一个使用 wait_event 进入休眠的进程,唤醒之后,它会判断 condition 是否为真,如果还是假的继续睡眠。
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key);
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL)
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
/*
* Wakeup macros to be used to report events to the targets.
*/
#define wake_up_poll(x, m) \
__wake_up(x, TASK_NORMAL, 1, (void *) (m))
#define wake_up_locked_poll(x, m) \
__wake_up_locked_key((x), TASK_NORMAL, (void *) (m))
#define wake_up_interruptible_poll(x, m) \
__wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))
#define wake_up_interruptible_sync_poll(x, m) \
__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))
wake_up和wake_up_interruptible都会将这个等待队列头中的所有进程都唤醒。wake_up 可 以 唤 醒 处 于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状 态 的 进 程 , 而 wake_up_interruptible 只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。
ref: