对/proc文件系统进行读写操作

2011年5月19日 由 edsionte 留言 »

本博客之前的文章中多次涉及到/proc文件系统,下面的几条命令都在曾经的文章中出现过:

cat /proc/interrupts
cat /proc/devices
cat /proc/kallsyms | grep super_blocks

第一条命令用于查看系统内已注册的中断信息,包括中断号、已接受的手段请求和驱动器名称等;第二条命令用于查看系统内已注册的字符设备和块设备信息,包括设备号和设备名称;第三条命令用于在内核符号表中检索super_blocks符号的的地址,kallsyms文件包括内核中所有的标示符及其地址。

1.概述

proc即process的缩写,最初的proc文件系统只是存放进程的相关信息。但现在的/proc文件系统除此之外还包含系统的状态信息和配置信息。
通过ls命令就可以查看/proc文件系统所包含的内容。

edsionte@edsionte-desktop:/proc$ ls
1      1290   1469  1541  1627   19612  29    49    9          dri              mdstat          sys
10     13     1471  1544  1630   19613  3     5     908        driver           meminfo         sysrq-trigger
1013   1301   1474  1548  1632   19629  30    50    913        edsionte_procfs  misc            sysvipc
…………

其中以数字为名的目录即为系统中正在运行的进程信息,数字即为进程的pid。比如我们可以进入init进程的目录,查看它的地址空间:

edsionte@edsionte-desktop:/proc/1$ sudo cat maps
[sudo] password for edsionte:
00110000-00263000 r-xp 00000000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00263000-00264000 ---p 00153000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00264000-00266000 r--p 00153000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00266000-00267000 rw-p 00155000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00267000-0026a000 rw-p 00000000 00:00 0
0026a000-00272000 r-xp 00000000 08:07 704713     /lib/tls/i686/cmov/libnss_nis-2.11.1.so
00272000-00273000 r--p 00007000 08:07 704713     /lib/tls/i686/cmov/libnss_nis-2.11.1.so
00273000-00274000 rw-p 00008000 08:07 704713     /lib/tls/i686/cmov/libnss_nis-2.11.1.so
00471000-0048b000 r-xp 00000000 08:07 1048610    /sbin/init
…………

除了查看进程的相关信息,我们还可以通过打印相关文件来查看系统的当前运行状态。比如查看当前内存的使用情况:

edsionte@edsionte-desktop:/proc$ cat meminfo
MemTotal:         961368 kB
MemFree:          145264 kB
Buffers:           31648 kB
Cached:           297716 kB
SwapCached:        14436 kB
…………

总之,/proc文件系统相当于内核的一个快照,该目录下的所有信息都是动态的从正在运行的内核中读取。

基于这种原因,/proc文件系统就成为了用户和内核之间交互的接口。一方面,用户可以从/proc文件系统中读取很多内核释放出来的信息;另一方面,内核也可以在恰当的时候从用户那里得到输入信息,从而改变内核的相关状态和配置。

相比传统的文件系统,/proc是一种特殊的文件系统,即虚拟文件系统。这里的虚拟是强调/proc文件系统下的所有文件都存在于内存中而不是磁盘上。也就是说/proc文件系统只占用内存空间,而不占用系统的外存空间。

2.用户态和内核态之间的数据通信

既然内核的数据以/proc文件系统的形式呈现给用户,也就是说内核的信息以文件的形式存在于该文件系统中,那么/proc文件系统就应当提供一组接口对其内的文件进行读写操作。接下来我们以一个实际的内核模块程序easyProc.c为例,说明/proc文件系统的常用接口。该程序中依次创建了几个虚拟文件,然后在用户态对这些文件进行读写测试。

2.0数据结构

每个虚拟文件都对应一个proc_dir_entry类型的数据结构,该结构具体定义如下:

struct proc_dir_entry {
	const char *name;			// virtual file name
	mode_t mode;				// mode permissions
	uid_t uid;				// File's user id
	gid_t gid;				// File's group id
	struct inode_operations *proc_iops;	// Inode operations functions
	struct file_operations *proc_fops;	// File operations functions
	struct proc_dir_entry *parent;		// Parent directory
	...
	read_proc_t *read_proc;			// /proc read function
	write_proc_t *write_proc;		// /proc write function
	void *data;				// Pointer to private data
	atomic_t count;				// use count
	...
};

除了保存该虚拟文件的基本信息外,该结构中还有read_proc和write_proc两个字段,下文中将有详细说明。

2.1创建目录

/proc文件系统中创建一个目录对应的函数接口如下:
struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent);
其中name为要创建的目录名;parent为这个目录的父目录,当要创建的目录位于/proc下时此参数为空。比如我们使用该函数在/proc下创建一个目录edsionte_procfs。

#define MODULE_NAME "edsionte_procfs"
struct proc_dir_entry *example_dir;
	example_dir = proc_mkdir(MODULE_NAME, NULL);
	if (example_dir == NULL) {
		rv = -ENOMEM;
		goto out;
	}
2.2创建普通文件

在/proc文件系统中创建一个虚拟文件可以使用如下的函数:

 static inline struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent) ;

该函数中name为要创建的文件名;mode为创建文件的属性;parent指向该文件父目录的指针,如果创建的虚拟文件位于/proc下,则这个参数为NULL。

比如我们通过该函数在/proc/edsionte_procfs目录下创建一个虚拟文件foo,其权限为644。其中example_dir指向我们刚创建的目录文件edsionte_procfs。

struct proc_dir_entry  *foo_file;
	foo_file = create_proc_entry("foo", 0644, example_dir);
	if (foo_file == NULL) {
		rv = -ENOMEM;
		goto no_foo;
	}
2.3.创建符号链接文件

当我们需要在/proc文件系统下创建一个符号链接文件时,可使用如下接口:

struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, const char *dest);

name参数为要创建的符号链接文件名;parent为该符号链接文件的父目录;dest为符号链接所指向的目标文件。

下面的代码演示了如何通过该函数来对已存在的虚拟文件jiffies创建符号链接文件jiffies_too:

	symlink = proc_symlink("jiffies_too", example_dir, "jiffies");
	if (symlink == NULL) {
		rv = -ENOMEM;
		goto no_symlink;
	}

我们内核模块加载函数中完成上述几个虚拟文件的创建工作。

2.4.删除文件或目录

既然有创建虚拟文件的函数,必然也就有删除虚拟文件的函数接口:

void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

该函数中的参数name和parent与上述函数的参数意义相同。
在示例程序中,我们在卸载函数中完成上述几个文件的删除工作:

	remove_proc_entry("jiffies_too", example_dir);
	remove_proc_entry("foo", example_dir);
	remove_proc_entry("MODULE_NAME", NULL);
2.5读写proc文件

如果只是创建了虚拟文件,那么它并不能被读写。为此,我们必须为每个虚拟文件挂接读写函数,如果该虚拟文件是只读的,那么只需挂载相应的读函数。

正如上面所述,每个虚拟文件对应的proc_dir_entry结构都有read_proc和write_proc两个字段,它们均为函数指针,其各自的类型定义如下:

 typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);
  typedef int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);

如果要实现对虚拟文件的读写,则需要实现上述两个函数接口。对于我们的示例程序,我们的实现方法如下:

static int proc_read_foobar(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int len;
	struct fb_data_t *fb_data = (struct fb_data_t *)data;

	//将fb_data的数据写入page
	len = sprintf(page, "%s = %s\n", fb_data->name, fb_data->value);

	return len;
}

static int proc_write_foobar(struct file *file, const char *buffer, unsigned long count, void *data)
{
	int len;
	struct fb_data_t *fb_data = (struct fb_data_t *)data;

	if (count > FOOBAR_LEN)
		len = FOOBAR_LEN;
	else
		len = count;

	//写函数的核心语句,将用户态的buffer写入内核态的value中
	if (copy_from_user(fb_data->value, buffer, len))
		return -EFAULT;

	fb_data->value[len] = '\0';

	return len;
}

当用户读我们刚创建的虚拟文件时,该文件对应的read_proc函数将被调用。该函数将数据写入内核的缓冲区中。上述读函数的例子中,缓冲区即为page。当用户给虚拟文件写数据时,write_proc函数将被调用,该函数从缓冲区buffer中读取count个字节的数据。

3.测试

接下来我们将进行一系列的读写测试。由于我们只为jiffies与其符号链接文件jiffies_too实现了读回调函数,因此它们为只读文件,当对这两个文件进行写操作时就会出现错误;对于foo和bar文件,我们为其实现了读、写函数,因此既可以对它们进行读操作也可以进行写操作。

root@edsionte-desktop:/proc/edsionte_procfs# cat jiffies
jiffies = 833619
root@edsionte-desktop:/proc/edsionte_procfs# cat jiffies_too
jiffies = 834442
root@edsionte-desktop:/proc/edsionte_procfs# cat bar
bar = bar
root@edsionte-desktop:/proc/edsionte_procfs# cat foo
foo = foo
root@edsionte-desktop:/proc/edsionte_procfs# echo "time" > jiffies
bash: echo: 写操作出错: 输入/输出错误
root@edsionte-desktop:/proc/edsionte_procfs# echo "time" > jiffies_too
bash: echo: 写操作出错: 输入/输出错误
root@edsionte-desktop:/proc/edsionte_procfs# echo "hello" >> bar
root@edsionte-desktop:/proc/edsionte_procfs# cat bar
bar = hello

示例程序可以在此下载完成代码。

参考:

1.边干边学-Linux内核指导;作者: 李善平;出版社: 浙江大学出版社;

2.使用 /proc 文件系统来访问 Linux 内核的内容;
http://www.ibm.com/developerworks/cn/linux/l-proc.html

广告位

2 条评论

  1. wqz说道:

    写得很棒,对我帮助很大。

    [回复一下]

    edsionte 回复:

    @wqz, 谢谢支持。

    [回复一下]

发表回复

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key