seq_file学习(1)—— single_open

作者

彭东林
pengdonglin137@163.com
 

平台

Linux-4.14.13
Qemu + vexpress
 

概述

从内核中导出信息到用户空间有很多方法,可以自己去实现file_operations的read函数或者mmap函数,但是这种方法不够简单,而且也会有一些限制,比如一次read读取大于1页时,驱动里就不得不去进行复杂的缓冲区管理。为此,就需要学习一下seq_file的用法,为了更简单和方便,内核提供了single_xxx系列接口,它是对seq_file的进一步封装。
 

正文

示例程序
 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/seq_file.h>
 4 #include <linux/debugfs.h>
 5 #include <linux/fs.h>
 6 
 7 static struct dentry *seq_file_demo_dir;
 8 
 9 static int seq_file_demo_show(struct seq_file *seq, void *v)
10 {
11         seq_printf(seq, "Hello World\n");
12         return 0;
13 }
14 
15 static int seq_file_demo_open(struct inode *inode, struct file *file)
16 {
17         return single_open(file, &seq_file_demo_show, NULL);
18 }
19 
20 static const struct file_operations seq_file_demo_fops = {
21         .owner = THIS_MODULE,
22         .open = seq_file_demo_open,
23         .read = seq_read,
24         .llseek = seq_lseek,
25         .release = single_release,
26 };
27 
28 static int __init seq_file_demo_init(void)
29 {
30         seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL,
31                 NULL, &seq_file_demo_fops);
32         return 0;
33 }
34 
35 static void __exit seq_file_demo_exit(void)
36 {
37         if (seq_file_demo_dir)
38                 debugfs_remove(seq_file_demo_dir);
39 }
40 
41 module_init(seq_file_demo_init);
42 module_exit(seq_file_demo_exit);
43 MODULE_LICENSE("GPL");
上面的demo在/sys/kernel/debug/下创建了一个"seq_file_demo",读取这个文件会得到"Hello World"字符串。上面的函数中需要我们实现的只有一个:seq_file_demo_show,直接调用seq_file提供的输出函数即可,不用我们去考虑缓冲区的分配、释放以及越界等问题,可以尽情畅快的输出。
上面的代码里有些标准化的函数,看着有些碍眼,所以在Linux-4.15的include/linux/seq_file.h中提供了下面的宏定义:
 1 #define DEFINE_SHOW_ATTRIBUTE(__name)                    \
 2 static int __name ## _open(struct inode *inode, struct file *file)    \
 3 {                                    \
 4     return single_open(file, __name ## _show, inode->i_private);    \
 5 }                                    \
 6                                     \
 7 static const struct file_operations __name ## _fops = {            \
 8     .owner        = THIS_MODULE,                    \
 9     .open        = __name ## _open,                \
10     .read        = seq_read,                    \
11     .llseek        = seq_lseek,                    \
12     .release    = single_release,                \
13 }

利用上面的宏可以对我们的驱动做进一步简化:

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/seq_file.h>
 4 #include <linux/debugfs.h>
 5 #include <linux/fs.h>
 6 
 7 static struct dentry *seq_file_demo_dir;
 8 
 9 static int seq_file_demo_show(struct seq_file *seq, void *v)
10 {
11         seq_printf(seq, "Hello World\n");
12         return 0;
13 }
14 DEFINE_SHOW_ATTRIBUTE(seq_file_demo);
15 
16 static int __init seq_file_demo_init(void)
17 {
18         seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL,
19                 NULL, &seq_file_demo_fops);
20         return 0;
21 }
22 
23 static void __exit seq_file_demo_exit(void)
24 {
25         if (seq_file_demo_dir)
26                 debugfs_remove(seq_file_demo_dir);
27 }
28 
29 module_init(seq_file_demo_init);
30 module_exit(seq_file_demo_exit);
31 MODULE_LICENSE("GPL");
这样我们只需要专心实现show函数即可。
如果看一下single_open,会发现它是对seq_file的进一步封装:
 1 int single_open(struct file *file, int (*show)(struct seq_file *, void *),
 2         void *data)
 3 {
 4     struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL);
 5     int res = -ENOMEM;
 6 
 7     if (op) {
 8         op->start = single_start;
 9         op->next = single_next;
10         op->stop = single_stop;
11         op->show = show;
12         res = seq_open(file, op);
13         if (!res)
14             ((struct seq_file *)file->private_data)->private = data;
15         else
16             kfree(op);
17     }
18     return res;
19 }

上面设置了single_xx的start、next以及stop回调函数,实现很简单:

 1 static void *single_start(struct seq_file *p, loff_t *pos)
 2 {
 3     return NULL + (*pos == 0);
 4 }
 5 
 6 static void *single_next(struct seq_file *p, void *v, loff_t *pos)
 7 {
 8     ++*pos;
 9     return NULL;
10 }
11 
12 static void single_stop(struct seq_file *p, void *v)
13 {
14 }

其中,*ops表示的是要输出的元素的索引编号,从0开始,依次递增;

single_start的返回值表示要输出的元素的首地址,这个函数的作用是找到索引号为*pos的元素,并返回该元素的首地址,此外也可以做一些加锁的操作
single_next的入参中v表示刚刚show过的元素的首地址,*pos是该元素的索引,这个函数的目的是计算并返回下一个要show的元素的首地址以及索引号
single_stop里可以做一些释放锁的操作
show需要自己实现,向用户show出当前元素的相关信息
 
 
未完待续
posted @ 2018-02-10 21:23  摩斯电码  阅读(10516)  评论(0编辑  收藏  举报