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