参考资料:
宋宝华:《linux设备驱动开发详解》
《Mastering Linux Device Driver Development》
原文翻译与理解:
等待队列用于处理阻塞I/O,等待队列等待特定条件变为true,等待一个给定事件发生,或者是等待数据和资源可以被获得。
内核中等待队列的数据结构如下:
成员为一个自旋锁和一个list_head类型的变量。
等待队列就是一个list,(这个list里面的进程被置为sleep状态,因而当某个条件发生的时候,它们可以被唤醒),等待队列的成员自旋锁用于保护这个list的并发访问。等待队列里面的进程想要进入睡眠状态,并且这种睡眠状态是因为等待一个或多个事件的发生,并且可以被唤醒。head表示这一系列进程。每个想要睡眠并且等待事件发生的进程会在睡眠之间放入到list。当一个进程在list的时候,称为wait queue entry。当事件发生的时候,list中的一个或者多个进程会被唤醒,并且移除出这个list。我们可以用两种方式显式初始化一个wait queue。一种是静态的声明和定义:
我们也可以动态初始化wait queue:
任何想要sleep并且等待my_event发生的进程可以触发以下两个函数: wait_event_interruptible() 或者 wait_event() 。大多数时候,事件仅仅是资源可达。为了让事情简单,这两个函数第二个参数都是一个表达式,如果表示为false那么进程就会被置为睡眠。
wait_event() 和 wait_event_interruptible() 差异在于:后者的睡眠是可以被信号打断的。它们被调用的时候都会看条件是否满足,条件为false时,进程被置为睡眠态并且被移除出running queue。
当需要等待的条件可能不再是true或者false,而是一个确定的延时,可以使用:wait_event_timeout。
这个函数有两个行为,取决于timeout走完与否:
(1)timeout已经走完了:
如果条件为false,wait_event_timeout 返回0;如果条件为true,wait_event_timeout返回1。
(2)timeout还没有走完:如果条件为true返回剩余时间。
时间的单元是jiffies。linux有相关api将毫秒和微秒转为 jiffies。从上可以看出,不管条件是否满足,时间走完都会返回的。
如果一些变量改变,造成这些进程等待的条件发生变化,必须使用wake_up系列的函数。也即是说,要唤醒在wait_queue种睡眠的进程,必须调用wake_up(),wake_up_all()和wake_up_interruptible(),或者wake_up_interruptible_all()。当使用这些函数的时候,条件会被重新评估。如果此时条件为true,则wait_queue相应进程被唤醒,并且这些进程的状态被设置为TASK_RUNNING;否则条件为false,没有任何事情发生。
例如:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/delay.h>
#include<linux/workqueue.h>
static DECLARE_WAIT_QUEUE_HEAD(my_wq); //初始化一个wait_queue
static int condition = 0; //condition
/* declare a work queue*/
static struct work_struct wrk;
static void work_handler(struct work_struct *work)
{
pr_info(“Waitqueue module handler %s\n”, __FUNCTION__);
msleep(5000);
pr_info(“Wake up the sleeping module\n”);
condition = 1;
wake_up_interruptible(&my_wq);
}
static int __init my_init(void)
{
pr_info(“Wait queue example\n”);
INIT_WORK(&wrk, work_handler);
schedule_work(&wrk);
pr_info(“Going to sleep %s\n”, __FUNCTION__);
wait_event_interruptible(my_wq, condition != 0);
pr_info(“woken up by the work job\n”);
return 0;
}
void my_exit(void)
{
pr_info(“waitqueue example cleanup\n”);
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR(“John Madieu <john.madieu@labcsmart.com>”);
MODULE_LICENSE(“GPL”);
这个例子中,进程在wait_queue中被放入5s,然后被work_handler唤醒。输出日志为:
可以看到module被wait_event_interruptible置为睡眠状态,仅work_handler设置condition=1并且wake_up wait_queue时,module被唤醒继续往下执行。