等待队列介绍:
内核的等待队列 creator sz111@126.com 1. 等待队列在内核中有着极其重要的作用,作为异步操作,他的实现简单而又强大。
它通过一个双链表和把等待tast的头,和等待的进程列表链接起来。从上图可以清晰看到。所以我们知道,如果要实现一个等待队列,首先要有两个部分。队列头和队列项。下面看他们的数据结构。
struct list_head {
struct list_head *next, *prev;
};
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;//2.6版本是采用void指针,而以前的版本是struct task_struct * task;
//实际在用的时候,仍然把private赋值为task
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
所以队列头和队列项是通过list_head联系到一起的,list_head是一个双向链表,在linux内核中有着广泛的应用。并且在list.h中对它有着很多的操作。
2.对列头和队列项的初始化:
/*
* Macros for declaration and initialisaton of the datatypes
*/
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
//这个是初始化一个队列项,设定tast_list链表前后都是空,说明还没有加入到链表里面。
//私有数据private为任务的任务结构。
//这个宏说声明和初始化都同时做了。如果不愿意这样的话,可以先声明,然后通过
//init_waitqueue_entry进行完成。
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
//声明一个队列头,让他的链表前后都指向自己,这个时候还没有加入任何的链表项。
//这个宏说声明和初始化都同时做了。如果不愿意这样的话,可以先声明,然后通过
//init_waitqueue_head进行完成。
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_BIT_KEY_INITIALIZER(word, bit) \
{ .flags = word, .bit_nr = bit, }
extern void init_waitqueue_head(wait_queue_head_t *q);
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p;
q->func = default_wake_function;
}
//将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
/*
* Used for wake-one threads:
*/
//将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。
//其实因为队列是个环形队列,所以head是头,head的钱一个就可以认为是尾,当然,环形也无所//谓头尾了。
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);
}
static inline void __remove_wait_queue(wait_queue_head_t *head,
wait_queue_t *old)
{
list_del(&old->task_list);
}
3.睡眠和唤醒操作。
对等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.c和include/linux/sched.h中)。思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入" 睡眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。
常用的睡眠操作有interruptible_sleep_on和 sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何 singal。在一般情况下,我们要采用可以中断的sleep。
interruptible_sleep_on宏代码如下:
//首先定义队列项,然后对队列项进行初始化,任务结构设定为当前进程的任务结构。
#define SLEEP_ON_VAR \
unsigned long flags; \
wait_queue_t wait; \
init_waitqueue_entry(&wait, current);
//然后把队列项加入到队列头中。
#define SLEEP_ON_HEAD \
spin_lock_irqsave(&q->lock,flags); \
__add_wait_queue(q, &wait); \
spin_unlock(&q->lock);
//唤醒之后要把队列项从队列头上删除。
#define SLEEP_ON_TAIL \
spin_lock_irq(&q->lock); \
__remove_wait_queue(q, &wait); \
spin_unlock_irqrestore(&q->lock, flags);
void fastcall __sched interruptible_sleep_on(wait_queue_head_t *q)
{
SLEEP_ON_VAR
current->state = TASK_INTERRUPTIBLE;
SLEEP_ON_HEAD
schedule();
SLEEP_ON_TAIL
}
对应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为 TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。 wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:
#define wake_up(x)__wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1, NULL)
__wake_up函数主要是获取队列操作的锁,具体工作是调用__wake_up_common完成的。
/**
* __wake_up - wake up threads blocked on a waitqueue.
* @q: the waitqueue唤醒的队列的队列头
* @mode: which threads 唤醒那种类型的等待队列,如:可中断和不可中断
* @nr_exclusive: how many wake-one or wake-many threads to wake up
* @key: is directly passed to the wakeup function
*/
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);
}
/*
* The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
* wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
* number) then we wake all the non-exclusive tasks and one exclusive task.
*
* There are circumstances in which we can try to wake a task which has already
* started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
* zero in this (rare) case, and we handle it by continuing to scan the queue.
*/
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 = list_entry(tmp, wait_queue_t, task_list);
unsigned flags = curr->flags;
//这里的func实际上是
//int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,
// void *key)
//{唤醒一个进程,将它放到运行队列中,如果它还不在运行队列的话。"当前"进程总是在运行队列中的
// return try_to_wake_up(curr->private, mode, sync);
//}
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
4.等待队列的应用
等待队列的的应用涉及两个进程,假设为A和B。A是资源的消费者,B是资源的生产者。A在消费的时候必须确保资源已经生产出来,为此定义一个资源等待队列。这个队列同时要被进程A和进程B使用,我们可以将它定义为一个全局变量。
DECLARE_WAIT_QUEUE_HEAD(rsc_queue); /* 全局变量 */
在进程A中,执行逻辑如下:
while (resource is unavaiable) {
interruptible_sleep_on( &wq );
}
consume_resource();
在进程B中,执行逻辑如下:
produce_resource();
wake_up_interruptible( &wq );
等待队列使用步骤: 在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue很早就作为一种基本的功能单位出现在Linux内核里了,它以队列位基础数据结构,与进程调度机制紧密结合,能够用于实现内核中异步事件通知机制。等待队列可以用来同步对系统资源的访问。(信号量在内核中也依赖等待队列来实现).
Linux-2.6提供如下关于等待队列的操作: (1) 定义"等待队列头" wait_queue_head_t my_queue;
(2) 初始化"等待队列头" init_waitqueue_head(&my_queue); 定义和初始化的快捷方式: DECLARE_WAIT_QUEUE_HEAD(my_queue);
(3) 定义等待队列 DECLARE_WAITQUEUE(name, tsk); 定义并初始化一个名为name的等待队列(wait_queue_t);
(4) 添加/移除等待队列 void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); add_wait_queue()用于将等待队列wait添加到等待队列头q指向的等待队列链表中,而remove_wait_queue()用于将等待队列wait从附属的等待队列头q指向的等待队列链表中移除。
(5) 等待事件 wait_event(queue, condition); wait_event_interruptible(queue, condition); wait_event_timeout(queue, condition, timeout); wait_event_interruptible_timeout(queue, condition, timeout); 等待第一个参数queue作为等待队列头的等待队列被唤醒,而且第二个参数condition必须满足,否则阻塞。wait_event()和wait_event_interruptible()的区别在于后者可以被信号打断,而前者不能。加上timeout后的宏意味着阻塞等待的超时时间,以jiffy为单位,在第三个参数的timeout到达时,不论condition是否满足,均返回。
(6) 唤醒队列 void wake_up(wait_queue_head_t *queue); void wake_up_interruptible(wait_queue_head_t *queue); 上述操作会唤醒以queue作为等待队列头的所有等待队列对应的进程。 wake_up() <---> wait_event() wait_event_timeout() wake_up_interruptible() <---> wait_event_interruptible() wait_event_interruptible_timeout()
wake_up()可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程 wake_up_interruptble()只能唤醒处于TASK_INTERRUPTIBLE的进程。
(7) 在等待队列上睡眠 sleep_on(wait_queue_head_t *q); interruptible_sleep_on(wait_queue_head_t *q); sleep_on()函数的作用就是将当前进程的状态置成TASK_UNINTERRUPTIBLE,定义一个等待队列,并把它添加到等待队列头q,直到支援获得,q引导的等待队列被唤醒。 interruptible_sleep_on()与sleep_on()函数类似,其作用是将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒或者进程收到信号。
sleep_on() <---> wake_up() interruptible_sleep_on() <---> wake_up_interruptible()
以上参考博文:http://www.cnblogs.com/noaming1900/archive/2011/01/14/1935365.html
http://www.cnblogs.com/noaming1900/archive/2011/01/14/1935488.html |