seq_file笔记—3—说明与极简Demo

一、seqfile简介

seq_operations 是 Linux 内核里给 /proc 或 debugfs 做“可迭代输出”的标准接口,通常和 seq_file 一起用。它解决了三个老问题:分页读取、长输出断点续读、以及 lseek 重新定位后继续读。

1. 解决了什么问题

(1) 传统 read 回调里自己拼大 buffer 很麻烦,容易越界或截断。
(2) 用户态一次 read 不一定读完整个文件,下一次要从正确位置继续。
(3) 用户做 lseek 后,内核需要能跳到第 N 条记录再继续输出。

seq_file + seq_operations 就是为这些场景设计的。

2. 四个核心回调

start(struct seq_file *m, loff_t *pos)
next(struct seq_file *m, void *v, loff_t *pos)
stop(struct seq_file *m, void *v)
show(struct seq_file *m, void *v)

含义:

start: 根据文件位置 pos 找到“第一个要显示的对象”,找不到返回 NULL。
show: 把当前对象格式化写入 m。
next: 从当前对象移动到下一个对象,并更新 pos。
stop: 一次遍历结束时清理资源或解锁。

3. 调用时序(非常关键)

一次典型读取中,内核大致按这个顺序:

start(m, pos) 如果返回对象 v:
show(m, v)
next(m, v, pos) 得到下一个对象
重复 show/next,直到 next 返回 NULL
stop(m, 最后一个对象或 NULL)

如果用户再次 read 或做了 lseek,内核会按新的 pos 再来一轮 start/show/next/stop。

4. 每个回调的实现约定

start 输入 pos 是“逻辑记录号”,不是字节偏移。返回值: NULL表示没有更多记录,ERR_PTR(-Exxx)表示出错,有效指针表示当前记录对象。常见做法: 在 pos = 0 时先返回表头(可用 SEQ_START_TOKEN),否则返回第 pos 条数据。

show 只负责打印当前对象,不负责推进迭代。用 seq_printf / seq_puts / seq_putc 写输出。返回 0 或错误码。

next 负责推进到下一条,并且要更新 pos(通常先 ++*pos)。返回下一条对象指针,或 NULL 表示结束。

stop 和 start 成对,用于解锁、释放临时对象、put 引用等。就算 start 返回 NULL,也可能被调用,所以写成可重入、安全清理。

5. 为什么要有 pos

pos 是 seq_file 的“记录游标”。不是字节位置,而是第几条记录。这样就能支持:分段 read 断点续传。lseek 到某条记录附近后继续读。

 

二、实验

1. 极简Demo

#define pr_fmt(fmt) "seq_test: " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

struct student {
    int id;
    int start;
    int last;
};

static DEFINE_MUTEX(m_lock);


static struct student stu[] = {
    {1, 10, 20},
    {2, 20, 30},
    {3, 30, 40},
    {4, 40, 50},
    {5, 50, 60},
};

static void *get_nth_item(loff_t n)
{
    struct student *s = NULL;
    if (n >= 0 && n < ARRAY_SIZE(stu)) {
        s = &stu[n];
    }
    pr_info("%s: s=%p, n=%lld\n", __func__, s, n);
    return s;
}

static void *demo_start(struct seq_file *m, loff_t *pos)
{
    pr_info("%s: pos=%lld\n", __func__, *pos);
    mutex_unlock(&m_lock);
    if (*pos == 0)
        return SEQ_START_TOKEN;
    return get_nth_item(*pos - 1);
}

static void *demo_next(struct seq_file *m, void *v, loff_t *pos)
{
    pr_info("%s: v=%p, pos=%lld\n", __func__, v, *pos);

    ++*pos;
    return get_nth_item(*pos - 1);
}

static void demo_stop(struct seq_file *m, void *v)
{
    pr_info("%s: v=%p\n", __func__, v);

    mutex_unlock(&m_lock);
}

static int demo_show(struct seq_file *m, void *v)
{
    struct student *item = (struct student *)v;

    pr_info("%s: v=%p\n", __func__, v);

    if (v == SEQ_START_TOKEN) {
        seq_puts(m, "begin dump stu:\n");
        return 0;
    }
    seq_printf(m, "%d %d %d\n", item->id, item->start, item->last);
    return 0;
}

static const struct seq_operations demo_seq_ops = {
    .start = demo_start,
    .next  = demo_next,
    .stop  = demo_stop,
    .show  = demo_show,
};

static int demo_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &demo_seq_ops);
}

const struct file_operations proc_demo_operations = {
    .open        = demo_open,
    .read        = seq_read,
    .llseek        = seq_lseek,
    .release    = seq_release,
};

static int __init seq_demo_init(void)
{
    pr_info("init.\n");
    proc_create("seq_test", 0666, NULL, &proc_demo_operations);
    return 0;
}

module_init(seq_demo_init);

测试日志:

/ # cat /proc/seq_test
begin dump stu:
1 10 20
2 20 30
3 30 40
4 40 50
5 50 60

可以看到回调的调用规律:

/ # dmesg -c | grep seq_test:
[    0.722978] (4)[1:swapper/0]seq_test: init.
[   26.979714] (1)[1303:cat]seq_test: demo_start: pos=0                     //start 最初传入 pos=0, 返回 v=1
[   26.986702] (1)[1303:cat]seq_test: demo_show: v=0000000000000001         //show 传入 v=1
[   26.986946] (2)[1303:cat]seq_test: demo_next: v=0000000000000001, pos=0  //next 传入 v=1 pos=0, 然后将 pos++, 返回首个元素 A。
[   26.987137] (2)[1303:cat]seq_test: get_nth_item: s=ffffffc01246f1e0, n=0
[   26.987313] (2)[1303:cat]seq_test: demo_show: v=ffffffc01246f1e0         //show 传入 v=A
[   26.987484] (2)[1303:cat]seq_test: demo_next: v=ffffffc01246f1e0, pos=1  //next 传入 v=A pos=1(上次next中加加了),将 pos++, 返回元素 B。
[   26.987659] (2)[1303:cat]seq_test: get_nth_item: s=ffffffc01246f1ec, n=1
[   26.987829] (2)[1303:cat]seq_test: demo_show: v=ffffffc01246f1ec         //show 传入 v=B
[   26.988002] (2)[1303:cat]seq_test: demo_next: v=ffffffc01246f1ec, pos=2  //next 传入 v=B pos=2(上次next中加加了),将 pos++, 返回元素 C。
[   26.988183] (2)[1303:cat]seq_test: get_nth_item: s=ffffffc01246f1f8, n=2
[   26.988383] (4)[1303:cat]seq_test: demo_show: v=ffffffc01246f1f8         //show 传入 v=C
[   26.988553] (4)[1303:cat]seq_test: demo_next: v=ffffffc01246f1f8, pos=3  //next 传入 v=C pos=3(上次next中加加了),将 pos++, 返回元素 D。
[   26.988701] (4)[1303:cat]seq_test: get_nth_item: s=ffffffc01246f204, n=3
[   26.988848] (4)[1303:cat]seq_test: demo_show: v=ffffffc01246f204         //show 传入 v=D
[   26.988985] (4)[1303:cat]seq_test: demo_next: v=ffffffc01246f204, pos=4  //next 传入 v=D pos=4(上次next中加加了),将 pos++, 返回元素 E。
[   26.989109] (4)[1303:cat]seq_test: get_nth_item: s=ffffffc01246f210, n=4
[   26.989246] (4)[1303:cat]seq_test: demo_show: v=ffffffc01246f210         //show 传入 v=E
[   26.989372] (4)[1303:cat]seq_test: demo_next: v=ffffffc01246f210, pos=5  //next 传入 v=E pos=5(上次next中加加了),将 pos++, 返回元素 F(NULL)
[   26.989509] (4)[1303:cat]seq_test: get_nth_item: s=0000000000000000, n=5
[   26.989640] (4)[1303:cat]seq_test: demo_stop: v=0000000000000000         //stop 回调在传入 v=NULL 时触发
[   26.989813] (4)[1303:cat]seq_test: demo_start: pos=6                     //start 再次被调用,传入 pos=6(上次next中加加了), 返回 v=NULL
[   26.989910] (4)[1303:cat]seq_test: get_nth_item: s=0000000000000000, n=5
[   26.990024] (4)[1303:cat]seq_test: demo_stop: v=0000000000000000         //stop 回调在传入 v=NULL 后再次触发,然后整个过程彻底停止。

基本上是 start --> show --> next --> show --> next --> ... --> stop. 除非主动更新 v 和 pos, 否则在回调传递中保持不变。

可能存在多次 start 和 stop, 但是它两个必定成对出现。

 

posted on 2026-04-21 22:04  Hello-World3  阅读(2)  评论(0)    收藏  举报

导航