内核proc文件系统 --和--seq接口

内核proc文件系统

原文地址:http://blog.chinaunix.net/uid-20543672-id-3221530.html


  所有要使用 proc的内核模块都应当包含 <linux/proc_fs.h> 头文件。首先要了解以下proc编程中最重要的数据结构:

  1. struct proc_dir_entry {
  2. unsigned int low_ino;
  3. unsigned int namelen;
  4. const char *name; // 入口文件名
  5. mode_t mode; // 文件访问权限模式
  6. nlink_t nlink;
  7. uid_t uid; // 文件的用户ID
  8. gid_t gid; // 文件的组ID
  9. loff_t size;
  10. const struct inode_operations *proc_iops; // 文件Inode操作函数
  11. /*
  12. * NULL ->proc_fops means "PDE is going away RSN" or
  13. * "PDE is just created". In either case, e.g. ->read_proc won't be
  14. * called because it's too late or too early, respectively.
  15. *
  16. * If you're allocating ->proc_fops dynamically, save a pointer
  17. * somewhere.
  18. */
  19. const struct file_operations *proc_fops; // 文件操作函数
  20. struct proc_dir_entry *next, *parent, *subdir; // 此入口的兄弟、父目录、和下级子入口指针
  21. void *data;// 文件私有数据指针
  22. read_proc_t *read_proc; // 文件读取操作函数指针
  23. write_proc_t *write_proc; // 文件写操作函数指针
  24. atomic_t count; /* 引用计数 */
  25. int pde_users; /* 进程调用模块的计数 */
  26. spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
  27. struct completion *pde_unload_completion;
  28. struct list_head pde_openers;/* 调用 ->open, 但是未调用 ->release的进程指针 */
  29. };
这个数据结构在内核中代表了一个proc入口,在procfs中表现为一个文件。你可以在这个结构体中看到一些文件特有的属性成员,如uid、gid、mode、name等。但是在利用默认的proc 的API编程中,我们需要关注的是这个入口的读写函数成员:

  1. read_proc_t *read_proc;
    write_proc_t *write_proc;
在创建好proc入口之后,如果我们使用内核的默认操作函数集proc_file_operations,就需要对上面的两个成员进行初始化,将他们映射到我们事先定义好的读写函数上即可。但是如果我们自己实现了 const  struct file_operations  * proc_fops,那就不用在意这两个函数。这个结构体中的其他成员基本无须我们单独来初始化,入口创建函数会帮我们处理。

procfs提供了一些接口函数用于在 /proc 文件系统中创建和删除一个入口文件:


  1. struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent);

name:文件名

mode:文件权限

parent:文件在 /proc 文件系统中父目录指针。

返回值是创建完成的 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。

然后通过这个返回的指针来初始化这个文件入口的其他参数,如在对该文件执行读写操作时应该调用的函数。


 如果这个/proc入口带有私有数据,以及这个数据所需要的操作函数,可以使用以下函数:

  1. struct proc_dir_entry *proc_create_data(const char *name, mode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data);

name:文件名

mode:文件权限

parent:文件在 /proc 文件系统中父目录指针。

proc_fops:文件操作函数(若不使用内核的默认操作函数集proc_file_operations时,可以通过对其的初始化使用自己定义的file_operations,/proc/config.gz的实现就是一个很好的例子)

data:私有数据指针,会被赋值给所创建的proc_dir_entry的data成员。

返回值是创建完成的 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。


我们还可以使用 proc_mkdir、proc_mkdir_mode 以及 proc_symlink 在 /proc 文件系统中创建目录和软链接。

  1. struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent,const char *dest);

name:软链接文件名

parent:文件在 /proc 文件系统中父目录指针。

dest:软链接目标文件名


  1. struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);

name:目录名

parent:目录在 /proc 文件系统中父目录指针。

返回值是创建完成的 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。

创建的目录初始化权限是有只读和可执行权限。


  1. struct proc_dir_entry *proc_mkdir_mode(const char *name, mode_t mode, struct proc_dir_entry *parent);

name:目录名

mode:目录权限

parent:目录在 /proc 文件系统中父目录指针。

返回值是创建完成的 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。

 

对于以上所有的parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。下面列出了可以使用的其他一些父 proc_dir_entry,以及它们在这个文件系统中的位置。

proc_dir_entry 在文件系统中的位置
proc_root_fs /proc

proc_net /proc/net

proc_bus /proc/bus

proc_root_driver /proc/driver


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

注意:创建 /proc 入口,有两种选择:

(1)使用proc的默认操作函数集proc_file_operations,可以使用上面的函数:

create_proc_entry

proc_create_data(其中的proc_fops=NULL

(2)使用自己实现的file_operations,请使用:

proc_create_data(其中的proc_fops=你实现的操作函数

对于(2),/proc/config.gz的实现就是一个很好的例子(/kernel/configs.c),他的读取函数是自己实现的(但内部使用了内核的libfs接口)。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


2、映射回调函数使用proc的默认操作函数集proc_file_operations时需要,但是这个默认接口越来越不受待见,这样的代码会慢慢的被清除出内核主线,我们应该使用后面介绍的seq_file接口

在完成了/proc入口创建的之后,还有一件很重要的事情就是向已经创建的proc_dir_entry中的 write_proc 和read_proc函数指针赋值,指向实现对/proc文件节点的读写操作的函数。当某个进程访问文件时(使用 read /write系统调用), 这个请求最终会调用这些函数。所以在映射这些回调之前,我们必须已经定义好了这两个函数。

首先我们来看看read_proc的原型

  1. typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);

当一个进程读取 /proc 文件时, 请求通过VFS传递到procfs( __proc_file_read),procfs( __proc_file_read)会使用__get_free_page为其分配一页内存(PAGE_SIZE 字节)作为临时缓冲,而read_proc通过向这个内存页写入数据来让procfs通过VFS返回给用户空间。

page是procfs( __proc_file_read)提供的数据缓存页指针,缓冲区在内核空间中,read_proc函数中可直接写入,而无须调用 copy_to_user。 

start 、off :含义有些复杂,由于 /proc 文件可能超过缓存大小(一页),procfs可能需要多次调用该函数来获取整个文件的数据。该函数返回时procfs( __proc_file_read)根据start和off来判断如何将其返回给用户空间,

(1)*start = NULL

用于proc文件小于一页的情况:

off:传入参数,相对整个proc文件的偏移量,procfs( __proc_file_read)会从page+off开始返回给用户空间

(2)NULL < *start < page

用于proc文件大于一页的情况:

off:传入参数,相对整个proc文件的偏移量,read_proc应该将文件内容偏移off开始的数据放入page起始的页中,返回后procfs( __proc_file_read)会从page开始返回给用户空间

(3)*start > page

用于proc文件大于一页的情况:

off:传入参数,相对这个文件的偏移量,read_proc应该文件内容偏移off开始的数据放入缓存中*start指向的位置,返回后procfs( __proc_file_read)会从*start开始返回给用户空间

 

count:传入参数,期望read_proc返回的字节数。其实在传入前procfs已经处理过了,使其不会大于缓存大小(一页)。

eof :简单的标志,指向一个整数, 当所有数据全部写入之后,就需要设置 eof为非零,以表示文件读取完成。

data :表示私有数据指针, 可以用做内部用途。 

具体的使用方法在后面的实验中来感悟.

 

再来看看write_proc的原型

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

 filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)

buff 参数是传递给进来的字符串数据缓存。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它,而应该使用copy_from_user。

len 参数定义了在 buff 中有多少数据要被写入。

data 参数是一个指向私有数据的指针。

 

3、删除 /proc 入口

如果要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数:


  1. void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

name:需要删除的文件名字符串

parent:这个文件在 /proc 文件系统中的位置,即父目录指针。 

 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

以上的函数是创建proc文件入口的基本流程。

对于创建一个只读的的简单 /proc 入口项来说,可以使用 create_proc_read_entry,它使用create_proc_entry创建一个 /proc 项,并用其中的read_proc参数对proc_dir_entry中的read_proc 函数指针进行初始化。

对于这样方便使用的函数,include/linux/proc_fs.h中还有许多,比如你如果要创建一个/proc/net目录下的入口文件,你可以调用:
  1. struct proc_dir_entry *proc_net_fops_create(struct net *net, const char *name, mode_t mode, const struct file_operations *fops);

如果你要删除一个/proc/net目录下的入口文件,你可以调用:

  1. void proc_net_remove(struct net *net, const char *name);

要创建一个/proc/net目录下的子目录,你可以调用:

  1. struct proc_dir_entry *proc_net_mkdir(struct net *net, const char *name, struct proc_dir_entry *parent);

有兴趣的朋友可以自己去include/linux/proc_fs.h中发掘更多这样的函数。

如果你想要更细致的了解proc文件系统的原理,建议你还是去阅读fs/proc/generic.c,其中包含了以上介绍的API的源码,并且还有procfs对于文件读写的函数实现。看了这些,你就不会对如何实现 read_proc和write_proc的回调感到迷惑了。


seq接口

 转自:用户空间与内核空间数据交换的方式(3)------seq_file

可以参考文档:Documentation\filesystems\seq_file.txt

是在2.6内核中,已经大量地使用了该功能。
要想使用seq_file功能,开发者需要包含头文件linux/seq_file.h,并定义与设置一个seq_operations结构(类似于file_operations结构): 

  
  
struct seq_operations { void * ( * start) ( struct seq_file * m, loff_t * pos); void ( * stop) ( struct seq_file * m, void * v); void * ( * next) ( struct seq_file * m, void * v, loff_t * pos); int ( * show) ( struct seq_file * m, void * v); };

start函数用于指定seq_file文件的读开始位置,返回实际读开始位置,如果指定的位置超过文件末尾,应当返回NULL,start函数可以有一个特殊的返回SEQ_START_TOKEN,它用于让show函数输出文件头,但这只能在pos为0时使用,next函数用于把seq_file文件的当前读位置移动到下一个读位置,返回实际的下一个读位置,如果已经到达文件末尾,返回NULL,stop函数用于在读完seq_file文件后调用,它类似于文件操作close,用于做一些必要的清理,如释放内存等,show函数用于格式化输出,如果成功返回0,否则返回出错码。
Seq_file也定义了一些辅助函数用于格式化输出:

  
  
/* 函数seq_putc用于把一个字符输出到seq_file文件 */ int seq_putc( struct seq_file * m, char c); /* 函数seq_puts则用于把一个字符串输出到seq_file文件 */ int seq_puts( struct seq_file * m, const char * s); /* 函数seq_escape类似于seq_puts,只是,它将把第一个字符串参数中出现的包含在第二个字符串参数
中的字符按照八进制形式输出,也即对这些字符进行转义处理 */ int seq_escape( struct seq_file * , const char * , const char * ); /* 函数seq_printf是最常用的输出函数,它用于把给定参数按照给定的格式输出到seq_file文件 */ int seq_printf( struct seq_file * , const char * , ...)__attribute__ ((format(printf, 2 , 3 ))); /* 函数seq_path则用于输出文件名,字符串参数提供需要转义的文件名字符,它主要供文件系统使用 */ int seq_path( struct seq_file * , struct vfsmount * , struct dentry * , char * );

在定义了结构struct seq_operations之后,用户还需要把打开seq_file文件的open函数,以便该结构与对应于seq_file文件的struct file结构关联起来,例如,struct seq_operations定义为:

  
  
struct seq_operations exam_seq_ops = { .start = exam_seq_start, .stop = exam_seq_stop, .next = exam_seq_next, .show = exam_seq_show };

那么,open函数应该如下定义:
 

  
  
static int exam_seq_open( struct inode * inode, struct file * file) { return seq_open(file, & exam_seq_ops); };

注意,函数seq_open是seq_file提供的函数,它用于把struct seq_operations结构与seq_file文件关联起来。
最后,用户需要如下设置struct file_operations结构: 

  
  
struct file_operations exam_seq_file_ops = { .owner = THIS_MODULE, .open = exm_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release };

 
注意,用户仅需要设置open函数,其它的都是seq_file提供的函数。
然后,用户创建一个/proc文件并把它的文件操作设置为exam_seq_file_ops即可:

  
  
struct proc_dir_entry * entry; entry = create_proc_entry( " exam_seq_file " , 0 , NULL); if (entry) entry -> proc_fops = & exam_seq_file_ops;

  
对于简单的输出,seq_file用户并不需要定义和设置这么多函数与结构,它仅需定义一个show函数,然后使用single_open来定义open函数就可以,以下是使用这种简单形式的一般步骤:
1.定义一个show函数

  
  
int exam_show( struct seq_file * p, void * v) { … }

  
2. 定义open函数

  
  
int exam_single_open( struct inode * inode, struct file * file) { return (single_open(file, exam_show, NULL)); }

注意要使用single_open而不是seq_open。
3. 定义struct file_operations结构  

  
  
struct file_operations exam_single_seq_file_operations = { .open = exam_single_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, };

  
注意,如果open函数使用了single_open,release函数必须为single_release,而不是seq_release。 下面给出了一个使用seq_file的具体例子seqfile_exam.c,它使用seq_file提供了一个查看当前系统运行的所有进程的/proc接口,在编译并插入该模块后,用户通过命令"cat /proc/exam_esq_file"可以查看系统的所有进程。

  
  
// kernel module: seqfile_exam.c #include < linux / config.h > #include < linux / module.h > #include < linux / proc_fs.h > #include < linux / seq_file.h > #include < linux / percpu.h > #include < linux / sched.h > static struct proc_dir_entry * entry; static void * l_start( struct seq_file * m, loff_t * pos) { loff_t index = * pos; if (index == 0 ) { seq_printf(m, " Current all the processes in system:\n " " %-24s%-5s\n " , " name " , " pid " ); return & init_task; } else { return NULL; } } static void * l_next( struct seq_file * m, void * p, loff_t * pos) { task_t * task = (task_t * )p; task = next_task(task); if (( * pos != 0 ) && (task == & init_task)) { return NULL; } ++* pos; return task; } static void l_stop( struct seq_file * m, void * p) { } static int l_show( struct seq_file * m, void * p) { task_t * task = (task_t * )p; seq_printf(m, " %-24s%-5d\n " , task -> comm, task -> pid); return 0 ; } static struct seq_operations exam_seq_op = { .start = l_start, .next = l_next, .stop = l_stop, .show = l_show }; static int exam_seq_open( struct inode * inode, struct file * file) { return seq_open(file, & exam_seq_op); } static struct file_operations exam_seq_fops = { .open = exam_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int __init exam_seq_init( void ) { entry = create_proc_entry( " exam_esq_file " , 0 , NULL); if (entry) entry -> proc_fops = & exam_seq_fops; return 0 ; } static void __exit exam_seq_exit( void ) { remove_proc_entry( " exam_esq_file " , NULL); } module_init(exam_seq_init); module_exit(exam_seq_exit); MODULE_LICENSE( " GPL " );

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值