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) 收藏 举报
浙公网安备 33010602011771号