等待队列是很多其他linux内核机制的基石,例如异步事件通知机制,信号量也是。
Linux中等待队列的实现思想:当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息封装到wait_queue中,然后挂载到wait_queue的链表中,执行调度睡眠。当某些事件发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。
但是很少有文章深入分析该机制的内核代码,所以有必要分析一下等待队列的内核实现:
1.首先分析等待队列链表头的数据结构:wait_queue_head_t
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
从上面分析,等待队列头数据头其实就是内核链表的一个节点(这个结点叫做task_list)+一个自旋锁。
wait_queue_head_t myqueue;//实际上就是定义一个等待队列链表的头结点
其次还有一个很重要的数据结构,就是等待队列的数据结构:wait_queue_t
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;
};
这个等待队列包含几个成员,主要的包括:一个 private指针,一个回调函数,一个链表的结点。
哥明白了,哥继续!
2.现在我们来初始化等待队列链表头,使用以下语句:
Init_waitqueue_head(&myqueue);
我们来分析一下这个语句做了哪些事:
static inline void init_waitqueue_head(wait_queue_head_t *q)
{
spin_lock_init(&q->lock);
INIT_LIST_HEAD(&q->task_list);
}
(1)初始化了头结点的自旋锁
(2)调用INIT_LIST_HEAD宏把链表结点的prev指针和next指针指向自己
这样就完成了初始化,创建了一个保存等待队列的链表并初始化了该链表的头结点。
3.有了等待队列的链表,下面我们要完成把当前进程加入到等待队列中,并把该等待队列加入到等待队列的链表中去,首先我们看看怎么把当前进程加入到等待队列中去。
用一个宏实现:DECLARE_WAITQUEUE(wait, current)让我们来分析这个宏:
#define DECLARE_WAITQUEUE(name, tsk) /
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
代码定义了一个等待队列,名字叫name,然后调用__WAITQUEUE_INITIALIZER宏的值来赋值给它。柯南继续追踪
#define __WAITQUEUE_INITIALIZER(name, tsk) { /
.private = tsk, /
.func = default_wake_function, /
.task_list = { NULL, NULL } }
int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,
void *key)
{
task_t *p = curr->private;
return try_to_wake_up(p, mode, sync);
}
原来如此,__WAITQUEUE_INITIALIZER宏将等待队列name的private指针指向当前tsk,该等待队列的回调函数初始化为默认的唤醒函数,同时将等待队列name的链表的next和prev指针指向空。
总的来说DECLARE_WAITQUEUE宏意思就是定义一个叫做wait的等待队列项,该项指向当前进程,同时指定了当队列被唤醒时执行的默认唤醒函数,另外它的两个链表指针指向空。(该函数调用try_to_wake_up,这个就复杂了,有兴趣的童鞋可以关注一下,好像就是把任务加到run_list中去。)
4.有了等待队列的链表,也有了等待队列项,现在我们来完成将等待队列项加入到等待队列链表中去。
add_wait_queue(&myqueue ,wait)
让我们来详细分析一下:
void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);//核心函数,继续追踪
spin_unlock_irqrestore(&q->lock, flags);
}
__add_wait_queue(q, wait);//核心函数,继续追踪………………..
static inline void __add_wait_queue_tail(wait_queue_head_t *head,
wait_queue_t *new)
{
list_add_tail(&new->task_list, &head->task_list);
}
原来如此,看到这大家都明白了,其实add_wait_queue就是把等待队列项的链表结点,加到等待队列头初始化的链表中去!哈哈!现在大家都看清楚等待队列的实质了把,其实就是一个链表,每个结点就是一个“等待的task”。
5.现在有了等待队列,接下来的任务就是让task睡眠,和在一定时机让task苏醒。你可以调用系统有的函数例如Wait_event()或者sleep on()也可以自己在驱动中写代码实现(实际就是设置进程状态位,然后调用schedule()让出cpu)
Wait_event(queue, condition)
#define wait_event(wq, condition) /
do { /
if (condition) /
break; /
__wait_event(wq, condition); /
} while (0)
核心是__wait_event(wq, condition),继续追踪
#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)
又见死循环!太牛逼了!首先判断condition是否满足,如果满足的话wait_event()立即返回,否则就schedule()进行睡眠。如果被唤醒,那么就再次判断condition的条件,如果成立就跳出,如果不成立,再次睡眠。
6.Wake_up函数,唤醒每个在等待队列上的等待的任务。
#define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);//核心函数,继续追踪
spin_unlock_irqrestore(&q->lock, flags);
}
下面的东西就经典了啊,童鞋们注意了
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int sync, void *key)
{
struct list_head *tmp, *next;
list_for_each_safe(tmp, next, &q->task_list) {
wait_queue_t *curr;
unsigned flags;
curr = list_entry(tmp, wait_queue_t, task_list);
flags = curr->flags;
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) &&
!--nr_exclusive)
break;
}
其实就是遍历等待队列链表上的每个结点,调用默认的唤醒函数func(curr, mode, sync, key),该函数唤醒进程,并使进程进入内核的run_list(前面提到过程比较复杂)这样就完成了进程的唤醒啦!
具体的例程代码有很多,大家参考设备驱动开发详解中的相关例程代码就好了,快12点了,先去休息了