Linux proc文件系统介绍

最初开发/proc文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。
/proc文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。
尽管像本文这样短小的一篇文章无法详细介绍/proc的所有用法,但是它依然对这两种用法进行了展示,从而可以让我们体会一下/proc是多么强大。图1是对/proc中部分元素进行一次交互查询的结果。它显示的是/proc文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在GNU/Linux中创建的第一个进程是init进程,因此它的process-id为1。然后对这个目录执行一个 ls命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。例如,要查看init的command-line项的内容,只需对cmdline文件执行cat命令。

                                                               图1 对/proc的交互过程

/proc中另外一些有趣的文件有:cpuinfo,它标识了处理器的类型和速度;pci:显示在PCI总线上找到的设备;modules:标识了当前加载到内核中的模块。

图2展示了对/proc中的一个虚拟文件进行读写的过程。这个例子首先检查内核的TCP/IP栈中的IP转发的目前设置,然后再启用这种功能。

            图2 对/proc里的文件进行读写(配置内核)

另外,我们还可以使用sysctl来配置这些内核条目。

顺便说一下,/proc文件系统并不是GNU/Linux系统中的惟一一个虚拟文件系统。在这种系统上,sysfs是一个与/proc类似的文件系统,但是它的组织更好(从 /proc 中学习了很多教训)。不过 /proc 已经确立了自己的地位,因此即使sysfs与/proc相比有一些优点,/proc也依然会存在。还有一个debugfs文件系统,不过(顾名思义)它提供的更多是调试接口。debugfs的一个优点是它将一个值导出给用户空间非常简单(实际上这不过是一个调用而已)。

/proc目录下常见的文件介绍:

/proc/apm 
高级电源管理(APM)版本信息及电池相关状态信息,通常由apm命令使用; 
proc/buddyinfo 
用于诊断内存碎片问题的相关信息文件;
/proc/cmdline 
在启动内核时传递至内核的相关参数信息,这些信息通常由lilo或grub等启动管理工具进行传递;

/proc/cpuinfo 
处理器的相关信息的文件; 
/proc/crypto 
系统上已安装的内核使用的密码算法及每个算法的详细信息列表;
/proc/devices 
系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名;
/proc/dma 
每个正在使用且注册的ISA DMA通道的信息列表;
/proc/fb 
帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息; 
/proc/filesystems 
当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型; 
/proc/interrupts 
ARM体系架构系统上每个IRQ相关的中断号列表;

/proc/iomem 
每个物理设备上的记忆体(RAM或者ROM)在系统内存中的映射信息; 
/proc/kmsg 
此文件用来保存由内核输出的信息,通常由/sbin/klogd或/bin/dmsg等程序使用,不要试图使用查看命令打开此文件; 
/proc/meminfo 
系统中关于当前内存的利用状况等的信息,常由free命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值; 
/proc/modules 
当前装入内核的所有模块名称列表,可以由lsmod命令使用,也可以直接查看;如下所示,其中第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态(Live:已经装入;Loading:正在装入;Unloading:正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量; 
/proc/partitions 
块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目,如下图所示; 

/proc/slabinfo 
在内核中频繁使用的对象(如inode、dentry等)都有自己的cache,即slab pool,而/proc/slabinfo文件列出了这些对象相关slap的信息;详情可以参见内核文档中slapinfo的手册页; 
/proc/uptime 
系统上次启动以来的运行时间,如下所示,其第一个数字表示系统运行时间,第二个数字表示系统空闲时间,单位是秒;

/proc/version 
当前系统运行的内核版本号,如下所示;

/proc/zoneinfo 
内存区域(zone)的详细信息列表,信息量较大;

如何在/proc目录下创建目录或文件:

要在/proc文件系统中创建一个虚拟文件,请使用proc_create()或proc_create_data()函数。这个函数可以接收一个文件名、一组权限和这个文件在/proc文件系统中出现的位置。这两个函数的返回值是一个proc_dir_entry类型指针(或者为NULL,说明在发生了错误时)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。函数的原型和proc_dir_entry结构中的一部分如下图所示。

struct proc_dir_entry {
	/*
	 * number of callers into module in progress;
	 * negative -> it's going away RSN
	 */
	atomic_t in_use;
	atomic_t count;		/* use count */
	struct list_head pde_openers;	/* who did ->open, but not ->release */
	/* protects ->pde_openers and all struct pde_opener instances */
	spinlock_t pde_unload_lock;
	struct completion *pde_unload_completion;
	const struct inode_operations *proc_iops;	//Inode operations functions
	const struct file_operations *proc_fops;	//File operations functions
	void *data;			//私有数据指针,在操作函数中可以使用的到
	unsigned int low_ino;
	nlink_t nlink;
	kuid_t uid;			//文件的user id
	kgid_t gid;			//文件的group id
	loff_t size;
	struct proc_dir_entry *parent;	//父目录
	struct rb_root_cached subdir;
	struct rb_node subdir_node;
	umode_t mode;		//访问权限
	u8 namelen;
	char name[];		//创建的虚拟文件或目录名
} __randomize_layout;

struct proc_dir_entry *proc_create(const char *name, umode_t mode,struct proc_dir_entry *parent,const struct file_operations *proc_fops);
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,struct proc_dir_entry *parent,const struct file_operations *proc_fops,void *data);

创建目录的函数原型如下:

struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent);
struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,struct proc_dir_entry *parent, void *data);

前者不可以传入私有数据,后者可以。关于私有数据的操作,我们后面会使用一个例子来说明。

要从/proc中删除一个文件,可以使用remove_proc_entry()函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在/proc文件系统中的位置(parent)。函数原型如下:

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

当在/proc文件系统下创建文件之后用户是怎么访问这个文件的,其实看到proc_create()函数的最后一个参数,传入的是一个file_operations结构体指针,其实,就是通过里面的read和write回调函数来实现的。下面我们来看一个具体的例子。

在/proc目录下创建hello_proc文件,并往里面写入数据或获取里面的数据。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>

/* 定义一个proc_dir_entry结构体类型变量*/
struct proc_dir_entry *hello_proc = NULL;
/* 定义一个全局数据,用来保存用户空间返回的数据 */
static char hello_data[20] = {};

/* 如果使用cat此节点,则传入的count为4K,直到读取的数据大小为4K,也就是直到此函数返回0
   当此函数返回0时,读取到的内容是不显示的。
 */
static ssize_t hello_proc_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
	int ret = 0;
	/* 首先清空用户空间的user_buf地址的内容,有可能显示杂乱信息 */
    if (clear_user(user_buf, count)) {
        printk(KERN_ERR "clear error\n");
        return -EIO;
    }
	
	/* 从hello_data数组中读取数据到用户空间user_buf,读取的长度应该是字符串的大小 */
	ret = simple_read_from_buffer(user_buf, count, ppos, hello_data, strlen(hello_data));
	
    return ret;
}

/*
  用户空间使用echo往此节点写入数据,只有要写入的数据写完之后,也就是返回count是,此函数此不会被调用
*/
static ssize_t hello_proc_write(struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
	int ret;
    printk("hello_proc_write:count is %d.\n",count);
	/* 写入数据之前,将数组清空 */
	memset(hello_data,0,sizeof(hello_data));
	/* 将用户空间写入的数据保存到数据中 */
	ret = simple_write_to_buffer(hello_data, sizeof(hello_data),ppos,user_buf,count);
	
	printk("hello_proc_write:ret is %d.\n",ret);
	printk("hello_proc_write:user_buf is %s",hello_data);
    /* 返回用户空间写入字符串的大小 */
	return count;
}

/* 定义一个file_operations结构体变量 */				      
static const struct file_operations hello_proc_fops = {
    .owner      = THIS_MODULE,
	.read		= hello_proc_read,	//使用cat时的回调函数
	.write      = hello_proc_write,	//使用echo时的回调函数
};

/* 驱动入口函数 */
static int __init proc_test_init(void)
{
	/* 调用proc_create()函数创建"hello_proc"文件 */
    hello_proc = proc_create("hello_proc", 0,NULL,&hello_proc_fops);
    return 0;
}

/* 驱动出口函数 */
static void __exit proc_test_exit(void)
{	
	/* 删除此文件 */
	if(hello_proc)
		remove_proc_entry("hello_proc", NULL);
}

module_init(proc_test_init);
module_exit(proc_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("proc filesystem test by Haitao Cai");

将此文件上传到服务器并编译模块,然后将此模块上传到开发板上面。试验过程如下:

生成的节点如下:

cat /proc/hello_proc的输出信息如下:

echo hello > /proc/hello_proc的输出信息如下:

再次cat /proc/hello_proc的输出信息如下:

在使用proc_create()函数创建文件时,第二个参数是关于权限的设置,我们这里使用默认的0,也就是只有读取权限,这里就不再详细介绍,如果感兴趣可以自己做实验。

完毕!

猜你喜欢

转载自blog.csdn.net/caihaitao2000/article/details/81674013