您的位置:首页 > 运维架构 > Linux

linux内核中等待队列 (函数wait_event与wake_up)

2013-08-15 16:07 836 查看
        根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析
       介绍这几个函数,不得不先介绍等待队列wait_queue_head_t与完成量completion。
       等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。
       完成量机制是基于等待队列的,内核利用该机制等待某一操作的结束。这两种经常被使用。
一、等待队列
       (一)数据结构
       等待队列结构如下,因为每个等待队列都可以再中断时被修改,因此,在操作等待队列之前必须获得一个自旋锁。

[cpp]
view plaincopyprint?

struct __wait_queue_head {  
        spinlock_t lock;  
        struct list_head task_list;  
};  
typedef struct__wait_queue_head wait_queue_head_t;  

       等待队列是通过task_list双链表来实现,其数据成员是以下数据结构:

[cpp]
view plaincopyprint?

typedef struct__wait_queue wait_queue_t;  
struct __wait_queue {  
        unsigned int flags;            
#defineWQ_FLAG_EXCLUSIVE      0x01  /* 表示等待进程想要被独占地唤醒  */  
        void *private;               /* 指向等待进程的task_struct实例 */  
        wait_queue_func_t func;      /* 用于唤醒等待进程              */  
        struct list_head task_list;  /* 用于链表元素,将wait_queue_t链接到wait_queue_head_t */  
};  

 其图如下:



       等待队列如何使用哪?分两步
       1. 为了使得等待进程在一个等待队列中睡眠,需要调用函数wait_event()函数。进程进入睡眠,将控制权释放给调度器。
       2. 在内核中另一处,调用wake_up()函数唤醒等待队列中的睡眠进程。
注:使用wait_event()函数使得进程睡眠;而在内核另一处有一个对应的wake_up()函数被调用。

        (二)初始化等待队列元素
        有两种方法初始化队列:
        1. 动态初始化init_waitqueue_entry()

[cpp]
view plaincopyprint?

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;  
}  

        2. 静态初始化DEFINE_WAIT()

[cpp]
view plaincopyprint?

#define DEFINE_WAIT_FUNC(name, function)                \  
    wait_queue_t name = {                       \  
        .private    = current,              \  
        .func       = function,             \  
        .task_list  = LIST_HEAD_INIT((name).task_list), \  
    }  
  
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)  

       其中函数autoremove_wake_function()是用来唤醒进程的,该函数不经调用default_wake_function(),还将所属等待队列成员从等待队列删除。   
       (三)进程睡眠
        1. 通过add_wait_queue()函数将一个进程添加到等待队列,首先获得自旋锁,然后调用__add_wait_queue()实现将新的等待进程添加等待队列(添加到等待队列的头部),然后解锁;代码如下:

[cpp]
view plaincopyprint?

static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)  
{  
    list_add(&new->task_list, &head->task_list);  
}  

        另一个函数add_wait_queue_exclusive()的含义与add_wait_queue()函数类似,但是将等待进程添加到等待队列的尾部,并设置WQ_FLAG_EXCLUSIVE标志。
        使得进程在等待队列上睡眠的另一种方法是:prepare_to_wait(),除了有add_wait_queue()函数的参数外,还要设置进程的状态。
        另一个函数prepare_to_wait_exclusive()语义类似。        
        通常情况下,add_wait_queue()函数不会直接使用,而是调用wait_evnet()函数

[cpp]
view plaincopyprint?

/** 
 * wait_event - sleep until a condition gets true 
 * @wq: the waitqueue to wait on 
 * @condition: a C expression for the event to wait for 
 * 
 * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the 
 * @condition evaluates to true. The @condition is checked each time 
 * the waitqueue @wq is woken up. 
 * 
 * wake_up() has to be called after changing any variable that could 
 * change the result of the wait condition. 
 */  
#define wait_event(wq, condition)                   \  
do {                                    \  
    if (condition)                          \  
        break;                          \  
    __wait_event(wq, condition);                    \  
} while (0)  

函数__wait_event()

[cpp]
view plaincopyprint?

#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)  

         其中wq是等待进程需要加入的等待队列,而condition是通过与所等待时间有关的一个C表达式形式给出。表示,条件满足时,可以立即停止处理。主要工作由__wait_event()来完成。
       分析__wait_event()函数,
       (1) 调用DEFINE_WAIT宏建立等待队列成员;
       (2) 使用一个无线循环,在循环体内,
                (a) 调用prepare_to_wait()使得进程在等待队列上等待;
                (b) 当进程被唤醒时,检查指定的条件condition是否满足,如果满足则跳出循环,否则将控制权交给调度器,然后进程继续睡眠。
        (3) 调用函数finish_wait()将进程状态设置为TASK_RUNNING,并从等待队列的链表中移除对应的成员。
       其他与wait_event类似的函数:
       1. wait_event_timeout()函数 ,使得进程处于TASK_INTERRUPTIBLE状态,从而睡眠进程可以通过接收信号被唤醒;
       2. wait_event_timeout()函数,等待满足指定的条件,但是如果等待时间超过指定的超时限制则停止睡眠,可以防止进程永远睡眠;
       3. wait_event_interruptible_timeout() 使得进程睡眠,但是可以通过接收信号被唤醒,也具有超时限制。
       (四)进程唤醒
       内核中虽然定义了很多唤醒等待队列中进程的函数,但是最终调用的都是__wake_up()

[cpp]
view plaincopyprint?

#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)  
#define wake_up_nr(x, nr)       __wake_up(x, TASK_NORMAL, nr, NULL)  
#define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL)  
#define wake_up_locked(x)       __wake_up_locked((x), TASK_NORMAL)  
  
#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)  
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)  
#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)  
#define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)  

       而__wake_up()函数在加锁之后调用的是__wake_up_common()

[html]
view plaincopyprint?

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
            int nr_exclusive, int wake_flags, void *key)  
{  
    wait_queue_t *curr, *next;  
  
    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
        unsigned flags = curr->flags;  
  
        if (curr->func(curr, mode, wake_flags, key) &&  
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
            break;  
    }  
}  

        其中:q是等待队列,mode指定进程的状态,用于控制唤醒进程的条件,nr_exclusive表示将要唤醒的设置了WQ_FLAG_EXCLUSIVE标志的进程的数目。 
        然后扫描链表,调用func,直至没有更多的进程被唤醒,或者被唤醒的的独占进程数目已经达到规定数目。

__wake_up_common循环遍历等待队列内的所有元素,分别执行其对应的唤醒函数。

这里的唤醒函数即先前定义等待队列项DEFINE_WAIT(__wait)时默认初始化的autoremove_wake_function函数。autoremove_wake_function最终会调用try_to_wake_up函数将进程置为TASK_RUNNING状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。

   (五)独占等待

当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项。它与一个正常的睡眠有 2 个重要的不同:

(1)当等待队列入口设置了 WQ_FLAG_EXCLUSEVE 标志,它被添加到等待队列的尾部;否则,添加到头部。

(2)当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止唤醒.但内核仍然每次唤醒所有的非独占等待。

采用独占等待要满足 2 个条件:

(1)希望对资源进行有效竞争;

(2)当资源可用时,唤醒一个进程就足够来完全消耗资源。

使一个进程进入独占等待,可调用: 

void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state); 

注意:无法使用 wait_event 和它的变体来进行独占等待.

唤醒函数

很少会需要调用wake_up_interruptible 之外的唤醒函数,但为完整起见,这里是整个集合:

wake_up(wait_queue_head_t *queue); 

wake_up_interruptible(wait_queue_head_t *queue); 

wake_up 唤醒队列中的每个非独占等待进程和一个独占等待进程。wake_up_interruptible 同样, 除了它跳过处于不可中断休眠的进程。它们在返回之前, 使一个或多个进程被唤醒、被调度(如果它们被从一个原子上下文调用, 这就不会发生).

wake_up_nr(wait_queue_head_t *queue, int nr); 

wake_up_interruptible_nr(wait_queue_head_t *queue, int nr); 

这些函数类似 wake_up, 除了它们能够唤醒多达 nr 个独占等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒

wake_up_all(wait_queue_head_t *queue); 

wake_up_interruptible_all(wait_queue_head_t *queue); 

这种 wake_up 唤醒所有的进程, 不管它们是否进行独占等待(可中断的类型仍然跳过在做不可中断等待的进程)

wake_up_interruptible_sync(wait_queue_head_t *queue); 

一个被唤醒的进程可能抢占当前进程, 并且在 wake_up 返回之前被调度到处理器。 但是, 如果你需要不要被调度出处理器时,可以使用 wake_up_interruptible 的"同步"变体. 这个函数最常用在调用者首先要完成剩下的少量工作,且不希望被调度出处理器时。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: