proc文件的实现也需要编写出一个内核模块并插入内核。模块的编写过程我们已经Hello,Module过了,而proc文件的实现稍微复杂一些。
在linux内核模块编写中,常常会有一些结构体,结构体中定义了很多函数指针,内核模块的编写常常是围绕着实现这些函数指针而进行的。实现了这些接口便可以调用相应的函数把相应的功能模块插入内核(拗口),以下是proc文件的结构体接口描述:
//linux/proc_fs.h
//读函数原型
typedef int (read_proc_t)(char *page, char **start, off_t off,
int count, int *eof, void *data);
//写函数原型
typedef int (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 | /proc |
proc_net | /proc/net |
proc_bus | /proc/bus |
proc_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中写入字符串后再读取来查看该模块的行为。