经常看到应用程序可以通过/proc目录下的文件读取一些系统信息,而驱动程序也可以通过/proc对外提供一些信息,或者通过/proc给驱动程序设置一些参数。/proc其实是一个虚拟文件系统,并不占用磁盘,而是存在于内存中。这是一个比ioctl()好用得多的、可用于应用程序与驱动程序交互的机制!
在我们之前的示例中,所有的操作都是针对/dev/aMsg这个设备文件操作的。如果我们想要获取一些额外的状态信息,比如获取aMsg中缓冲区中有效数据的长度,那么要怎么做呢?最简单的办法就是使用/proc文件系统。比如我可以创建一个虚拟文件叫做/proc/aMsg/length,那么只要使用cat命令访问:
cat /proc/aMsg/length
那么就会返回明文的:
length=6
这样的文本信息。
要在驱动程序中使用/proc文件系统其实很简单,至少在3.1内核之后变得非常简单了。首先通过proc_mkdir()函数在/proc根目录上创建一个子目录,比如/proc/aMsg,然后再使用proc_create()在子目录中创建一个文件节点,比如/proc/aMsg/length,并绑定一个struct file_operations结构体就可以了。换句话说,每一个/proc文件节点都可以看做一个新的/dev节点,只有为其实现read()和write()操作即可(可以只实现其一)。
就以上面提到的这个场景为例。首先,创建/proc/aMsg子目录:
//proc/aMsg目录 static struct proc_dir_entry* g_proc_dir; //.... //创建/proc/aMsg目录 g_proc_dir=proc_mkdir("aMsg",0); if(g_proc_dir==0) { printk("Unable to mkdir /proc/aMsg\n"); return -ENOMEM; }
然后,创建/proc/aMsg/length节点:
//创建/proc/aMsg/length文件 struct proc_dir_entry* t_proc_length=proc_create("length",0644,g_proc_dir,&g_amsg_proc_length_fops); if(t_proc_length==0) { printk("Unable to create /proc/aMsg/length\n"); return -ENOMEM; }
其中涉及到一个proc_create()函数,其第一个参数是节点的文件名,第二个参数是节点的访问权限,第三个参数是其隶属的/proc子目录,最后一个参数是一个struct file_operations结构体的指针。这个struct file_operations应该很熟悉了吧?一般只要实现read()或者write()就好了,至于能不能用其他高级的文件操作,我还没试过。这个结构体很简单,就只实现了read()操作,用明文返回“length=%d”。
//对/proc/aMsg/length的read操作 static ssize_t amsg_proc_length_read(struct file* p_file,char* p_buf,size_t p_count,loff_t* p_offset) { //结果缓冲区 char t_buf[64]; sprintf(t_buf,"length=%d\n",g_length); //结果长度 int t_length=strlen(t_buf); //最多能够读取的字节数(p_count和t_length之间较小者) int t_size=(p_count<t_length?p_count:t_length); //没有成功拷贝的字节数 int t_rest=copy_to_user(p_buf,t_buf,t_size); //返回成功拷贝的字节数 return t_size-t_rest; } //填充/proc/aMsg/length的file_operations结构体 static struct file_operations g_amsg_proc_length_fops= { .owner=THIS_MODULE, .read=amsg_proc_length_read };
是不是非常简单?好吧,直接把完整代码贴上了:
aMsg.c
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/proc_fs.h> #include <linux/kernel.h> MODULE_LICENSE("Dual BSD/GPL"); //主设备号 #define DEV_MAJOR 224 //缓冲区大小 #define DEV_BUF_SZ 1024 //当前字符串长度 static int g_length=0; //缓冲区 static char g_buffer[DEV_BUF_SZ]; //proc/aMsg目录 static struct proc_dir_entry* g_proc_dir; //read操作 static ssize_t amsg_read(struct file* p_file,char* p_buf,size_t p_count,loff_t* p_offset) { //最多能够读取的字节数(p_count和g_length之间较小者) int t_size=(p_count<g_length?p_count:g_length); //没有成功拷贝的字节数 int t_rest=copy_to_user(p_buf,g_buffer,t_size); //不管结果如何,都清空缓冲区 g_length=0; //返回成功拷贝的字节数 return t_size-t_rest; } //write操作 static ssize_t amsg_write(struct file* p_file,const char* p_buf,size_t p_count,loff_t* p_offset) { //最多能够写入的字节数(p_count和sizeof(g_buffer)之间较小者) int t_size=(p_count<sizeof(g_buffer)?p_count:sizeof(g_buffer)); //没有成功拷贝的字节数 int t_rest=copy_from_user(g_buffer,p_buf,t_size); //成功拷贝的字节数,也就是字符串的长度 g_length=t_size-t_rest; //返回成功拷贝的字节数 return g_length; } //填充/dev/aMsg的file_operations结构体 static struct file_operations g_amsg_fops= { .owner=THIS_MODULE, .read=amsg_read, .write=amsg_write }; //对/proc/aMsg/length的read操作 static ssize_t amsg_proc_length_read(struct file* p_file,char* p_buf,size_t p_count,loff_t* p_offset) { //结果缓冲区 char t_buf[64]; sprintf(t_buf,"length=%d\n",g_length); //结果长度 int t_length=strlen(t_buf); //最多能够读取的字节数(p_count和t_length之间较小者) int t_size=(p_count<t_length?p_count:t_length); //没有成功拷贝的字节数 int t_rest=copy_to_user(p_buf,t_buf,t_size); //返回成功拷贝的字节数 return t_size-t_rest; } //填充/proc/aMsg/length的file_operations结构体 static struct file_operations g_amsg_proc_length_fops= { .owner=THIS_MODULE, .read=amsg_proc_length_read }; //模块初始化代码 static int amsg_init_module(void) { //注册字符设备(这是Old way) int t_ret=register_chrdev(DEV_MAJOR,"aMsg",&g_amsg_fops); //注册失败的处理 if(t_ret<0) { printk("Unable to register\n"); return t_ret; } //创建/proc/aMsg目录 g_proc_dir=proc_mkdir("aMsg",0); if(g_proc_dir==0) { printk("Unable to mkdir /proc/aMsg\n"); return -ENOMEM; } //创建/proc/aMsg/length文件 struct proc_dir_entry* t_proc_length=proc_create("length",0644,g_proc_dir,&g_amsg_proc_length_fops); if(t_proc_length==0) { printk("Unable to create /proc/aMsg/length\n"); return -ENOMEM; } return 0; } //模块清理代码 static void amsg_cleanup_module(void) { //注销字符设备 unregister_chrdev(DEV_MAJOR,"aMsg"); //删除/proc/aMsg/length文件 remove_proc_entry("length",g_proc_dir); //删除/proc/aMsg目录 remove_proc_entry("aMsg",0); } module_init(amsg_init_module); module_exit(amsg_cleanup_module);
Makefile
obj-m := aMsg.o KERNEL_DIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules clean: rm *.o *.ko *.mod.c .PHONY:clean
make、insmod、mknod之后,可以有了/proc/aMsg/length文件了。使用cat读取:
cat /proc/aMsg/length
可以发现不停输出“length=0”,这符合预期。然后在一个终端中,以root身份执行:
echo hello > /dev/aMsg
此时再使用cat读取/proc/aMsg/length,可以看到不停输出"length=6"。咦,为啥是6而不是5?这是因为echo命令自动加上了一个换行符"\n"。OK,一切正常,说明/proc/aMsg/length的read()实现正常了!那么write()操作也很简单了,不再赘述~