Linux内核模块编程--阻塞进程
阻塞进程当某人要求你什么事而你当时不能时你在做什么?如果你是人而你被别人打扰,你唯一能说的是:‘现在不行,我正忙着呢。 走开!’。但是如果你是一个内核模块而你被一个进程打扰,你有另外的可能。你可以让那个进程睡眠直到你能为它服务。毕竟,内核可以让进程睡眠并且可以随时唤醒它(那就是在单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打开文件。
范例 sleep.c
/* sleep.c - 创建一个 /proc 文件,而如果几个进程同时试图打开它,除了一个外使其他所有的睡眠 *//* Copyright (C) 1998-99 by Ori Pomerantz *//* 必要头文件 *//* 标准头文件 */#include /* 内核工作 */#include /* 明确指定是模块 *//* 处理 CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include#endif/* 使用 proc 文件系统所必须的 */#include/* 为了使进程睡眠和唤醒 */#include#include/* 2.2.3 版/usr/include/linux/version.h 包含该宏* 但 2.0.35 版不包含--假如以备需要 */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)#include /* 为了得到 get_user 和 put_user */#endif/* 模块的文件函数 ********************** *//* 保存收到的上一个消息以证明能够处理输入 */#define MESSAGE_LENGTH 80static char Message[MESSAGE_LENGTH];/* 既然使用文件操作结构,我们就不能特殊的 proc 输出 - 我们不得不使用这个标准的读函数 */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static ssize_t module_output(struct file *file, /* 要读的文件 */char *buf, /* 放置数据的缓冲区(在用户内存段) */size_t len, /* 缓冲区长度 */loff_t *offset) /* 文件偏移量 - 忽略 */#elsestatic int module_output(struct inode *inode, /* 要读的节点 */struct file *file, /* 要读的文件 */char *buf, /* 放置数据的缓冲区(在用户内存段) */int len) /* 缓冲区长度 */#endif{static int finished = 0;int i;char message[MESSAGE_LENGTH+30];/* 返回0指明文件尾--没有更多说的 */if (finished) {finished = 0;return 0;}/* 如果你到现在还不懂这个,你没有希望成为内核程序员 */sprintf(message, "Last input:%s
", Message);for(i=0; i put_user(message[i], buf+i);finished = 1;return i; /* 返回‘读’的字节数 */}/* 当用户向 /proc 文件写说这个函数接收用户输入 */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static ssize_t module_input(struct file *file, /* 文件自己 */const char *buf, /* 带有输入的缓冲区 */size_t length, /* 缓冲区长度 */loff_t *offset) /* 文件偏移量-忽略 */#elsestatic int module_input(struct inode *inode, /* 文件的节点 */struct file *file, /* 文件自己 */const char *buf, /* 带有输入的缓冲区 */int length) /* 缓冲区长度 */#endif{int i;/* 将输入放入 Message, module_output 随后将能使用它 */for(i=0; i #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)get_user(Message[i], buf+i);#elseMessage[i] = get_user(buf+i);#endif/* 我们需要一个标准的以0终止的字符串 */Message[i] = '