zoukankan      html  css  js  c++  java
  • 内核的等待队列

    内核的等待队列


     




                                    内核的等待队列
    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 );
                    
                    
                    
                    
                    
                    
                    


    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/84137/showart_1667870.html
  • 相关阅读:
    apply call bind方法的区别和含义
    html头部meta标签
    语义化标签
    “文件名和url路径名”命名要点以及大小写问题
    BMP GIF PNG JPG等图片格式的区别和适用情况
    前端页面的性能优化
    js阻止默认事件,如a标签跳转和事件冒泡
    散列碰撞问题的解决——开链法(拉链法)
    substring()方法
    对学生成绩进行散列
  • 原文地址:https://www.cnblogs.com/yuzaipiaofei/p/4124198.html
Copyright © 2011-2022 走看看