用户空间和内核空间通讯之【proc文件系统】
今天我们介绍还有一种用户内核空间通信的方法: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文件夹下的文件的读写操作。关于文件系统这里就不展开了,以后有时间再写个它的专题。
cdnversion='+~(-new Date()/36e5)];</script>