设备驱动程序学习笔记(2)-使用proc文件系统

By:             潘云登

Date:          2009-5-23

Email:         intrepyd@gmail.com

Homepage: http://blog.csdn.net/intrepyd

Copyright: 该文章版权由潘云登所有。可在非商业目的下任意传播和复制。

对于商业目的下对本文的任何行为需经作者同意。


写在前面

1.          本文内容对应《linux设备驱动程序》第四章。

2.          参考文章《在Linux下用户空间与内核空间数据交换的方式》,请google一下。

3.          希望本文对您有所帮助,也欢迎您给我提意见和建议。

4.          本文包含以下内容:

²         认识/proc文件系统

²         创建自己的/proc文件

²         /proc文件

²         /proc文件

²         关于read_proc方法的start参数

²         使用seq_file接口

²         利用printk跟踪seq_file迭代器的调用

 


认识 /proc文件系统

/proc文件系统是一种由软件创建而不占用磁盘空间的文件系统,内核使用它向外界导出信息。/proc下面的每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态地生成文件的内容。

pydeng@pydeng-laptop:~$ file /proc/meminfo

/proc/meminfo: empty

pydeng@pydeng-laptop:~$ cat /proc/meminfo

MemTotal:         493268 kB

MemFree:            6124 kB

Buffers:           15960 kB

Cached:           250204 kB

SwapCached:          888 kB

......

 


创建自己的 /proc文件

可以通过create_proc_entry创建一个/proc入口项。

struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,

struct proc_dir_entry *parent)

参数name给出要建立的proc条目的名称,参数mode给出了建立的该proc条目的访问权限,参数parent指定建立的proc条目所在的目录。如果要在/proc下建立proc条目,parent应当为NULL。否则它应当为proc_mkdir返回的struct proc_dir_entry结构的指针。

struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir_entry *parent)

proc_mkdir用于创建一个proc目录,参数name指定要创建的proc目录的名称,参数parent为该proc目录所在的目录。

         如果/proc子目录本身已经存在,将入口项置于/proc的子目录中有更为简单的办法,即把目录名称作为入口项名称的一部分,如指定name参数为driver/pydproc


proc文件

在创建/proc入口项后,驱动程序必须实现并指定一个回调函数,用于在读取文件时生成数据。

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

 

struct proc_dir_entry *res;

……

res->read_proc=&read_proc;

在读取/proc文件时,内核会分配一个内存页,驱动程序可以将数据通过这个内存页返回到用户空间。参数表中的page指针指向用来写入数据的缓冲区;函数使用start返回实际的数据写到内存的哪个位置;offset指明用户在文件中进行读取操作的位置;count是请求传输的数据长度;eof参数指向一个整型数,当没有数据返回时,驱动程序必须设置这个参数;data参数是提供给驱动程序的专用数据指针,可用于内部记录。read_proc方法必须返回存放到内存页缓冲区的字节数。

         大多数时候,/proc入口项是只读文件,因此,内核提供了一个内联函数将创建入口项和指定回调函数合并到一起。

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);


proc文件

用户可以通过写/proc文件对模块进行配置。写/proc文件同样需要提供一个回调函数,其原型如下:

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

主要作用在于,根据用户的输入内容*buffer,修改驱动程序的专用数据*data。注意,buffer为用户空间的指针,必须使用copy_from_userget_user等函数获取数据。


关于 read_proc方法的start参数

read_proc方法被调用时,*start的初始值为NULL。如果保留*start为空,内核将假定数据保存在内存页偏移量0的地方,即该函数将虚拟文件的整个内容放到了内存页,并同时忽略offset参数。相反,如果将*start设置为非空值,内核将认为由*start指向的数据是offset指定的偏移量处的数据,可直接返回给用户。通常,返回少量数据的简单read_proc方法可忽略start参数,复杂的read_proc方法会将*start设置为页面,并将所请求偏移量处的数据放到内存页中。具体可参考实例代码procfs_exam.c


使用 seq_file接口

如果/proc文件输出内容大于1个内存页,需要多次读,因此处理起来很难。seq_file接口使得内核输出大文件信息更容易。它假定正在创建的虚拟文件要顺序遍历一个项目序列,而这些项目正是要返回给用户空间的。为使用seq_file,必须创建四个迭代器对象,用来表示项目序列中的位置。每前进一步,该对象输出序列中的一个项目。这四个迭代器对象分别为startnextstopshow,保存在一个seq_operations结构中。

struct seq_operations {

        void * (*start) (struct seq_file *sfile, loff_t *pos);

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

        void * (*next) (struct seq_file * sfile, void *v, loff_t *pos);

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

};

start方法始终会首先调用,sfile参数多少时候可以忽略,pos表明读取的位置。对位置的解释并不一定是结果文件的字节位置,通常被解释为指向序列中下一个项目的游标。

next函数将迭代器移动到下一个位置,并在序列中没有项目时返回NULL。参数v是先前对startnext的调用的返回值,可作为私有值只用。

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

在上述调用期间,内核会调用show方法将实际的数据输出到用户空间。它不能使用printk函数,而要使用针对seq_file输出的一组特殊函数。

int seq_printf(struct seq_file *sfile, const char *fmt, ...);

int seq_putc(struct seq_file *sfile, char c);

int seq_puts(struct seq_file *sfile, const char *s);

int seq_escape(struct seq_file *m, const char *s, const char *esc);

int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);

在定义了seq_operations结构之后,需要在打开seq_file文件的open函数中,使用seq_open方法将该结构与对应于seq_file文件的struct file结构关联起来,如:

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

{

    return seq_open(file, &scull_seq_ops);

}

 

static struct file_operations scull_proc_ops = {

    .owner   = THIS_MODULE,

    .open    = scull_proc_open,

    .read    = seq_read,

    .llseek  = seq_lseek,

    .release = seq_release

};

open是唯一一个必须自己实现的文件操作。最后,需要在创建/proc入口项之后,将文件操作赋给它。

entry = create_proc_entry("scullseq", 0, NULL);

if (entry)

    entry->proc_fops = &scull_proc_ops;


利用 printk跟踪seq_file迭代器的调用

为了跟踪迭代器方法的调用,分别在scull设备(scull/main.c)的四个迭代器函数的入口处,以及startnext方法中的if判断后面加入printk打印。在头文件(scull/scull.h)中定义SCULL_DEBUG,打开创建/proc入口项的开关。重新编译并加载模块。在用cat命令查看/proc文件内容后,用dmesg命令查看日志中的printk打印。

root@pydeng-laptop:/home/pydeng/ldd3_examples/scull# make clean > /dev/null

root@pydeng-laptop:/home/pydeng/ldd3_examples/scull# make > /dev/null

root@pydeng-laptop:/home/pydeng/ldd3_examples/scull# ./scull_load

root@pydeng-laptop:/home/pydeng/ldd3_examples/scull# cat /proc/scullseq

 

Device 0: qset 1000, q 4000, sz 0

 

Device 1: qset 1000, q 4000, sz 0

 

Device 2: qset 1000, q 4000, sz 0

 

Device 3: qset 1000, q 4000, sz 0

root@pydeng-laptop:/home/pydeng/ldd3_examples/scull# dmesg

......

[  505.199261] pyd_debug:seq_start().

[  505.199270] pyd_debug:seq_show().

[  505.199275] pyd_debug:seq_next().

[  505.199277] pyd_debug:seq_show().

[  505.199280] pyd_debug:seq_next().

[  505.199282] pyd_debug:seq_show().

[  505.199285] pyd_debug:seq_next().

[  505.199287] pyd_debug:seq_show().

[  505.199290] pyd_debug:seq_next().

[  505.199292] pyd_debug:seq_next():after if, *pos=4.

[  505.199294] pyd_debug:seq_stop().

[  505.199326] pyd_debug:seq_start().

[  505.199329] pyd_debug:seq_start():after if, *pos=4.

[  505.199331] pyd_debug:seq_stop().

跟踪发现,show方法在每次startnext返回非空值后调用。如果startnext返回NULL,就调用stop方法。在最后一个next返回NULL并调用stop方法后,迭代器并未就此停止。这时候,内核将传递next修改后的pos,再次调用start方法。在start返回NULL并再次调用stop之后,迭代器才真正停止工作。因此,stop方法中的清除工作或许还需要对这两种情况有所区分,如防止重复释放内存。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值