linux seq_file 接口
如我们上面提到的,在 /proc 下的大文件的实现有点麻烦. 一直以来,/proc 方法因为 当输出数量变大时的错误实现变得声名狼藉. 作为一种清理 /proc 代码以及使内核开发 者活得轻松些的方法,添加了 seq_file 接口. 这个接口提供了简单的一套函数来实现大 内核虚拟文件. ? set_file 接口假定你在创建一个虚拟文件,它涉及一系列的必须返回给用户空间的项. 为使用 seq_file,你必须创建一个简单的 "iterator" 对象,它能在序列里建立一个位 置,向前进,并且输出序列里的一个项. 它可能听起来复杂,但是,实际上,过程非常简 单. 我们一步步来创建 /proc 文件在 scull 驱动里,来展示它是如何做的. ? 第一步,不可避免地,是包含 <linux/seq_file.h>. 接着你必须创建 4 个 iterator 方 法,称为 start,next,stop,和 show. ? start 方法一直是首先调用. 这个函数的原型是: ? void *start(struct seq_file *sfile,loff_t *pos); ? sfile 参数可以几乎是一直被忽略. pos 是一个整型位置值,指示应当从哪里读. 位置的 解释完全取决于实现; 在结果文件里不需要是一个字节位置. 因为 seq_file 实现典型地 步进一系列感兴趣的项,position 常常被解释为指向序列中下一个项的指针. scull 驱 动解释每个设备作为系列中的一项,因此进入的 pos 简单地是一个 scull_device 数组 的索引. 因此,scull 使用的 start 方法是: ? static void *scull_seq_start(struct seq_file *s,loff_t *pos) { if (*pos >= scull_nr_devs) return NULL;? /* No more to read */ return scull_devices + *pos; } ? 返回值,如果非 NULL,是一个可以被 iterator 实现使用的私有值. ? next 函数应当移动 iterator 到下一个位置,如果序列里什么都没有剩下就返回 NULL. 这个方法的原型是: ? void *next(struct seq_file *sfile,void *v,loff_t *pos); ? 这里,v 是从前一个对 start 或者 next 的调用返回的 iterator,pos 是文件的当前位 置. next 应当递增有 pos 指向的值; 根据你的 iterator 是如何工作的,你可能(尽管 可能不会)需要递增 pos 不止是 1. 这是 scull 所做的: ? static void *scull_seq_next(struct seq_file *s,loff_t *pos) { (*pos)++; if (*pos >= scull_nr_devs) return NULL; return scull_devices + *pos; } ? 当内核处理完 iterator,它调用 stop 来清理: void stop(struct seq_file *sfile,void *v); scull 实现没有清理工作要做,所以它的 stop 方法是空的. ? 设计上,值得注意 seq_file 代码在调用 start 和 stop 之间不睡眠或者进行其他非原 子性任务. 你也肯定会看到在调用 start 后马上有一个 stop 调用. 因此,对你的 start 方法来说请求信号量或自旋锁是安全的. 只要你的其他 seq_file 方法是原子的,调用的整个序列是原子的. (如果这一段对你没有意义,在你读了下一章后再回到这.) ? 在这些调用中,内核调用 show 方法来真正输出有用的东西给用户空间. 这个方法的原型 是: ? int show(struct seq_file *sfile,void *v); ? 这个方法应当创建序列中由 iterator v 指示的项的输出. 不应当使用 printk,但是; 有一套特殊的用作 seq_file 输出的函数: ? int seq_printf(struct seq_file *sfile,const char *fmt,...); ? 这是给 seq_file 实现的 printf 对等体; 它采用常用的格式串和附加值参数. 你 必须也将给 show 函数的 set_file 结构传递给它,然而. 如果 seq_printf 返回 非零值,意思是缓存区已填充,输出被丢弃. 大部分实现忽略了返回值,但是. ? int seq_putc(struct seq_file *sfile,char c); int seq_puts(struct seq_file *sfile,const char *s); 它们是用户空间 putc 和 puts 函数的对等体. int seq_escape(struct seq_file *m,const char *s,const char *esc); ? 这个函数是 seq_puts 的对等体,除了 s 中的任何也在 esc 中出现的字符以八进 制格式打印. esc 的一个通用值是"tn",它使内嵌的空格不会搞乱输出和可能 搞乱 shell 脚本. ? int seq_path(struct seq_file *sfile,struct vfsmount *m,struct dentry *dentry,char *esc); ? 这个函数能够用来输出和给定命令项关联的文件名子. 它在设备驱动中不可能有用; 我们是为了完整在此包含它. ? 回到我们的例子; 在 scull 使用的 show 方法是: ? static int scull_seq_show(struct seq_file *s,void *v) { struct scull_dev *dev = (struct scull_dev *) v; struct scull_qset *d; int i; ? if (down_interruptible (&dev->sem)) return -ERESTARTSYS; ? seq_printf(s,"nDevice %i: qset %i,q %i,sz %lin",(int) (dev - scull_devices),dev->qset,dev->quantum,dev->size); ? for (d = dev->data; d; d = d->next) { /* scan the list */ seq_printf(s," item at %p,qset at %pn",d,d->data); ? if (d->data && !d->next) /* dump only the last item */ ? for (i = 0; i < dev->qset; i++) { if (d->data[i]) seq_printf(s," % 4i: %8pn", i,d->data[i]); } } up(&dev->sem); return 0; } ? 这里,我们最终解释我们的" iterator" 值,简单地是一个 scull_dev 结构指针. ? 现在已有了一个完整的 iterator 操作的集合,scull 必须包装起它们,并且连接它们到 /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 }; ? 有那个结构在,我们必须创建一个内核理解的文件实现. 我们不使用前面描述过的 read_proc 方法; 在使用 seq_file 时,最好在一个稍低的级别上连接到 /proc. 那意味 着创建一个 file_operations 结构(是的,和字符驱动使用的同样结构) 来实现所有内核 需要的操作,来处理文件上的读和移动. 幸运的是,这个任务是简单的. 第一步是创建一 个 open 方法连接文件到 seq_file 操作: ? static int scull_proc_open(struct inode *inode,struct file *file) { return seq_open(file,&scull_seq_ops); } ? 调用 seq_open 连接文件结构和我们上面定义的序列操作. 事实证明,open 是我们必须 自己实现的唯一文件操作,因此我们现在可以建立我们的 file_operations 结构: ? static struct file_operations scull_proc_ops = { .owner = THIS_MODULE, .open = scull_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; ? 这里我们指定我们自己的 open 方法,但是使用预装好的方法 seq_read,seq_lseek,和 seq_release 给其他. ? 最后的步骤是创建 /proc 中的实际文件: ? entry = create_proc_entry("scullseq",NULL); if (entry) entry->proc_fops = &scull_proc_ops; ? 不是使用 create_proc_read_entry,我们调用低层的 create_proc_entry,我们有这个 原型: ? struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent); ? 参数和它们的在 create_proc_read_entry 中的对等体相同: 文件名子,它的位置,以及 父目录. ? 有了上面代码,scull 有一个新的 /proc 入口,看来很象前面的一个. 但是,它是高级 的,因为它不管它的输出有多么大,它正确处理移动,并且通常它是易读和易维护的. 我 们建议使用 seq_file,来实现包含多个非常小数目的输出行数的文件. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |