5.1. /proc 文件系统

在 Linux 中,内核和内核模块还有另一种机制可以向进程发送信息 --- 那就是/proc文件系统。 最初设计目的是为了方便访问有关进程的信息(因此得名),现在内核的每个部分都使用它来报告一些有趣的东西,例如/proc/modules它提供了模块列表,以及/proc/meminfo它提供内存使用情况统计信息。

使用 proc 文件系统的方法与设备驱动程序中使用的方法非常相似 --- 创建一个结构,其中包含/proc文件所需的所有信息,包括指向任何处理函数的指针(在我们的例子中只有一个,即当有人尝试从/proc文件读取时调用的函数)。 然后,init_module向内核注册该结构,而cleanup_module则取消注册。

我们使用proc_register_dynamic[1] 的原因是,我们不想预先确定用于我们文件的 inode 编号,而是允许内核确定它以防止冲突。 普通文件系统位于磁盘上,而不是仅仅在内存中(/proc就是这种情况),在那种情况下,inode 编号是指向磁盘位置的指针,该位置是文件索引节点(简称 inode)的所在位置。 inode 包含有关文件的信息,例如文件的权限,以及指向可以找到文件数据的磁盘位置的指针。

因为在文件打开或关闭时我们不会被调用,所以我们没有地方放置try_module_gettry_module_put在这个模块中,如果文件被打开然后模块被移除,就无法避免后果。

这里有一个简单的例子,展示了如何使用 /proc 文件。 这是 /proc 文件系统的 HelloWorld。 它分为三个部分:创建文件/proc/helloworld在函数init_module中,当文件/proc/helloworld在回调函数procfs_read中被读取时,返回一个值(和一个缓冲区),以及删除文件/proc/helloworld在函数cleanup_module.

/proc/helloworld是在加载模块时使用函数create_proc_entry创建的。 返回值是一个 'struct proc_dir_entry *',它将用于配置文件/proc/helloworld(例如,此文件的所有者)。 空返回值表示创建失败。

每次,每次文件/proc/helloworld被读取时,都会调用函数procfs_read。 此函数的两个参数非常重要:缓冲区(第一个参数)和偏移量(第三个参数)。 缓冲区的内容将返回给读取它的应用程序(例如 cat 命令)。 偏移量是文件中的当前位置。 如果函数的返回值不为空,则再次调用此函数。 因此,请小心使用此函数,如果它永远不返回零,则读取函数将被无限调用。

% cat /proc/helloworld
HelloWorld!
        

例 5-1. procfs1.c

/*
 *  procfs1.c -  create a "file" in /proc
 *
 */

#include <linux/module.h>	/* Specifically, a module */
#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/proc_fs.h>	/* Necessary because we use the proc fs */

#define procfs_name "helloworld"

/**
 * This structure hold information about the /proc file
 *
 */
struct proc_dir_entry *Our_Proc_File;

/* Put data into the proc fs file.
 * 
 * Arguments
 * =========
 * 1. The buffer where the data is to be inserted, if
 *    you decide to use it.
 * 2. A pointer to a pointer to characters. This is
 *    useful if you don't want to use the buffer
 *    allocated by the kernel.
 * 3. The current position in the file
 * 4. The size of the buffer in the first argument.
 * 5. Write a "1" here to indicate EOF.
 * 6. A pointer to data (useful in case one common 
 *    read for multiple /proc/... entries)
 *
 * Usage and Return Value
 * ======================
 * A return value of zero means you have no further
 * information at this time (end of file). A negative
 * return value is an error condition.
 *
 * For More Information
 * ====================
 * The way I discovered what to do with this function
 * wasn't by reading documentation, but by reading the
 * code which used it. I just looked to see what uses
 * the get_info field of proc_dir_entry struct (I used a
 * combination of find and grep, if you're interested),
 * and I saw that  it is used in <kernel source
 * directory>/fs/proc/array.c.
 *
 * If something is unknown about the kernel, this is
 * usually the way to go. In Linux we have the great
 * advantage of having the kernel source code for
 * free - use it.
 */
int
procfile_read(char *buffer,
	      char **buffer_location,
	      off_t offset, int buffer_length, int *eof, void *data)
{
	int ret;
	
	printk(KERN_INFO "procfile_read (/proc/%s) called\n", procfs_name);
	
	/* 
	 * We give all of our information in one go, so if the
	 * user asks us if we have more information the
	 * answer should always be no.
	 *
	 * This is important because the standard read
	 * function from the library would continue to issue
	 * the read system call until the kernel replies
	 * that it has no more information, or until its
	 * buffer is filled.
	 */
	if (offset > 0) {
		/* we have finished to read, return 0 */
		ret  = 0;
	} else {
		/* fill the buffer, return the buffer size */
		ret = sprintf(buffer, "HelloWorld!\n");
	}

	return ret;
}

int init_module()
{
	Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL);
	
	if (Our_Proc_File == NULL) {
		remove_proc_entry(procfs_name, &proc_root);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
		       procfs_name);
		return -ENOMEM;
	}

	Our_Proc_File->read_proc = procfile_read;
	Our_Proc_File->owner 	 = THIS_MODULE;
	Our_Proc_File->mode 	 = S_IFREG | S_IRUGO;
	Our_Proc_File->uid 	 = 0;
	Our_Proc_File->gid 	 = 0;
	Our_Proc_File->size 	 = 37;

	printk(KERN_INFO "/proc/%s created\n", procfs_name);	
	return 0;	/* everything is ok */
}

void cleanup_module()
{
	remove_proc_entry(procfs_name, &proc_root);
	printk(KERN_INFO "/proc/%s removed\n", procfs_name);
}

注释

[1]

在 2.0 版本中,在 2.2 版本中,如果我们把 inode 设置为零,这是自动完成的。