美文网首页
linux内核seq_file接口

linux内核seq_file接口

作者: Nothing_655f | 来源:发表于2020-07-23 11:25 被阅读0次

    一些关于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

    相关文章

      网友评论

          本文标题:linux内核seq_file接口

          本文链接:https://www.haomeiwen.com/subject/jbqjlktx.html