编写一个简单的proc文件

来源:互联网 发布:2016中超网络直播乐视 编辑:程序博客网 时间:2024/05/17 02:36

proc文件的实现也需要编写出一个内核模块并插入内核。模块的编写过程我们已经Hello,Module过了,而proc文件的实现稍微复杂一些。

在linux内核模块编写中,常常会有一些结构体,结构体中定义了很多函数指针,内核模块的编写常常是围绕着实现这些函数指针而进行的。实现了这些接口便可以调用相应的函数把相应的功能模块插入内核(拗口),以下是proc文件的结构体接口描述:

//linux/proc_fs.h//读函数原型typedefint (read_proc_t)(char *page, char **start, off_t off,  int count, int *eof, void *data);//写函数原型typedefint (write_proc_t)(struct file *file, const char __user *buffer,   unsigned long count, void *data);   struct proc_dir_entry {unsigned int low_ino;unsigned short namelen;const char *name;//proc文件的名字mode_t mode;nlink_t nlink;uid_t uid;gid_t gid;unsigned long size;struct inode_operations * proc_iops;struct file_operations * proc_fops;get_info_t *get_info;struct module *owner;struct proc_dir_entry *next, *parent, *subdir;void *data;read_proc_t *read_proc;//读函数(需用户实现)write_proc_t *write_proc;//写函数(需用户实现)atomic_t count;/* use count */int deleted;/* delete flag */};/*创建一个proc结构体@name: proc文件的名字@mode:@parent: proc文件被创建的目录*/struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,struct proc_dir_entry *parent);/*从内核中移除一个proc文件*/void remove_proc_entry(const char *name, struct proc_dir_entry *parent) ;

已经有一些预定义的proc目录可直接作为parent使用:

proc_dir_entry在文件系统中的位置proc_root_fs/procproc_net/proc/netproc_bus/proc/busproc_root_driver/proc/driver
write_proc_t 函数(写函数):用户空间到内核空间

很明显,write_proc_t函数的实现赋予proc 写语义,它的原型如下:

int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);

参数file可忽略,buffer为用户空间需要写入的数据的头指针,count 为用户空间需要写入内核的数据长度,data为proc结构体得私有数据,对应于 struct  proc_dir_entry结构体中的data字段。编码过程中常常需要将用户空间的数据拷贝到内核,因为用户空间的指针不能在内核空间中使用(那不是很多拷贝,效率不是很低..),常见的拷贝函数有以下两枚

/* Copy buffer to user-space from kernel-space */
unsigned long copy_to_user( void __user *to, const void *from,unsigned long n );
/* Copy buffer to kernel-space from user-space */
unsigned long copy_from_user( void *to,const void __user *from, unsigned long n );


__user宏只是方便源代码查找而已,没有实际语义。这两个copy函数可能会造成阻塞,故只能在进程上下文中使用,如果在中断上下文中使用,那恭喜你,你的内核会莫名的挂掉了。

read_proc_t 函数(读函数):内核空间到用户空间
typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);
当某个进程读取我们的proc文件时,内核会分配一个内存页(即PAGE_SIZE大小的内存块),内核模块将数据写入到这张页来返回数据到用户空间。
以上参数中,page参数是为进程分配的内存页,其中count定义了定义了可以写入的最大字符数。在返回多页数据(通常一页是4K),我们需要使用start和off参数。当所有数据全部写入后,就需要设置eof(文件结束参数)。与write类似,data也是私有数据。此处提供的page缓冲区在内核空间。因此不必使用copy_to_user,而可以直接写入。该函数返回写入的字节数。

start参数:start参数用户实现返回大于一页的数据。在read_proc_t方法被调用时,*start的初始值为NULL。如果保留*start为NULL,内核将家丁数据保存在内存页偏移量0的地方,并且忽略off参数。相反,如果我们将*start设为非空值,内核将认为由*start指向的数据是off参数指定的偏移量中的数据。通常返回少量数据的简单read_proc函数可忽略start参数,复杂的read_proc函数将*start设置为页面,并将请求偏移量处得数据放到内存页。
start参数还有另一种用法,将在下文继续讨论。

以下内容实现一个简单的可读写的proc文件例子:  

#include<linux/module.h>#include<linux/kernel.h>#include<linux/proc_fs.h>#include<linux/string.h>#include<linux/vmalloc.h>#include<asm/uaccess.h>MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Fortune Cookie Kernel Module");#define MAX_COOKIE_LENGTH PAGE_SIZE//保存proc结构体指针static struct proc_dir_entry *proc_entry;//保存缓冲区指针static char *cookie_pot;// cookie_index=已经写入的数据数目-1 。static int cookie_index;  //遍历标static int next_fortune;ssize_t fortune_write(struct file *filp,const char __user *buff, unsigned long len, void *data){int space_avaliable=( MAX_COOKIE_LENGTH - cookie_index) +1; //计算剩余空间if(len>space_avaliable){printk(KERN_INFO "fortune:cookie pot is full\n");return -ENOSPC;}if(copy_from_user(&cookie_pot[cookie_index],buff,len  ) )return -EFAULT;cookie_index+=len;cookie_pot[cookie_index-1]=0;return len;}//每读一次返回一个之前写入的字符串。int fortune_read(char *page,char **start,off_t off,int count ,int *eof,void *data){int len;printk(KERN_INFO "fortune_read\n");if(off>0){*eof=1;return 0;}if( next_fortune >= cookie_index ) next_fortune= 0 ;len=sprintf(page,"%s\n", &cookie_pot[ next_fortune ] ) ;next_fortune+=len;return len;}int init_fortune_module(void){int ret=0;//分配一个页大小的数据块,用cookie_pot保存cookie_pot=(char *)vmalloc( MAX_COOKIE_LENGTH );if(!cookie_pot)ret=-ENOMEM;//错误处理。显然else{memset(cookie_pot,0,MAX_COOKIE_LENGTH);//清零proc_entry=create_proc_entry("fortune",0644,NULL);//创建一个新的proc文件,名为fortune.if(proc_entry==NULL){ret=-ENOMEM;vfree(cookie_pot);printk(KERN_INFO "fortune :couldn't create proc entry\n");}else{cookie_index=0;next_fortune=0;proc_entry->read_proc=fortune_read;proc_entry->write_proc=fortune_write;proc_entry->owner=THIS_MODULE;printk(KERN_INFO "fortune: Module load.\n");//一些一本的proc结构体初始化而已。}}return ret;}void cleanup_fortune_module(void){remove_proc_entry("fortune",proc_entry);//移除vfree( cookie_pot );printk(KERN_INFO "fortune:Module unload\n");}module_init(init_fortune_module);module_exit(cleanup_fortune_module);

上文实现了一个可以读写的proc文件。读者可以往/proc/fortune中写入字符串后再读取来查看该模块的行为。







原创粉丝点击