zoukankan      html  css  js  c++  java
  • linux内核中等待队列 (函数wait_event与wake_up)

            根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析

           介绍这几个函数,不得不先介绍等待队列wait_queue_head_t与完成量completion。

           等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。

           完成量机制是基于等待队列的,内核利用该机制等待某一操作的结束。这两种经常被使用。

    一、等待队列

           (一)数据结构

           等待队列结构如下,因为每个等待队列都可以再中断时被修改,因此,在操作等待队列之前必须获得一个自旋锁。

    struct __wait_queue_head {
            spinlock_t lock;
            struct list_head task_list;
    };
    typedef struct__wait_queue_head wait_queue_head_t;
           等待队列是通过task_list双链表来实现,其数据成员是以下数据结构:

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

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

    #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()实现将新的等待进程添加等待队列(添加到等待队列的头部),然后解锁;代码如下:

    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_EXCLUSIXE标志。

            使得进程在等待队列上睡眠的另一种方法是:prepare_to_wait(),除了有add_wait_queue()函数的参数外,还要设置进程的状态。

            另一个函数prepare_to_wait_exclusive()语义类似。        

            通常情况下,add_wait_queue()函数不会直接使用,而是调用wait_evnet()函数

    /**
     * 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()

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

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

    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,直至没有更多的进程被唤醒,或者被唤醒的的独占进程数目已经达到规定数目。

    完成量,下一次在学习。

    参考资料:

          (1) http://blog.chinaunix.net/space.php?uid=20565550&do=blog&id=303575

          (2) 《深入linuc内核架构》

  • 相关阅读:
    Codeforces Round #360 B
    Codeforces Round #360 C
    Codeforces Round #360 D
    新姿势 树剖求LCA
    Codeforces 165D Beard Graph 边权树剖+树状数组
    hdu3966 树链剖分+线段树 裸题
    Codeforces Round #425 D
    Codeforces Round #425 B
    Codeforces Round #425 A
    bzoj 1036 树链剖分+线段树 裸题
  • 原文地址:https://www.cnblogs.com/youngerchina/p/5624635.html
Copyright © 2011-2022 走看看