阻塞进程
当某人要求你什么事而你当时不能时你在做什么?如果你是人而你被别人打扰,你唯一能说的是:‘现在不行,我正忙着呢。 走开!’。但是如果你是一个内核模块而你被一个进程打扰,你有另外的可能。你可以让那个进程睡眠直到你能为它服务。毕竟,内核可以让进程睡眠并且可以随时唤醒它(那就是在单CPU上呈现同一时间多个进程运行的方式)。
这个内核模块就是这样的例子。那个文件(被称为 /proc/sleep)在同一时间只能被一个进程打开。如果那个文件已经打开了,内核模块调用module_interruptible_sleep_on(保持一个文件打开的最简单的办法是用 tail -f)。这个函数改变那个任务(任何任务是包含有关进程的信息和系统调用的内核的一种数据结构)的状态为TASK_INTERRUPTIBLE,它的意思是任务不能运行,除非它被唤醒。并且该任务被加入 WaitQ-- 等待访问该文件的任务队列。然后函数调用调度程序进行上下文转换到一个还要使用CPU的不同的进程。
当一个进程用完该文件,它关闭该文件,然后module_close 被调用。那个函数唤醒队列中的所有进程(没有机制只唤醒其中的一个)。然后它返回而刚刚关闭该文件的进程可以继续运行。调度程序及时地决定那个进程已经用了够多的时间而将CPU的控制权交给另一个进程。最后,队列中的某个进程会获得调度程序赋予的CPU的控制权。它正好在对module_interruptible_sleep_on(这意味着进程仍然在内核模式--直到进程被关照,它发布 open 系统调用然而系统调用还没有返回。进程不知道别人在它发布调用和它返回之前的大部分时间内使用CPU)的调用后开始。然后它能继续设置一个全局变量以告诉所有其他进程该文件仍然打开,它们将继续它们等待的生活。当另一个进程得到CPU时间片,它们将看到那个全局变量而继续去睡眠。
为了使我们的生活更有趣, module_close 没有唤醒等待访问该文件的进程的垄断权。一个信号,例如Ctrl-C (SIGINT)也可以唤醒一个进程(这是因为我们使用 module_interruptible_sleep_on。我们不能使用module_sleep_on 作为代替,但那将导致那些他们控制的计算机被忽略的用户的极度的愤怒)。在那种情况下,我们想立即用 -EINTR 返回。这是很重要的,例如用户因此可以在进程收到该文件前将它杀死。
还有一点需要记住。有时候进程不想睡眠,它们想要么立即得到它们需要的要么被告知它不能完成。当文件打开时这类进程使用 O_NONBLOCK 标志。例如当文件打开时,内核被假设从阻塞操作中返回错误代码-EAGAIN作为回应。在这章的源目录中有的程序 cat_noblock 能用O_NONBLOCK打开文件。