Linux内核模块编程-将/proc作为输入
将 /proc 作为输入迄今为止,我们有两中办法从内核模块中产生输出:我们可以登记一个设备驱动程序并 mknod 一个设备文件,或者我们可以创建一个/proc文件。这可以让内核模块告诉我们任何它可能告诉我们的事情。唯一的问题是这没有办法让我们告诉它。我们将输入发送给内核模块的第一个办法将是通过写回 /proc 文件。
因为 proc 文件系统主要是为了让内核报告它对进程的状态,所以对输入没有专门的预备。 proc_dir_entry结构没有包含一个指向输入函数的指针而包含输出函数的指针。为了向/proc 文件中写,我们需要使用标准的文件系统机制。
在 Linux 中对文件系统登记有标准的机制。既然每个文件系统必须有它自己的处理节点和文件操作(两者的不同在于文件操作处理文件自己,而节点操作处理对文件的引用,例如创建对它的连接)的函数, 所以有一个特殊的结构保存所有这些函数的指针, inode_operations 结构, 包含一个指向 file_operations结构的指针。在 /proc 中,任何时候登记一个新文件我们都允许特别指定哪个 inode_operations 结构将用于访问它。这就是我们使用的机制, inode_operations 结构包含指向 file_operations 结构的指针,而它又包含指向我们的module_input 和 module_output 函数的指针。
注意在内核中标准的读写的任务是颠倒的。读函数用作输出而写函数用于输入。造成这个局面的原因是读写是依据用户的观点--如果一个进程从内核中读什么,那么内核就需要输出它,而如果进程向内核中写什么,那么内核就需要将它作为输入接收。
这儿另一个引起注意的地方是 module_permission 函数。这个函数在进程试图用 /proc文件做什么的时候被调用,并且它决定是否允许访问。现在它仅仅基于操作和当前用户的UID(就像 current 中的那样,一个指向包含当前运行进程的信息的结构的指针),但它也可以基于任何我们喜欢的东西,例如其他进程在用该文件做什么,时间,或者我们上次的接收的输入。
使用 put_user 和 get_user 的原因是在 Linux 中内存 (在Intel 架构下,在其他处理器下可能不同)是分段的。这意味着指针不能单独由它自己指向一个唯一的内存位置,只是在内存的段中的位置,你需要知道它可以使用哪个内存段。对内核只有一个内存段,其他进程也各有一个。
进程只能访问自己的内存段,因此当写普通的作为进程运行的程序时我们不必为段操心。当你写内核模块时,通常你想访问内核的内存段,它由系统自动的处理。然而,当内存缓冲区中的内容需要在当前进程和内核中传递时,内核的函数收到的是指向位于进程的内存段中的内存缓冲区的指针。 put_user 和 get_user 宏可以让你访问那些内存。
范例 procfs.c
/* procfs.c - 创建一个可以输入输出的在 /proc 中的“文件”。 *//* Copyright (C) 1998-1999 by Ori Pomerantz *//* 必要头文件 *//* 标准头文件 */#include /* 内核工作 */#include /* 明确指定是模块 *//* 处理 CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include#endif/* 使用 proc 文件系统所必要的 */#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;}/* 我们使用 put_user 将字符串从内核的内存段中拷贝到调用我们的文件的进程的内存段中。* 顺便说一下, get_user的用法相反。*/sprintf(message, "Last input:%s", Message);for(i=0; i put_user(message[i], buf+i);/* 注意,我们假设消息的长度小于 len,或者被截短。在真实的情况下,如果消息的长度小于* len 那么我们会返回 len 而在下次调用时将用消息的第 len+1 个字节开始填充。 */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);/* 在 2.2 版中 get_user 的语义改变了,它不再返回一个字符而是期待将一个变量作为它的第一参数* 和一个用户内存段的指针作为第二参数。** 这个改变的原因是在 2.2 版中, get_user 也可以读短整型数或整数。它是通过使用sizeof来知道它将 * 读到何种变量的,为此,它需要那个变量自身。*/#elseMessage[i] = get_user(buf+i);#endifMessage[i] = '