linux内核seq_file接口
一些关于vfs的前置知识,推荐http://www.ibm.com/developerworks/cn/linux/l-vfs/
seq_file 接口作用
从内核中导出信息到用户空间有很多方法,可以自己去实现file_operations的read函数或者mmap函数,但是这种方法不够简单,而且也会有一些限制,比如一次read读取大于1页时,驱动里就不得不去进行复杂的缓冲区管理。为此,就需要学习一下seq_file的用法,为了更简单和方便,内核提供了single_xxx系列接口,它是对seq_file的进一步封装。
实例程序一
#include <linux/init.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
static struct dentry *seq_file_demo_dir;
static int seq_file_demo_show(struct seq_file *seq, void *v)
{
seq_printf(seq, "Hello World\n");
return 0;
}
static int seq_file_demo_open(struct inode *inode, struct file *file)
{
return single_open(file, &seq_file_demo_show, NULL);
}
static const struct file_operations seq_file_demo_fops = {
.owner = THIS_MODULE,
.open = seq_file_demo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init seq_file_demo_init(void)
{
seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL,
NULL, &seq_file_demo_fops);
return 0;
}
static void __exit seq_file_demo_exit(void)
{
if (seq_file_demo_dir)
debugfs_remove(seq_file_demo_dir);
}
module_init(seq_file_demo_init);
module_exit(seq_file_demo_exit);
MODULE_LICENSE("GPL");
上面的demo在/sys/kernel/debug/下创建了一个"seq_file_demo",读取这个文件会得到"Hello World"字符串。上面的函数中需要我们实现的只有一个:seq_file_demo_show,直接调用seq_file提供的输出函数即可,不用我们去考虑缓冲区的分配、释放以及越界等问题。
如果是版本较高的kernel, 如Linux-4.15的include/linux/seq_file.h中提供了下面的宏定义:
define DEFINE_SHOW_ATTRIBUTE(__name)
所以代码可以简化为如下
#include <linux/init.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
static struct dentry *seq_file_demo_dir;
static int seq_file_demo_show(struct seq_file *seq, void *v)
{
seq_printf(seq, "Hello World\n");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(seq_file_demo);
static int __init seq_file_demo_init(void)
{
seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL,
NULL, &seq_file_demo_fops);
return 0;
}
static void __exit seq_file_demo_exit(void)
{
if (seq_file_demo_dir)
debugfs_remove(seq_file_demo_dir);
}
module_init(seq_file_demo_init);
module_exit(seq_file_demo_exit);
MODULE_LICENSE("GPL");
接下来看看Demo中show 函数用使用的singal_open 函数, 是对seq_file的进一步封装
static int seq_file_demo_open(struct inode *inode, struct file *file)
{
return single_open(file, &seq_file_demo_show, NULL);
}
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
void *data)
{
struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL);
int res = -ENOMEM;
if (op) {
op->start = single_start;
op->next = single_next;
op->stop = single_stop;
op->show = show;
res = seq_open(file, op);
if (!res)
((struct seq_file *)file->private_data)->private = data;
else
kfree(op);
}
return res;
}
static void *single_start(struct seq_file *p, loff_t *pos)
{
return NULL + (*pos == 0);
}
static void *single_next(struct seq_file *p, void *v, loff_t *pos)
{
++*pos;
return NULL;
}
static void single_stop(struct seq_file *p, void *v)
{
}
ops表示的是要输出的元素的索引编号,从0开始,依次递增;
single_start的返回值表示要输出的元素的首地址,这个函数的作用是找到索引号为pos的元素,并返回该元素的首地址,此外也可以做一些加锁的操作
single_next的入参中v表示刚刚show过的元素的首地址,pos是该元素的索引,这个函数的目的是计算并返回下一个要show的元素的首地址以及索引号
single_stop里可以做一些释放锁的操作
show需要自己实现,向用户show出当前元素的相关信息,需要传入自己定义函数指针
总结下步骤为
1,首先要调用创建proc文件或者debugfs的函数,需要绑定flie_operations
2,填充函数中调用的flie_operations结构体
seq和single开头为内核实现好的函数,直接填充上就行
open为必须填充函数
static const struct file_operations seq_file_demo_fops = {
.owner = THIS_MODULE,
.open = seq_file_demo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
3,实现open和write函数
4,在open函数中调用single_open绑定seq_show函数指针
关于第三个参数,其类型应为viod*,内核中有些地方传入的NULL,有些地方传入的inode->i_private,也有传入其他值的
data在single_open函数中是这么赋值的:
if (!res)
((struct seq_file *)file->private_data)->private = data;
data是seq_file结构体的private成员。
那么data如何真正被使用的呢?
show函数的第一个参数为seq_file类型,在show函数中,可以将seq_file的private成员转换成对应的类型进行使用。
也就是说,可以通过seq_file的private成员将data参数传递到show函数中
举个应用场景例子:
static int seq_file_demo_show(struct seq_file *seq, void *v)
{
// 3, 这里就可以使用到对应的指针了
void *demo_ptr = seq->private;
seq_printf(seq, "Hello World\n");
return 0;
}
static int seq_file_demo_open(struct inode *inode, struct file *file)
{
// 2, 调用 single_open 将 inode->i_private 赋值给seq private
return single_open(file, &seq_file_demo_show, inode->i_private);
}
static int __init seq_file_demo_init(void)
{
//0, 定义一个demo 指针来初始化的时候传递个 seq private data
void *demo_ptr;
// 1, 初始化的是将demo_ptr 传递
seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL,
demo_ptr, &seq_file_demo_fops);
return 0;
}
实例程序二
前面的例子是使用single_open 来封装了 seq_ops 结构体,我们也可以自定义seq_operations
seq_file结构体定义于linux/seq_file.h
struct seq_file {
char *buf; //序列文件对应的数据缓冲区,要导出的数据是首先打印到这个缓冲区,然后才被拷贝到指定的用户缓冲区。
size_t size; //缓冲区大小,默认为1个页面大小,随着需求会动态以2的级数倍扩张,4k,8k,16k...
size_t from; //没有拷贝到用户空间的数据在buf中的起始偏移量
size_t count; //buf中没有拷贝到用户空间的数据的字节数,调用seq_printf()等函数向buf写数据的同时相应增加m->count
size_t pad_until;
loff_t index; //正在或即将读取的数据项索引,和seq_operations中的start、next操作中的pos项一致,一条记录为一个索引
loff_t read_pos; //当前读取数据(file)的偏移量,字节为单位
u64 version; //文件的版本
struct mutex lock; //序列化对这个文件的并行操作
const struct seq_operations *op; //指向seq_operations
int poll_event;
const struct file *file; // seq_file相关的proc或其他文件
void *private; //指向文件的私有数据
};
seq操作函数:
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos); //开始读数据项,通常需要在这个函数中加锁,以防止并行访问数据
void (*stop) (struct seq_file *m, void *v); //停止数据项,和start相对,通常需要解锁
void * (*next) (struct seq_file *m, void *v, loff_t *pos); //下一个要处理的数据项
int (*show) (struct seq_file *m, void *v); //打印数据项到临时缓冲区
};
start在*pos为0时可以返回SEQ_START_TOKEN,通过这个值传递给show的时候,show会打印表格头。
start和next返回一条数据记录,stop停止打印,show显示一条记录。
注意:要在next中对pos递增处理,但递增的单位与迭代器有关,可能不是1。
一些有用的全局函数:
seq_open:通常会在打开文件的时候调用,以第二个参数为seq_operations表创建seq_file结构体。
seq_read, seq_lseek和seq_release:他们通常都直接对应着文件操作表中的read, llseek和release。
seq_escape:将一个字符串中的需要转义的字符(字节长)以8进制的方式打印到seq_file。
seq_putc, seq_puts, seq_printf:他们分别和C语言中的putc,puts和printf相对应。
seq_path:用于输出文件名。
single_open, single_release: 打开和释放只有一条记录的文件。
seq_open_private, __seq_open_private, seq_release_private:和seq_open类似,不过打开seq_file的时候创建一小块文件私有数据。
//#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/percpu.h>
#include <linux/init.h>
#include <linux/sched.h>// #include <linux/sched/signal.h>
static struct proc_dir_entry *entry;
static loff_t offset = 1;
static void *l_start(struct seq_file *m, loff_t * pos)
{
loff_t index = *pos;
loff_t i = 0;
struct task_struct * task ;
if (index == 0) {
seq_printf(m, "Current all the processes in system:\n"
"%-24s%-5s\n", "name", "pid");
printk(KERN_EMERG "++++++++++=========>%5d\n", 0);
// offset = 1;
return &init_task;
}else {
for(i = 0, task=&init_task; i < index; i++){
task = next_task(task);
}
BUG_ON(i != *pos);
if(task == &init_task){
return NULL;
}
printk(KERN_EMERG "++++++++++>%5d\n", task->pid);
return task;
}
}
static void *l_next(struct seq_file *m, void *p, loff_t * pos)
{
struct task_struct * task = (struct task_struct *)p;
task = next_task(task);
if ((*pos != 0) && (task == &init_task)) {
// if ((task == &init_task)) {
// printk(KERN_EMERG "=====>%5d\n", task->pid);
return NULL;
}
printk(KERN_EMERG "=====>%5d\n", task->pid);
offset = ++(*pos);
return task;
}
static void l_stop(struct seq_file *m, void *p)
{
printk(KERN_EMERG "------>\n");
}
static int l_show(struct seq_file *m, void *p)
{
struct task_struct * task = (struct task_struct *)p;
seq_printf(m, "%-24s%-5d\t%lld\n", task->comm, task->pid, offset);
// seq_printf(m, "======>%-24s%-5d\n", task->comm, task->pid);
return 0;
}
static struct seq_operations exam_seq_op = {
.start = l_start,
.next = l_next,
.stop = l_stop,
.show = l_show
};
static int exam_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &exam_seq_op);
}
static struct file_operations exam_seq_fops = {
.open = exam_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init exam_seq_init(void)
{
// entry = create_proc_entry("exam_esq_file", 0, NULL);
entry = proc_create("exam_esq_file", 0444, NULL, &exam_seq_fops);
if (!entry)
printk(KERN_EMERG "proc_create error.\n");
//entry->proc_fops = &exam_seq_fops;
printk(KERN_EMERG "exam_seq_init.\n");
return 0;
}
static void __exit exam_seq_exit(void)
{
remove_proc_entry("exam_esq_file", NULL);
printk(KERN_EMERG "exam_seq_exit.\n");
}
module_init(exam_seq_init);
module_exit(exam_seq_exit);
MODULE_LICENSE("GPL");
参考资料
LDD3
参考文档:Documentation\filesystems\seq_file.txt
https://blog.csdn.net/mumufan05/article/details/45803219
https://www.cnblogs.com/pengdonglin137/p/8439777.html
https://www.cnblogs.com/embedded-linux/p/9751995.html