作者
彭东林
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出当前元素的相关信息
未完待续