/proc 文件系统是由软件创建,被内核用来向外界报告信息的一个文件系统。/proc 下面的每一个文件都和一个内核函数相关联,当文件的被读取时,与之对应的内核函数用于产生文件的内容。我们已经见到了很多这样的文件,例如,/proc/modules 总是返回当前内核中加载的模块。
/proc 广泛的应用在 linux 文件系统中,现代 linux 发行版上的许多应用程序,例如 ps ,top 和 uptime 都从 /proc 获取他们所需要的信息。一些驱动程序也通过 /proc 文件系统报告信息,当然你的驱动程序也可以这么做。 /proc 文件系统是动态的,所以你的模块可以在任何时候添加或者删除入口。完全特征的 /proc 入口可能会是一个复杂的猛兽,它们既可以读也可以写。然而,大多数时候,/proc 中的文件是只读的。这里考虑的是只读的 /proc 文件。
然而,在继续之前,必须强调在 /proc 下面添加文件是不提倡的。/proc 文件系统被内核开发者看作是不可控制的混乱,这和它最初的目的--报告系统中运行的进程的信息相差十万八千里。在新的代码中推荐的获取信息的方式是通过 sysfs 。但是,使用 sysfs 是需要对 linux 设备模型有一个理解。并且,/proc 下面的文件较容易创建,并且完全符合调试的目的,所以才会学习 /proc 文件系统。
2.创建 /proc 文件
如果你定义了一个 read_proc 函数,你需要将它和 /proc 层次中的入口联系起来。这是通过一个叫做 create_proc_read_entry 的函数来实现的,这个函数的函数原型如下:
- struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode,struct proc_dir_entry *base,read_proc_t *read_proc,void *data);
struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode,struct proc_dir_entry *base,read_proc_t *read_proc,void *data);
上面的函数原型中,name 就是要创建的文件的名字;mode 就是文件的保护掩码,可以使用 0 来使用系统默认的掩码;base 指定了创建的文件所在的目录,如果 base 为 NULL,创建的文件就存在于 /proc 目录之下;read_proc 就是实现这个文件的函数;data 参数被内核忽略,但是却被传递给 read_proc 函数。举一个例子:
- create_proc_read_entry("scullmem", 0/*default mode*/, NULL /*parent dir*/, scull_read_procmem, NULL /*client data*/);
create_proc_read_entry("scullmem", 0/*default mode*/, NULL /*parent dir*/, scull_read_procmem, NULL /*client data*/);
这个例子中,我们在 /proc 下面创建了一个文件,名字为 scullmem,采用的是系统默认的保护权限。
目录入口指针可以用来在 /proc 下创建整个目录架构,然而,注意将一个文件放到 /proc 子目录中的一个更加容易的做法是将子目录的名字作为要创建的新文件名字的一部分路径,只要那个子目录已经存在。例如一个经常被忽略的惯例就是和驱动程序相关的 /proc 文件应该放到一个 driver 子目录中。例如,上面的例子中,我们想要将所创建的文件 scullmem 放到 driver 中,只需要将第一个参数修改为 "driver/scullmem" 就可以了。
/proc 中文件在模块被卸载的时候应该被删除。remove_proc_entry 所做的事情和 create_proc_read_entry 所做的事情相反。例如:
- remove_proc_entry("scullmem",NULL /*parent dir*/);
remove_proc_entry("scullmem",NULL /*parent dir*/);
不能成功的移除文件将导致这个文件在不合适的时候被调用,或者是,模块被卸载后,内核就崩溃了。当向上面一样使用 /proc 的时候,必须记住几个麻烦的事——难怪现在不推荐使用这种方式了。最重要的问题就是在删除 /proc 文件的时候了,在删除的时候文件可能仍然在使用。因为 /proc 文件没有相关的所有者,所以使用它们的时候并不会对引用计数起作用。这个问题可以简单的通过在删除文件之前使用下面的命令引起:
- sleep 100 < /proc/myfile
sleep 100 < /proc/myfile
另外一个问题是注册了两个一样名字的文件。内核信任驱动程序,并不检查文件的名字是否备注册过,所以如果你不小心,就可能会注册多个相同名字的文件。这就像在教室里发生的著名的问题一样,这些文件不能区分,不管是你在访问它们还是调用remove_proc_entry的时候。
3.在 /proc中实现文件
所有使用 /proc 的模块应该包括 <linux/proc_fs.h> 头文件来定义合适的函数。创建一个只读的 /proc 文件,你的驱动程序必须实现一个函数,当这个文件被读时,来产生一些数据。当一些进程使用 read 函数读取文件的时候,请求就通过这个函数到达了你的模块。我们会首先看一下这个函数,并且之后介绍注册的接口。当一个进程读取 /proc 文件的时候,内核就分配了一页的内存,驱动程序可以将要返回给用户空间的数据写到这个页里面。那个缓存被传递给了你的 read_proc 函数。read_proc 函数的原型如下:
- int (*read_proc)(char *page,char **start,off_t offset,int count,int *eof,void *data);
int (*read_proc)(char *page,char **start,off_t offset,int count,int *eof,void *data);
参数 page 是缓冲区的指针,你可以往里面写入数据;start 被函数用来说明感兴趣的数据被写到了页的位置。offset 和 count 与 read 函数的参数意义相同;eof 指向了一个整数,必须由驱动程序来设定报告没有返回的数据了,data 是依赖于特定的驱动的数据指针,你可以用来作为内部记账。
这个函数应该返回写入到页缓冲区中的数据字节数。就像 read 函数一样。其它的输出是 *eof 和 *start 。eof 是一个简单的标记,但是 start 的使用方法是有写复杂的,它的目的视为了帮助实现大的(超过一页)的 /proc 文件。start 参数并不常用,它的目的使用来指示返回给用户空间的住局在一页中的哪个位置。当 read_proc 函数被调用的时候,*start 参数将会被设置为 NULL。如果你保留它为 NULL 的话,内核会假设数据放到页里面时,参数 offset 为 0。换句话说,内核假设了一个头脑简单的 read_proc 版本,它把虚拟文件的所有内容写到页里面,而忽略了 offset 参数。相反,如果给 *start 设置了一个不是 NULL 的值,内核就假设由 *start 指向的数据将 offset 考虑在内,并且已经准备好返回给用户空间了。一般来说,简单的 proc_read 方法返回很少量的数据常常忽略了 *start 参数。更加复杂的方法才会设置页的 *start 的值,并且仅仅将数据放到所请求的 offset 那里。
4.实例
在这里,我们写了一个简单的例子来演示一下 /proc 的使用:
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/proc_fs.h>
- int read_proc(char *page,char **start,off_t offset,int count,int *eof,void *data);
- static int __init test_proc_init(void)
- {
- create_proc_read_entry("read_proc",0,NULL,read_proc,NULL);
- return 0;
- }
- static void __exit test_proc_exit(void)
- {
- remove_proc_entry("read_proc",NULL);
- }
- int read_proc(char *page,char **start,off_t offset,int count,int *eof,void *data)
- {
- int len = sprintf(page,"%s\n","hello world");
- return len;
- }
- module_init(test_proc_init);
- module_exit(test_proc_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("wangxq");
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
int read_proc(char *page,char **start,off_t offset,int count,int *eof,void *data);
static int __init test_proc_init(void)
{
create_proc_read_entry("read_proc",0,NULL,read_proc,NULL);
return 0;
}
static void __exit test_proc_exit(void)
{
remove_proc_entry("read_proc",NULL);
}
int read_proc(char *page,char **start,off_t offset,int count,int *eof,void *data)
{
int len = sprintf(page,"%s\n","hello world");
return len;
}
module_init(test_proc_init);
module_exit(test_proc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangxq");
当插入这个模块到内核之后,使用命令:
- cat /proc/read_proc
cat /proc/read_proc
就会看到输出 hello world。