linux内核seq_file接口

2020-07-23  本文已影响0人  Nothing_655f

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

上一篇下一篇

猜你喜欢

热点阅读