等待队列很早就作为一个基本的功能单位存在linux内核中,它以队列为基础数据结构,与进程调度机制紧密配合,能够用于实现内核中的异步事件通知机制。等待队列也可以用来同步对系统资源的访问。在使用时将其当做成一个普通队列数据结构,只不过等待队列是若干个休眠进程的集合,且内核自己实现了此队列初始化队列、入队列、出队列的一系列API,在使用时只需要调用系统的API即可。
简单的理解等待队列: 一个休眠进程的队列,等待特定事件的唤醒。
等待队列头: 等待队列头,顾名思义是等待队列的头部。一个等待队列有一个等待队列头,其他进程唤醒时,只将一个等待队列头的第一个休眠进程唤醒。
等待队列项: 等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
假设一个场景: 全年级同学在操场集合领书,不同的班级在一队(等待队列)。当叫到哪个班级时,该班级的第一位同学上来领书(队列唤醒),没叫到名字的同学原地等待(休眠进程,等待队列项)。此时班级就是一个等待队列头。相同班级的同学组成的队列就是等待队列。
结构体原型:
struct __wait_queue_head {
spinlock_t lock; struct list_head task_list;};typedef struct __wait_queue_head wait_queue_head_t;
等待队列头初始化:
#if 0//静态初始化DECLARE_WAIT_QUEUE_HEAD(name);
#else//动态初始化wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);#endif
结构体原型:
struct __wait_queue {
unsigned int flags; void *private; wait_queue_func_t func;
};typedef struct __wait_queue wait_queue_t;
等待队列项定义:
DECLARE_WAITQUEUE(name, tsk)
当前进程,先将其休眠,然后加入到等待队列,便于其他进程唤醒;进程在唤醒后,就需要移出等待队列。
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);
自动睡眠 Linux 内核中睡眠的最简单方式是一个宏定义, 称为 wait_event(有几个变体); 它结合了处理睡眠的细节和进程在等待的条件的检查. wait_event 的形式是:
queue 是等待队列头,condition 是条件,如果调用 wait_event 前 condition == 0 ,则调用 wait_event 之后,当前进程就会休眠,反之不会休眠。
wait_event(queue, condition); 将当前进程进程的状态设置为 TASK_UNINTERRUPTIBLE ,然后 schedule()
wait_event_interruptible(queue, condition); TASK_INTERRUPTIBLE ,然后 schedule()
wait_event_timeout(queue, condition, timeout); TASK_UNINTERRUPTIBLE ,然后 schedule_timeout()
wait_event_interruptible_timeout(queue, condition, timeout); TASK_INTERRUPTIBLE ,然后 schedule_timeout()
TASK_INTERRUPTIBLE 与 TASK_UNINTERRUPTIBLE 区别在于,它的休眠是否会被信号中断,别的进程发来一个信号比如 kill ,TASK_INTERRUPTIBLE 就会醒来去处理。然而 TASK_UNINTERRUPTIBLE 不会。schedule(),进程调度,而schedule_timeout()进行调度之后,一定时间后自动唤醒(超时唤醒),若唤醒后,condition仍然为假,则继续睡眠,反之执行。
手动睡眠
DECLARE_WAITQUEUE(name, tsk) 创建一个等待队列:
tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.
将等待队列头 加入/移除 等待队列: void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait); void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
设置进程状态:
set_current_state(TASK_INTERRUPTIBLE) 等
进程调度:
schedule() 或者 schedule_timeout()
还是用自动睡眠较为方便。
当进程进入等待队列休眠时,其他进程可以主动叫等待队列头唤醒首个等待队列项。若为自动睡眠,先判断condition:若为真则执行,否则继续睡眠。
void wake_up(wait_queue_head_t *q)void wake_up_interruptible(wait_queue_head_t *q)
笔者在最开始学习时,感觉其与信号量的作用似乎相似。最后经过思考,总结出不同。 信号量 : 其作用在于保护临界区,即临界区被占用时,其他访问该临界区的被动线程休眠。待信号量释放以后,其次的线程才可以访问。 等待队列: 是将当前进程先休眠,直到该进程被其他进程唤醒才可执行,且有超时唤醒功能。
2020-06-27