今天我们介绍另一种用户内核空间通信的方法:proc文件系统。
proc文件系统作为linux提供的一种虚拟文件系统并不占用实际外围存储空间,它仅存在于内存中,系统断电即消失。proc文件系统最开始的设计主要是为满足内核向用户态进程报告其状态而设计,并没有为输入做规定和说明。随着发展,现在的proc文件系统已经演变成一个“用户-内核”空间半双工的通信方式了(虽然目前已经开始有点混乱了,但某些早期开发的软件代码中还在继续使用这个文件系统)。用户不但可以从proc文件系统中读取内核的相关状态信息,还可以向其中写入数据以改变内核的某些行为状态。
/proc目录里主要存放由内核控制的状态信息,一般会动态改变。如果你对/proc目录执行'ls -l' 命令可以看到大部分文件都是 0 字节,原因是 procfs和其他常规的文件系统一样把自己注册到虚拟文件系统层 (VFS)。直到当VFS调用它,请求文件、目录的i-node的时候,procfs才根据内核中的信息动态地建立相应的文件和目录。我的系统中proc目录的结构如下,每个人可能不一样,这取决于你编译内核时所打开的选项:
这些文件的解释和意义如下:
cmdline:系统启动时输入给内核命令行参数 |
/proc目录下常见的就是上述几个文件和目录。需要格外注意的就是三个黄色的目录:net、scsi和sys。sys目录是可写的,可以通过它来访问或修改内核的某些控制参数,sysctl命令接口会用到/proc/sys目录,这里我就不展开了,在介绍sysctl章节时详细讨论。而net和scsi则依赖于内核配置,协议栈和我们前面介绍过的Netfitler都在/proc/net目录下建立了各自的某些控制信息所对应的文件。如果系统不支持scsi,则 scsi目录就不存在。
接下来我们就来实践一下Linux所提供给我们的proc机制来完成用户和内核空间的通信。如果要在/proc目录下创建一个目录,一般用:
struct proc_dir_entry *proc_mkdir(const char *name,
struct proc_dir_entry *parent)
其中,name为待创建的目录名;parent为待创建目录的上一级目录,如果为NULL则表示默认创建到/proc目录下。而如果要在/proc目录下创建一个文件,一般用:
struct proc_dir_entry *create_proc_entry(const char *name,
mode_t mode,
struct proc_dir_entry *parent)
name和parent的意义同proc_mkdir,mode指明了待创建文件的权限,即是否可读可写,或哪些用户可读,哪些用户可写。这两个函数均返回一个struct proc_dir_entry{}结构体的实例,该结构体定义在linux-2.6.21\include\linux\proc_fs.h文件中,如下:
点击(此处)折叠或打开
- struct proc_dir_entry {
- … …
- const struct inode_operations *proc_iops;
- const struct file_operations *proc_fops;
- … …
- read_proc_t *read_proc;
- write_proc_t *write_proc;
- … …
- };
比较重要的两个成员函数是read_proc和write_proc,分别指向proc目录下待创建的文件被“读”和“写”的时候的回调处理函数(重要)。
读函数的原型:
int read_proc(char *page, char **start, off_t off,int count, int *eof, void *data);
当我们通过诸如“cat”之类的命令来读取/proc目录下的文件内容时,内核会分配给proc读取程序一页大小的内存空间,即PAGE_SIZE大小,proc的驱动程序会自动将这块内存中的数据复制到用户空间,最终待访问的proc文件的read_proc回调函数会被调用,其中:
page:将需要传递给用户的数据复制到这个缓冲区地址里。这个缓冲区是内核空间的,不需要用户调用copy_to_user()之类的函数,系统会自动将page中的数据复制给用户。
start:在page所指向的缓冲区中需要复制给用户数据的起始地址。一般不使用。
off:用户打算读取文件时的偏移地址,即从这个地址开始读取。类似用户空间lseek移动文件指针的作用。
count:用户要求读取的字节数。
eof:如果读到文件结尾,当驱动程序的数据发送完毕时将这个值置为1,发送给用户,我们可以通过该值判断是否读取到文件末尾。
data:私有数据指针,一般不用。
写函数的原型:
int write_proc(struct file *file, const char __user *buffer,unsigned long count, void *data);
file:内核中一个打开的文件结构,通常忽略。
buffer:用户空间传递过来的数据指针,用户待写入文件的数据就存储在这个值所指向的地址区域中。而这家伙实际上是一个用户空间地址,内核中不能直接拿来用,需要调用诸如copy_from_user()之类的函数来讲用户空间的数据复制到内核空间来。
count:用户待写入文件的字节数。
data:一般不用。
删除proc文件比较简单,调用:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
即可,参数同上,其中name是要删除的proc文件的名称。看个proc文件的应用示例:
点击(此处)折叠或打开
- /* myproctest.c*/
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/stat.h>
- #include <linux/kernel.h>
- #include <linux/proc_fs.h>
- #include <asm/uaccess.h>
- MODULE_AUTHOR("Koorey Wung");
- MODULE_DESCRIPTION("procfs test module.");
- MODULE_LICENSE("GPL");
- #define PROCNAME "mytest"
- static struct proc_dir_entry * myproc_entry = NULL;
- static char msg[512]={0};
- static int my_read(char *page, char **start, off_t off,int count, int *eof, void *data)
- {
- int len = strlen(msg);
- if(off >= len)
- return 0;
- if(count > len-off)
- count = len-off;
- memcpy(page+off,msg+off,count);
- return off+count;
- }
- static int my_write(struct file *file, const char __user *buffer,unsigned long count, void *data)
- {
- unsigned long len = sizeof(msg);
- if(count >= len)
- count = len -1;
- if(copy_from_user(msg,(void*)buffer,count))
- return -EFAULT;
- msg[count]='\0';
- return count;
- }
- static int __init procTest_init(void)
- {
- myproc_entry = create_proc_entry(PROCNAME,0666,NULL);
- if(!myproc_entry){
- printk(KERN_ERR "can't create /proc/mytest \n");
- return -EFAULT;
- }
- myproc_entry->read_proc = my_read;
- myproc_entry->write_proc = my_write;
- return 0;
- }
- static void __exit procTest_exit(void)
- {
- remove_proc_entry(PROCNAME,NULL);
- }
- module_init(procTest_init);
- module_exit(procTest_exit);
编程生成myproctest.ko模块后,测试结果如下:
在上面的例子中我们看到read_proc的回调函数中我们确实没有调用copy_to_user()函数,结果还是正确误区地返回给用户。procfs既然属于一种特殊的文件系统,那我们我们是否可以像操作普通文件那样,对其进行扩充呢?答案是肯定,我们对上面的例子稍加改造,使用内核提供的文件系统的机制来实现对proc文件的读写操作,修改后的代码如下:
点击(此处)折叠或打开
- /* myproctest.c*/
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/stat.h>
- #include <linux/kernel.h>
- #include <linux/proc_fs.h>
- #include <asm/uaccess.h>
- MODULE_AUTHOR("Koorey Wung");
- MODULE_DESCRIPTION("procfs test module.");
- MODULE_LICENSE("GPL");
- #define PROCNAME "mytest"
- static struct proc_dir_entry * myproc_entry = NULL;
- static char msg[512]={0};
- static int my_file_read(struct file * file,char *data,size_t len,loff_t *off)
- {
- if(*off > 0)
- return 0;
- if(copy_to_user(data,msg,strlen(msg)))
- return -EFAULT;
- *off += strlen(msg);
- return strlen(msg);
- }
- static int my_file_write(struct file *file, const char *data,size_t len,loff_t *off)
- {
- if(copy_from_user(msg,(void*)data,len))
- return -EFAULT;
- msg[len]='\0';
- return len;
- }
- static struct file_operations my_file_test_ops = {
- .read = my_file_read,
- .write = my_file_write,
- };
- static int __init procTest_init(void)
- {
- myproc_entry = create_proc_entry(PROCNAME,0666,NULL);
- if(!myproc_entry){
- printk(KERN_ERR "can't create /proc/mytest \n");
- return -EFAULT;
- }
- myproc_entry->proc_fops = & my_file_test_ops;
- return 0;
- }
- static void __exit procTest_exit(void)
- {
- remove_proc_entry(PROCNAME,NULL);
- }
- module_init(procTest_init);
- module_exit(procTest_exit);
编译后验证,结果依然正确,这是我们站在文件系统的角度来实现proc目录下的文件的读写操作,关于文件系统这里就不展开了,以后有时间再写个它的专题。