会计考友 发表于 2012-8-4 12:07:07

Linux系统管理:内核等待队列机制介绍3

wait_queue 是定义在 里,我们可以先看看它的数据结构是怎么样:
   
    struct wait_queue {
   
    struct task_struct * task;
   
    struct wait_queue * next;
   
    };
   
    很简单是吧。这个结构里面只有二个字段,一个是 task_struct 的 pointer,另一个则是 wait_queue 的 pointer.很明显的,我们可以看出 wait_queue 其实就是一个 linked list,而且它还是一个 circular linked list. 其中 task_struct 就是用来指呼叫 sleep_on 等 function的 process.在 Linux 里,每一个 process 是由一个 task_struct 来描叙。task_struct 是一个很大的的结构,在此我们不会讨论。Linux 里有一个 global variable,叫 current,它会指到目前正在执行的 process 的 task_struct 结构。这也就是为什么当 process 呼叫 system call,切换到 kernel 时,kernel 会知道是那个 process 呼叫的。
   
    好,我们现在来看看 interruptible_sleep_on() 和 sleep_on() 是如何做的。这两个 function 都是位于 /usr/src/linux/kernel/sched.c 里。
   
    void interruptible_sleep_on(struct wait_queue **p)
   
    {
   
    SLEEP_ON_VAR
   
    current->state = TASK_INTERRUPTIBLE;
   
    SLEEP_ON_HEAD
   
    schedule();
   
    SLEEP_ON_TAIL
   
    }
   
    void sleep_on(struct wait_queue **p)
   
    {
   
    SLEEP_ON_VAR
   
    current->state = TASK_UNINTERRUPTIBLE;
   
    SLEEP_ON_HEAD
   
    schedule();
   
    SLEEP_ON_TAIL
   
    }

会计考友 发表于 2012-8-4 12:07:08

Linux系统管理:内核等待队列机制介绍3

各位有没有发现这两个 function 很类似。是的,它们唯一的差别就在于
   
    current->state = …
   
    这一行而已。之前,我们有说过,interruptible_sleep_on() 可以被 signal 中断,所以,其 current->state 被设为 TASK_INTERRUPTIBLE.而 sleep_on() 没办法被中断,所以 current->state 设为 TASK_UNINTERRUPTIBLE.接下来,我们只看 interruptible_sleep_on() 就好了。毕竟它们两的差异只在那一行而已。
   
    在 sched.c 里,SLEEP_ON_VAR 是一个 macro,其实它只是定义两个区域变量出来而已。
   
    #defineSLEEP_ON_VAR
   
    unsigned long flags;
   
    struct wait_queue wait;
   
    刚才我也说过,current 这个变量是指到目前正在执行的 process 的 task_struct 结构。所以 current->state = TASK_INTERRUPTIBLE 会设定在呼叫 interruptible_sleep_on() 的 process 身上。至于 SLEEP_ON_HEAD 做的事,则是将 current 的值放到 SLEEP_ON_VAR 宣告的 wait 变量里,并把 wait 放到 interruptible_sleep_on() 的参数所属的 wait_queue list 中。
   
    #defineSLEEP_ON_HEAD
   
    wait.task = current;
   
    write_lock_irqsave(&waitqueue_lock,flags);
   
    __add_wait_queue(p,&wait);
   
    write_unlock(&waitqueue_lock);
   
    wait 是在 SLEEP_ON_VAR 中宣告的区域变量。其 task 字段被设成呼叫 interruptible_sleep_on() 的 process.至于 waitqueue_lock 这个变量是一个 spin lock. waitqueue_lock 是用来确保同一时间只能有一个 writer.但同一时间则可以有好几个 reader.也就是说 waitqueue_lock 是用来保证 critical section 的 mutual exclusive access.
   
    unsigned long flags;
   
    write_lock_irqsave(&waitqueue_lock,flags);
   
    …critical section …
   
    write_unlock(&waitqueue_lock)
   
    学过 OS 的人应该知道 critical section 的作用是什么,如有需要,请自行参考 OS 参考书。在 critical section 里只做一件事,就是将 wait 这个区域变量放到 p 这个 wait_queue list 中。 p 是 user 在呼叫 interruptible_sleep_on() 时传进来的,它的型别是 struct wait_queue **.在此, critical section 只呼叫 __add_wait_queue()。
   
    extern inline void __add_wait_queue(struct wait_queue ** p,
   
    struct wait_queue * wait)
   
    {
   
    wait->next = *p ? : WAIT_QUEUE_HEAD(p);
   
    *p = wait;
   
    }
   
    __add_wait_queue() 是一个inline function,定义在 中。WAIT_QUEUE_HEAD()是个很有趣的 macro,待会我们再讨论。现在只要知道它会传回这个 wait_queue 的开头就可以了。所以,__add_wait_queue() 的意思就是要把 wait 放到 p 所属的 wait_queue list 的开头。但是,大家还记得吗? 在上面的例子里,一开始我们是把 write_wq 设为 NULL.也就是说 *p 是 NULL.所以,当 *p 是 NULL 时,
   
    wait->next = WAIT_QUEUE_HEAD(p)
   
    是什么意思呢?
   
    所以,现在,我们来看一下 WAIT_QUEUE_HEAD() 是怎么样的一个 macro,它是定义在 里。
   
    #define WAIT_QUEUE_HEAD(x) ((struct wait_queue *)((x)-1))
   
    x 型别是 struct wait_queue **,因为是一个 pointer,所以大小是 4 byte.因此,若 x 为 100 的话,那 ((x)-1) 就变成 96.如下图所示。 WAIT_QUEUE_HEAD(x) 其实会传回 96,而且将其转型为 struct wait_queue*,各位可以看看。原本的 wait_queue* 只配制在 100-104 之间。现在 WAIT_QUEUE_HEAD(x) 却直接传回96,但是 96-100 这块位置根本没有被我们配置起来。更妙的事。由于 x 是一个 wait_queue list 的开头,我们始终不会用到 96-100 这块,我们只会直接使用到 100-104 这块内存。这也算是 wait_queue 一项比较奇怪的 implementation 方式吧。下面有三张图,第一张表示我们宣告了一个 wait_queue* 的变量,地址在 100.另外还有一个 wait_queue 的变量,名叫 wait.第二张图是我们呼叫 interruptible_sleep_on() 之后得到的结果。第三张则是我们又宣告一个 wait_queue,名叫 ano_wait,将 ano_wait 放到 wait_queue list 后的结果就第三张图所显示的。
页: [1]
查看完整版本: Linux系统管理:内核等待队列机制介绍3