Chinaunix首页 | 论坛 | 博客
  • 博客访问: 974200
  • 博文数量: 109
  • 博客积分: 1751
  • 博客等级: 上尉
  • 技术积分: 1817
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-31 22:37
文章分类

全部博文(109)

文章存档

2014年(9)

2013年(21)

2012年(48)

2011年(31)

分类: LINUX

2011-07-29 16:07:21

 /proc 文件系统是一种特殊的、由软件创建的文件系统,内核使用它向外界导出信息。/proc 下面的每个文件都绑定与一个内核函数,用户读取其中的文件时,该函数动态地声称文件的内容。我已经见过这类文件的一些输出情况,例如:/proc/modules列出的是当前载入模块的列表。


Linux系统对/proc的使用很频繁。现代Linux发行版中的很多工具都是通过/proc来获取它们所需要的信息,例如:pstopuptime等。有些设备驱动程序也通过/proc导出信息。我们自己的驱动程序当然也可以这么做。因为/proc文件系统是动态的,所以驱动程序模块可以在任何时候添加或删除其中的入口项。


具有完整特征的/proc入口项可以相当复杂;在所有的这些特征当中,有一点要指出的是,这些/proc文件不仅可以用于读出数据,也可以用于写入数据。不过,大多数时候,/proc入口项是只读文件。本节将只涉及简单的只读情形。

/proc中实现文件

所有使用/proc的模块必须包含,并通过这个头文件来定义正确的函数。

为创建一个只读的/proc文件,驱动程序必须实现一个函数,用于在读取文件时生成数据。当某个进程读取这个文件时,读取请求会通过这个函数发送到驱动程序模块。我们把注册接口放在本节后面,先直接讲这个函数。


在某个进程读取我们的/proc文件时,内核会分配一个内存页(即PAGE_SIZE字节的内存块),驱动程序可以将数据通过这个内存页返回到用户空间。该缓冲区会传入我们定义的函数,而这个函数称为read_proc方法:

Int* read_proc(char *page , char ** start , off_t offset , int count , int *eof , void *data)


参数表中的page指针指向用来写入数据的缓冲区;函数应使用start返回实际的数据写到内存页的哪个位置;offset count这两个参数与read方法相同; eof 参数指向一个整数,当没有数据可返回时,驱动程序必须设置这个参数;data 参数是提供给驱动程序的专用数据指针,可用于内部记录。


该函数必须返回存放到内存页缓冲区的字节数,这一点与read函数对其他类型文件的处理相同。另外还有*eof*start两个输出值。Eof只是一个简单的标志,而start的用法就有点复杂了,它可以帮助实现大(大于一个内存页)的/proc文件。


Start参数的用法看起来有些特别,它用来指示要返回给用户的数据保存在内存页的什么位置。在我们的read_proc方法被调用时,*start的初始值为NULL。如果保留*start为空,内核将假定数据保存在内存页偏移量0的地方;也就是说,内核将对read_proc作如下简单假定:该函书将虚拟文件的整个内容放到了内存页,并同时忽略offset参数。相反,如果我们将*start设置为非空值,内核将认为由*start指向的数据是offset指定的偏移量处的数据,可直接返回给用户。通常,返回少量数据的简单read_proc 方法可忽略start参数,复杂的read_proc 方法会将“*start”设置为页面,并将所请求偏移处的数据放到内存页中。

长久以来,关于/proc文件还有另一个主要问题,这也是start意图解决的一个问题。有时,在连续的read调用之间,内核数据结构的ASCII表述会发生变化,以至于读取进程发现前后两次调用所获得的数据不一致。如果把*start设为一个小的整数值,那么调用程序可以利用它来增加filp->f_pos的值,而不依赖于返回的数据量,因此也就使得f_pos成为read_proc过程的一个内部记录值。例如,如果read_proc函数从一个大的结构数组返回数据,并且这些结构的前5个已经在第一次调用中返回,那么可将*start设置为5。下次调用中这个值将被作为偏移量;驱动程序也就知道应该从数组的第6个结构开始返回数据。


注意,还有一种更好的方法来实现/proc文件,该方法称为seq_file,稍后详细描述。

创建自己的/proc文件

一旦定义好了一个read_proc函数,就需要把它与一个/proc入口项连接起来。这是通过create_proc_read_entry实现的。


Struct proc_dir_entry *create_proc_read_entry( const char *name , mode_t mode , struct proc_dir_entry *base , read_proc_t *read_proc ,void *data);


其中,name 是要创建的文件名称;mode是该文件的保护掩码(可传入0表示系统默认值);base 指定该文件所在目录(如果base=NULL,则该文件江创建在/proc的根目录);read_proc是实现该文件的read_proc函数。内核会忽略data参数,但是会将该参数传递给read_proc

举例如下:

create_proc_read_entry( ”scullmem”, 0 , NULL , scull_read_procmem ,NULL);

上述代码在/proc目录下创建了一个称为scullmem的文件。


当然,在卸载模块时,/proc中的入口项也应被删除。 Remove_proc_entry(“scullmem” , NULL)

Seq_file 接口

针对/proc下大文件处理的不足而诞生了seq_file接口。

Seq_file接口假定我们正在创建的虚拟文件要顺序遍历一个项目序列,而这些项目正式必须要返回给用户空间的。为使用seq_file,我们必须创建一个简单的“迭代器(iterator)”对象,该对象用来表示项目序列中的位置,每前进一步,该对象输出序列中的一个项目。这听起来有些复杂,但实际上整个过程相当简单。


第一步:包含头文件,然后建立四个函数,分别为startnextstopshow

Start始终会首先调用,该函数的原型如下:

Void * startstruct seq_file *sfile,loff_t *pos);

这里的sfile 参数在大多数情况下可被忽略。Pos是一个整数的位置值,表明读取的位置。对位置的解释完全取决于迭代器的实现本身,并不一定非得是结果文件的字节位置。因为seq_file的实现通常要遍历一个项目序列,因此该位置通常被解释成为指向序列中下一个项目的游标。


Next 函数将迭代器移动到下一个位置,并在序列中没有其他项目时返回NULL。该方法的原型是:

Void *nextstruct seq_file , void *v ,loff_t *pos

其中,v是先前对start或者next的调用所返回的迭代器,pos是文件的当前位置。Next方法应增加pos所指向的值。


当内核使用迭代器之后,会调用stop方法通知我们进行清除工作。

Void stop(struct seq_file *sfile , void *v)


在上述调用之间,内核会调用show方法来讲实际的数据输出到用户空间,该方法的原型如下:

Int show( struct seq_file *sfile , void *v );

该方法应该为迭代器v所指向的项目建立输出。但是,它不能使用printk等函数,而要使用针对seq_file输出的一组特殊的函数:

  • Int seq_printf

  • Int seq_putc

  • Int seq_puts

  • Int seq_escape

  • Int seq_path


这里,我们定义了完整的迭代器操作函数,必须将这些函数打包并和/proc中的某个文件连接起来。首先要填充一个seq_operations 结构。

Static struct seq_operations scull_seq_ops = {

.start = scull_seq_start;

.next = scull_seq_next;

.stop = scull_seq_stop;

.show=scull_seq_show;

}


有了这个结构,我们必须创建一个内核能够理解的文件实现。在使用seq_file时,我们不使用先前描述的read_proc方法,而最好在略低的层次上连接/proc。也就是说,我们将创建一个file_operation结构,这个结构将实现内核在该/proc文件上进行读取和定位时所需要的操作。幸运的是,这个过程非常直接。首先创建一个open方法,该方法将文件连接到seq_file操作:

Static int scull_proc_open( struct inode * inode ,struct file *file)

{

Seq_open( file , &scull_seq_ops);

}


seq_open的调用将file 结构和我们上面定义的顺序操作连接在一起。open是唯一由我们自己实现的文件操作,因此,我们的file_operations结构可如下定义:

Static struct file_operations scull_proc_ops = {

.owner = THIS_MODULE;

.open = scull_proc_open;

.read = seq_read,

.llseek = seq_lseek,

.releae = seq_release

}


这里,我们指定了我们自己的open 方法,但对其他的file_operations 成员,我们是用来已经定义好的seq_readseq_lseekseq_release方法。


最后,我们建立实际的/proc文件:

Entry = create_proc_entry( “scullseq ”,0,NULL)

If( entry )

Entry->proc_fops = &scull_proc_ops

阅读(1177) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~