Chapter 6. Using /proc For Input

使用 /proc 作为输入

现在我们有两种从内核模块获得输出的方法:我们可以注册一个设备驱动并用 mknod生成一个设备文件,或者我们可以建立一个 /proc文件。这样内核就可以告诉我们重要的信息。 剩下的唯一问题是我们没法反馈信息。第一种方法是向/proc文件系统写入信息。

由于 /proc 文件系统是为内核输出其运行信息而设计的,它并未向内核输入信息提供了任何准备。 结构体struct proc_dir_entry并没有指向输入函数的指针,而是指向了一个输出函数。 作为替代办法,向/proc 写入信息,我们可以使用标准的文件系统提供的机制。

在Linux中有一种标准的注册文件系统的方法。既然每种文件系统都必须有处理文件 索引节点inode和文件本身的函数[1], 那么就一定有种结构体去存放这些函数的指针。这就是结构体struct inode_operations, 它其中又包含一个指向结构体struct file_operations的指针。在 /proc 文件系统中, 当我们需要注册一个新文件时,我们被允许选择哪一个struct inode_operations 结构体。这就是我们将使用的机制,用包含结构体 struct inode_operations指针的结构体struct file_operations 来指向我们的module_inputmodule_output函数。

需要注意的是“读”和“写”的含义在内核中是反过来的。“读”意味着输出,而“写”意味着输入。 这是从用户的角度来看待问题的。如果一个进程只能从内核的“输出”获得输入, 而内核也是从进程的输出中得到“输入”的。

在这儿另一件有趣的事就是module_permission函数了。该函数在每个进程想要对 /proc文件系统内的文件操作时被调用,它来决定是否操作被允许。 目前它只是对操作和操作所属用户的UID进行判断,但它可以也把其它的东西包括进来, 像还有哪些别的进程在对该文件进行操作,当前的时间,或是我们最后接收到的输入。

加入宏put_userget_user的原因是 Linux的内存是使用分页机制的(在Intel架构下是如此,但其它架构下有可能不同)。 这就意味着指针自身并不是指向一个确实的物理内存地址,而知是分页中的一个地址, 而且你必须知道哪些分页将来是可用的。其中内核本身占用一个分页,其它的每个进程都有自己的分页。

进程能看得到的分页只有属于它自己的,所以当编写用户程序时,不用考虑分页的存在。 但是当你编写内核模块时,你就会访问由系统自动管理的内核所在的分页。 当一块内存缓冲区中的内容要在当前运行中的进程和内核之间传递时, 内核的函数就接收指向在进程分页中的该内存缓冲区的指针。宏put_userget_user允许你进行这样的访问内存的操作。

Example 6-1. procfs.c

/* 
 *  procfs.c -  create a "file" in /proc, which allows both input and output.
 */
#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/module.h>	/* Specifically, a module */
#include <linux/proc_fs.h>	/* Necessary because we use proc fs */
#include <asm/uaccess.h>	/* for get_user and put_user */

/* 
 * Here we keep the last message received, to prove
 * that we can process our input 
 */
#define MESSAGE_LENGTH 80
static char Message[MESSAGE_LENGTH];
static struct proc_dir_entry *Our_Proc_File;

#define PROC_ENTRY_FILENAME "rw_test"

static ssize_t module_output(struct file *filp,	/* see include/linux/fs.h   */
			     char *buffer,	/* buffer to fill with data */
			     size_t length,	/* length of the buffer     */
			     loff_t * offset)
{
	static int finished = 0;
	int i;
	char message[MESSAGE_LENGTH + 30];

	/* 
	 * We return 0 to indicate end of file, that we have
	 * no more information. Otherwise, processes will
	 * continue to read from us in an endless loop. 
	 */
	if (finished) {
		finished = 0;
		return 0;
	}

	/* 
	 * We use put_user to copy the string from the kernel's
	 * memory segment to the memory segment of the process
	 * that called us. get_user, BTW, is
	 * used for the reverse. 
	 */
	sprintf(message, "Last input:%s", Message);
	for (i = 0; i < length && message[i]; i++)
		put_user(message[i], buffer + i);

	/* 
	 * Notice, we assume here that the size of the message
	 * is below len, or it will be received cut. In a real
	 * life situation, if the size of the message is less
	 * than len then we'd return len and on the second call
	 * start filling the buffer with the len+1'th byte of
	 * the message. 
	 */
	finished = 1;

	return i;		/* Return the number of bytes "read" */
}

static ssize_t
module_input(struct file *filp, const char *buff, size_t len, loff_t * off)
{
	int i;
	/* 
	 * Put the input into Message, where module_output
	 * will later be able to use it 
	 */
	for (i = 0; i < MESSAGE_LENGTH - 1 && i < len; i++)
		get_user(Message[i], buff + i);

	Message[i] = '\0';	/* we want a standard, zero terminated string */
	return i;
}

/* 
 * This function decides whether to allow an operation
 * (return zero) or not allow it (return a non-zero
 * which indicates why it is not allowed).
 *
 * The operation can be one of the following values:
 * 0 - Execute (run the "file" - meaningless in our case)
 * 2 - Write (input to the kernel module)
 * 4 - Read (output from the kernel module)
 *
 * This is the real function that checks file
 * permissions. The permissions returned by ls -l are
 * for referece only, and can be overridden here.
 */

static int module_permission(struct inode *inode, int op, struct nameidata *foo)
{
	/* 
	 * We allow everybody to read from our module, but
	 * only root (uid 0) may write to it 
	 */
	if (op == 4 || (op == 2 && current->euid == 0))
		return 0;

	/* 
	 * If it's anything else, access is denied 
	 */
	return -EACCES;
}

/* 
 * The file is opened - we don't really care about
 * that, but it does mean we need to increment the
 * module's reference count. 
 */
int module_open(struct inode *inode, struct file *file)
{
	try_module_get(THIS_MODULE);
	return 0;
}

/* 
 * The file is closed - again, interesting only because
 * of the reference count. 
 */
int module_close(struct inode *inode, struct file *file)
{
	module_put(THIS_MODULE);
	return 0;		/* success */
}

static struct file_operations File_Ops_4_Our_Proc_File = {
	.read = module_output,
	.write = module_input,
	.open = module_open,
	.release = module_close,
};

/* 
 * Inode operations for our proc file. We need it so
 * we'll have some place to specify the file operations
 * structure we want to use, and the function we use for
 * permissions. It's also possible to specify functions
 * to be called for anything else which could be done to
 * an inode (although we don't bother, we just put
 * NULL). 
 */

static struct inode_operations Inode_Ops_4_Our_Proc_File = {
	.permission = module_permission,	/* check for permissions */
};

/* 
 * Module initialization and cleanup 
 */
int init_module()
{
	int rv = 0;
	Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL);
	Our_Proc_File->owner = THIS_MODULE;
	Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File;
	Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File;
	Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR;
	Our_Proc_File->uid = 0;
	Our_Proc_File->gid = 0;
	Our_Proc_File->size = 80;

	if (Our_Proc_File == NULL) {
		rv = -ENOMEM;
		remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
		printk(KERN_INFO "Error: Could not initialize /proc/test\n");
	}

	return rv;
}

void cleanup_module()
{
	remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
}

还需要更多的关于procfs的例子?我要提醒你的是:第一,有消息说也许不久procfs将被sysfs取代;第二, 如果你真的很想多了解些procfs,你可以参考路径 linux/Documentation/DocBook/ 下的 那些技术性的文档。在内核代码树根目录下使用 make help 来获得如何将这些文档转化为你偏好的格式,例如: make htmldocs 。如果你要为内核加入一些你的文档,你也应该考虑这样做。

Notes

[1]

两者的区别是文件的操作针对具体的,实在的文件, 而文件索引节点的操作是针对文件的引用,像建立文件的连接等。