Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1264265
  • 博文数量: 404
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 5382
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-03 16:29
文章存档

2010年(40)

2009年(140)

2008年(224)

我的朋友

分类: LINUX

2008-12-17 14:04:37

等待队列

我们曾简要的提到进程(也就是正在运行的程序)可以转入休眠状态以等待某个特定事件,当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的技术要点是把等待队列(wait queue)和每一个事件联系起来。需要等待事件的进程在转入休眠状态后插入到队列中。当事件发生之后,内核遍历相应队列,唤醒休眠的任务让它投入运行状态。任务负责将自己从等待队列中清除。

等待队列的功能强大得令人吃惊,它们被广泛应用于整个内核中。更重要的是,实现等待队列的代码量并不大。

wait_queue结构: 单链

@ include/linux/wait.h

struct wait_queue {

    struct task_struct * task;

    struct wait_queue * next;

};

       进程(也就是正在运行的程序)可以转入休眠状态以等待某个特定事件,当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的技术要点是把等待队列(wait queue)和每一个事件联系起来。需要等待事件的进程在转入休眠状态后插入到队列中。当事件发生之后,内核遍历相应队列,唤醒休眠的任务让它投入运行状态。任务负责将自己从等待队列中清除。

等待队列的功能强大得令人吃惊,它们被广泛应用于整个内核中。更重要的是,实现等待队列的代码量并不大。      

wait_event @sched.h

通过使用这个宏,内核代码能够使当前执行的进程在等待队列wq中等待直至给定condition(可能是任何的表达式)得到满足。

如果条件已经为真,当前进程显然也就无需等待了。

否则,进程必须等待给定条件转变为真。这可以通过调用__wait_event来实现(16824行),我们将在下一节介绍它。由于 __wait_event已经同wait_event分离,已知条件为假的部分内核代码可以直接调用__wait_queue,而不用通过宏来进行冗余的(特别是在这些情况下)测试,实际上也没有代码会真正这样处理。更为重要的是,如果条件已经为真,wait_event会跳过将进程插入等待队列的代码。

__wait_event@sched.c

__wait_event使当前进程在等待队列wq中等待直至condition为真。

__wake_up@sched.c

该函数用来唤醒等待队列中正在休眠的进程。它由wake_upwake_up_interruptible调用(请分别参看16612行和16614行)。这些宏提供mode参数,只有状态满足mode所包含的状态之一的进程才可能被唤醒。

 
 
 
 

等待队列

内核的等待队列
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.cinclude/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_interruptiblewake_upwake_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.
等待队列的应用
等待队列的的应用涉及两个进程,假设为ABA是资源的消费者,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深入分析

前几天在看驱动的时候碰到了等待队列,上网去搜了 一下,再结合代码看了一下,深有体会. kernel 里,wait_queue 的应用很广,举凡 device driver,semaphore 等方面都会使用到 wait_queue implement。所以,它算是 kernel 里蛮 基本的一个数据结构。
首先,我们得明白,linux中的所有的进程都由task_struct这个结构管理。在生成进程的时候将会分配一个task_struct结构,之后将通过这个结构对进程进行管理。 task_struct结构存在于平坦地址空间内,任何时候Linux内核都可以参照所有进程的所有管理情报。内核堆栈也同样位于平坦地址空间内。(平坦的意思是"独立的连续区间")
下面是tesk_struct的主要成员:
--------------------------------------------------------------------------------
struct task_struct {
struct files_struct* files; //
文件描述符
struct signal_struct* sig; //
信号控制signal handler
struct mm_struct* mm; //
内存管理模块
long stat //
进程状态
struct list_head runlist; //
用于联结RUN队列
long priority; //
基本优先权
long counter; //
变动优先权
char comm[]; //
命令名
struct thread_struct tss; //
上下文保存领域
...
};
我们现在只需了解它里面的state就可以,state有下面几种状态:
状态 说明
TASK_RUNNING
执行可能状态
TASK_INTERRUPTIBLE
等待状态。可接受信号
TASK_UNINTERRUPTIBLE
等待状态。不能接受信号
TASK_ZOMBIE
僵尸状态。exit后的状态
TASK_STOPPED
延缓状态
我们要知道内核没有多进程,就只有一个进程(SMP就不清楚了),这跟在user space下是不同的.在用户空间里,我们可以使一个进程跑起while(1),其他的进程也能用,但是在内核中就不行了,原因在上面。
假设我们在 kernel 里产生一个 bufferuser 可以经由 readwrite system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问你要如何去处理这种情形呢 ? 第一种,传给 user 一个错误讯息,说 buffer 已经满了,不能再写入。第二种,将 user 的要求 block 住, 等有人将 buffer 内容读走,留出空位时,再让 user 写入资料。但问题来了,你要怎么将 user 的要求 block 住。难道你要用
while ( is_full );
write_to_buffer;
这样的程序代码吗? 想想看,如果你这样做会发生什么事? 第一,kernel会一直在这个 while 里执行。第二个,如果 kernel 一直在这个 while 里执行,表示它没有办法去 maintain系统的运作。那此时系统就相当于当掉了。在这里 is_full 是一个变量,当然,你可以让 is_full 是一个 function,在这个 function里会去做别的事让 kernel 可以运作,那系统就不会当。这是一个方式。还有,你说可以在while里面把buffer里的内容读走,再把is_full的值改了,但是我们会可能把重要的数据在我们不想被读的时候被读走了,那是比较麻烦的,而且很不灵活.如果我们使用 wait_queue 的话,那程序看起来会比较漂亮,而且也比较让人了解,如下所示:
struct wait_queue_head_t wq; /* global variable */
DECLARE_WAIT_QUEUE_HEAD (wq);
while ( is_full ){
interruptible_sleep_on( &wq );
} write_to_buffer();
interruptible_sleep_on( &wq )
是用来将目前的 process,也就是要求写资料到buffer process放到 wq 这个 wait_queue 里。在 interruptible_sleep_on 里,则是最后会呼叫 schedule() 来做 schedule 的动作,谁调用了schedule谁就趴下,让别人去运行,醒来就原地起来,执行schedule()后的代码。那那个调用了schedule的家伙什么醒过来呢?这时候就需要用到另一个函数了wake_up_interruptible(),如下所示:
if ( !is_empty ) {
read_from_buffer();
wake_up_interruptible( &wq );
}
这就wait_queue的用法,挺好懂的.wait_queue到底是怎么工作的呢?wait_queue_head_t是一个相单简单的结构,在中,代码如下:
--------------------------------------------------------------------------------
struct __wait_queue_head {
wq_lock_t lock;
truct list_head task_list;
#if WAITQUEUE_DEBUG
long __magic;
long __creator;#endif
};
typedef struct __wait_queue_head wait_queue_head_t;
其中task_list是一个正在睡眠的进程的链表,链表中的各个数据项的类型是wait_queue_t,链表就是在中定义的通用链表,wait_queue_t代码如下:
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
struct task_struct * task;
struct list_head task_list;
#if WAITQUEUE_DEBUG
long __magic;
long __waker;#endif
};
typedef struct __wait_queue wait_queue_t;
其实,主要的结构是wait_queue_t.让我们来看一下interruptible_sleep_on的代码中,代码如下:

#define SLEEP_ON_VAR \
unsigned long flags; \
wait_queue_t wait; \
init_waitqueue_entry(&wait, current); //
用当前进程生成一个wait_queue_t
#define SLEEP_ON_HEAD \
spin_lock_irqsave(&q->lock,flags); \
__add_wait_queue(q, &wait); //
wait 放到 q 所属的wait_queue_t list 的开头
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 interruptible_sleep_on(wait_queue_head_t *q)
{
SLEEP_ON_VAR
current->state = TASK_INTERRUPTIBLE;
SLEEP_ON_HEAD
schedule(); //
状态为TASK_INTERRUPTIBLE的进程是不会执行的
SLEEP_ON_TAIL
}
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
#if WAITQUEUE_DEBUG
if (!head || !new)
WQ_BUG();
CHECK_MAGIC_WQHEAD(head);
CHECK_MAGIC(new->__magic);
if (!head->task_list.next || !head->task_list.prev)
WQ_BUG();
#endif
list_add(&new->task_list, &head->task_list);
}
static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
{
#if WAITQUEUE_DEBUG
if (!old)
WQ_BUG();
CHECK_MAGIC(old->__magic);
#endif
list_del(&old->task_list);
}
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
/*** list_del - deletes entry from list.
* @entry: the element to delete from the list
* Note: list_empty on entry does not return true after this, the entry is in an undefined state.
*/
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = (void *) 0;
entry->prev = (void *) 0;
}
上面的代码都应该比较好懂.我们先用当前进程生成了一个wait_queue_t,把当前进程的state改成TASK_INTERRUPTIBLE,然后把这个wait_queue_t加到我们已经声明并初始化好的全局变量q中去.这时调用shedule,current 所指到的 process 会被放到 scheduling queue 中等待被挑出来执行。执行完 schedule() 之后,current 就没办法继续执行了。而当 current 以后被 wake up 时,就会从 schedule() 之后,也就是从 SLEEP_ON_TAIL 开始执行。我们现在当然明白wake_up_interruptible所需做的是把进程的状态改成Running,其代码如下:
--------------------------------------------------------------------------------
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive)
{
unsigned long flags;
if (unlikely(!q))
return;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0);
spin_unlock_irqrestore(&q->lock, flags);
}
static inline void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int sync)
{
struct list_head *tmp;
unsigned int state;
wait_queue_t *curr;
task_t *p
list_for_each(tmp, &q->task_list) {
curr = list_entry(tmp, wait_queue_t, task_list);
p = curr->task;
state = p->state;
if ((state & mode) && try_to_wake_up(p, mode, sync) && ((curr->flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive))
break;
}
}
static int try_to_wake_up(task_t * p, unsigned int state, int sync)
{
unsigned long flags;
int success = 0;
long old_state;
runqueue_t *rq;
sync &= SYNC_WAKEUPS;repeat_lock_task:
rq = task_rq_lock(p, &flags);
old_state = p->state;
if (old_state & state)
{ //
状态相同的就改
if (!p->array) {
/*
* Fast-migrate the task if it's not running or runnable * currently. Do not violate hard affinity.
*/
if (unlikely(sync&&!task_running(rq, p) && (task_cpu(p)!= smp_processor_id())&& (p->cpus_allowed & (1UL << smp_processor_id()))))
{
set_task_cpu(p, smp_processor_id());
task_rq_unlock(rq, &flags);
goto repeat_lock_task;
}
if (old_state == TASK_UNINTERRUPTIBLE)
rq->nr_uninterruptible--;
if (sync)
__activate_task(p, rq);
else {
activate_task(p, rq);
resched_task(rq->curr);
}
success = 1;
}
if (p->state >= TASK_ZOMBIE)
BUG();
p->state = TASK_RUNNING;
}
task_rq_unlock(rq, &flags);
return success;
}
由于 schedule的代码量比较大,就不贴出来了。

 

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_INTERRUPTIBLETASK_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()

 

 

 

标签: 嵌入式系统  Linux机制分析  

Linux中等待队列机制分析

什么是等待队列?

       在软件开发中任务经常由于某种条件没有得到满足而不得不进入睡眠状态,然后等待条件得到满足的时候再继续运行,进入运行状态。这种需求需要等待队列机制的支持。Linux中提供了等待队列的机制,该机制在内核中应用很广泛。

 

       Linux内核中使用等待队列的过程很简单,首先定义一个wait_queue_head,然后如果一个task想等待某种事件,那么调用wait_event(等待队列,事件)就可以了。

 

Linux中等待队列的实现

       等待队列应用广泛,但是内核实现却十分简单。其涉及到两个比较重要的数据结构:

1)        __wait_queue_head,该结构描述了等待队列的链头,其包含一个链表和一个原子锁,结构定义如下:

struct __wait_queue_head {

              spinlock_t lock;                    /* 保护等待队列的原子锁 */

              struct list_head task_list;              /* 等待队列 */

};

2)        __wait_queue,该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。该结构定义如下:

struct __wait_queue {

       unsigned int flags;

       void *private;                       /* 通常指向当前任务控制块 */

       /* 任务唤醒操作方法,该方法在内核中提供,通常为autoremove_wake_function */

       wait_queue_func_t func;             

       struct list_head task_list;              /* 挂入wait_queue_head的挂载点 */

};

 

        Linux中等待队列的实现思想如下图所示,当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息封装到wait_queue中,然后挂载到wait_queue的链表中,执行调度睡眠。当某些事件发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。

    使用等待队列时首先需要定义一个wait_queue_head,这可以通过DECLARE_WAIT_QUEUE_HEAD宏来完成,这是静态定义的方法。该宏会定义一个wait_queue_head,并且初始化结构中的锁以及等待队列。当然,动态初始化的方法也很简单,初始化一下锁及队列就可以了。

 

       一个任务需要等待某一事件的发生时,通常调用wait_event,该函数会定义一个wait_queue,描述等待任务,并且用当前的进程描述块初始化wait_queue,然后将wait_queue加入到wait_queue_head中。函数实现流程说明如下:

1  用当前的进程描述块(PCB)初始化一个wait_queue描述的等待任务。

2  在等待队列锁资源的保护下,将等待任务加入等待队列。

3  判断等待条件是否满足,如果满足,那么将等待任务从队列中移出,退出函数。

4  如果条件不满足,那么任务调度,将CPU资源交与其它任务。

5  当睡眠任务被唤醒之后,需要重复(2)、(3)步骤,如果确认条件满足,退出等待事件函数。

 

等待队列编程接口

序号

编程接口

使用说明

1

wait_event

这是一个宏,让当前任务处于等待事件状态。输入参数如下:

@wq:等待队列

@conditions:等待条件

2

wait_event_timeout

功能与wait_event类似,多了一个超时机制。参数中多了一项超时时间。

3

wait_event_interruptible

这是一个宏,与前两个宏相比,该宏定义的等待能够被消息唤醒。如果被消息唤醒,那么返回- ERESTARTSYS。输入参数如下:

@wq:等待队列

@condition:等待条件

@rt:返回值

4

wait_event_interruptible_timeout

与(3)相比,多了超时机制

5

wake_up

唤醒等待队列中的一个任务

6

wake_up_all

唤醒等待队列中的所有任务

 

阅读(1058) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~