嵌入式Linux中文站

内核等待队列机制介绍


相信很多写程序的人都写过 socket 的程序。当我们 open 一个 socket 之后,接着去读取这个 socket,如果此时没有任何资料可供读取,那 read 就会 block 住。(这是没有加上 O_NONBLOCK 的情形),直到有资料可读取才会传回来。在 Linux kernel 里有一个数据结构可以帮助我们做到这样的功能。这个数据结构就是这里要跟各位介绍的 wait queue。在 kernel 里,wait_queue 的应用很广,举凡 device driver semaphore 等方面都会使用到 wait_queue 来 implement。所以,它算是 kernel 里蛮基本的一个数据结构。

接下来,我要跟各位介绍一下 wait_queue 的用法,以及用一个例子来说明如何使用 wait_queue。最后,我会带各位去 trace 一下 wait_queue 的原始程序代码,看看 wait_queue 是如何做到的。

我想有件事要先提及的是 Linux 在 user space 跟在 kernel space 上的差异。我们知道 Linux 是 multi-tasking 的环境,同时可以有很多人执行很多的程序。这是从 user 的观点来看的。如果就 kernel 的观点来看,是没有所谓的 multi-tasking 的。在 kernel 里,只有 single-thread。也就是说,如果你的 kernel code 正在执行,那系统里只有那部分在执行。不会有另一部分的 kernel code 也在运作。当然,这是指 single processor 的情况下,如果是 SMP 的话,那我就不清楚了。我想很多人都在 Windows 3.1 下写过程序,在那种环境下写程序,每一个程序都必须适当的将 CPU 让给别的程序使用。如果有个程序里面有一个

while (1);

的话,那保证系统就停在那里了。这种的多任务叫做 non-preemptive。它多任务的特性是由各个程序相互合作而造成的。在 Linux 的 user space 下,则是所谓的 preemptive,各个 process 喜欢执行什么就执行什么,就算你在你的程序里加上 while(1); 这一行也不会影响系统的运作。反正时间到了,系统自动就会将你的程序停住,让别的程序去执行。这是在 user space 的情况下,在 kernel 这方面,就跟 Windows 3.1 程序是一样的。在 kernel 里,你必须适当的将 CPU 的执行权释放出来。如果你在 kernel里加入 while(1); 这一行。那系统就会跟 Windows 3.1 一样。卡在那里。当然啦,我是没试过这样去改 kernel,有兴趣的人可以去试试看,如果有不同的结果,请记得告诉我。

假设我们在 kernel 里产生一个 buffer,user 可以经由 read,write 等 system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问你要如何去处理这种情形呢 ? 第一种,传给 user 一个错误讯息,说 buffer 已经满了,不能再写入。第二种,将 user 的要求 block 住,等有人将 buffer 内容读走,留出空位时,再让 user 写入资料。但问题来了,你要怎么将 user 的要求 block 住。难道你要用

while ( is_full );
write_to_buffer;

这样的程序代码吗? 想想看,如果你这样做会发生什么事? 第一,kernel会一直在这个 while 里执行。第二个,如果 kernel 一直在这个 while 里执行,表示它没有办法去 maintain系统的运作。那此时系统就相当于当掉了。在这里 is_full 是一个变量,当然,你可以让 is_full 是一个 function,在这个 function里会去做别的事让 kernel 可以运作,那系统就不会当。这是一个方式。但是,如果我们使用 wait_queue 的话,那程序看起来会比较漂亮,而且也比较让人了解,如下所示:


struct wait_queue *wq = NULL; /* global variable */
while ( is_full ) {
interruptible_sleep_on( &wq );
}
write_to_buffer();

interruptible_sleep_on( &wq ) 是用来将目前的 process,也就是要求写资料到 buffer 的 process放到 wq 这个 wait_queue 里。在 interruptible_sleep_on 里,则是最后会呼叫 schedule() 来做 schedule 的动作,也就是去找另一个 process 来执行以维持系统的运作。当执行完 interruptible_sleep_on 之后,要求 write 的 process 就会被 block 住。那什么时候会恢复执行呢 ? 这个 process 之所以会被 block 住是因为 buffer 的空间满了,无法写入。但是如果有人将 buffer 的资料读取掉,则 buffer 就有空间可以让人写入。所以,有关于叫醒 process 的动作应该是在 read buffer 这方面的程序代码做的。

extern struct wait_queue *wq;
if ( !is_empty ) {
read_from_buffer();
wake_up_interruptible( &wq );
}
....

以上的程序代码应该要放在 read buffer 这部分的程序代码里,当 buffer 有多余的空间时,我们就呼叫 wake_up_interruptible( &wq ) 来将挂在 wq 上的所有 process 叫醒。请记得,我是说将 wq 上的所有 process 叫醒,所以,如果如果有10个 process 挂在 wq 上的话,那这 10 个都会被叫醒。之后,至于谁先执行。则是要看 schedule 是怎么做的。就是因为这 10 个都会被叫醒。如果 A 先执行,而且万一很不凑巧的,A 又把 buffer 写满了,那其它 9 个 process 要怎么办呢? 所以在 write buffer 的部分,需要用一个 while 来检查 buffer 目前是否满了.如果是的话,那就继续挂在 wq 上面.

上面所谈的就是 wait_queue 的用法。很简单不是吗? 接下来,我会再介绍一下 wait_queue 提供那些 function 让我们使用。让我再重申一次。wait_queue 应设为 global variable,比方叫 wq,只要任何的 process 想将自己挂在上面,就可以直接叫呼叫 sleep_on 等 function。要将 wq 上的 process 叫醒。只要呼叫 wake_up 等 function 就可以了.

就我所知,wait_queue 提供4个 function 可以使用,两个是用来将 process 加到 wait_queue 的:

sleep_on( struct wait_queue **wq );
interruptible_sleep_on( struct wait_queue **wq );

另外两个则是将process从wait_queue上叫醒的。

wake_up( struct wait_queue **wq );
wake_up_interruptible( struct wait_queue **wq );

我现在来解释一下为什么会有两组。有 interruptible 的那一组是这样子的。当我们去 read 一个没有资料可供读取的 socket 时,process 会 block 在那里。如果我们此时按下 Ctrl+C,那 read() 就会传回 EINTR。像这种的 block IO 就是使用 interruptible_sleep_on() 做到的。也就是说,如果你是用 interruptible_sleep_on() 来将 process 放到 wait_queue 时,如果有人送一个 signal 给这个 process,那它就会自动从 wait_queue 中醒来。但是如果你是用 sleep_on() 把 process 放到 wq 中的话,那不管你送任何的 signal 给它,它还是不会理你的。除非你是使用 wake_up() 将它叫醒。sleep 有两组。wake_up 也有两组。wake_up_interruptible() 会将 wq 中使用 interruptible_sleep_on() 的 process 叫醒。至于 wake_up() 则是会将 wq 中所有的 process 叫醒。包括使用 interruptible_sleep_on() 的 process。

本文永久更新链接:http://embeddedlinux.org.cn/emb-linux/kernel-driver/200809/22-89.html



分享:

评论