
[TOC]
Linux 内核 seq_buf 实现解析(lib/seq_buf.c)
[lib/seq_buf.c] [seq_buf:序列化缓冲区工具] [在固定大小缓冲区内安全地累积格式化输出,并支持“分段读出”给用户态]
介绍
seq_buf 可以理解为一个**“带状态的 snprintf 追加器”:你给它一个固定大小的 char buffer[],然后不断 printf/puts/putc 追加内容。它会维护当前写入位置、是否溢出等状态,保证不越界写**,并且常配套提供“按 readpos 分段把数据拷给用户态”的能力,常用于各种 debug 输出路径(尤其是实现 proc/debugfs/sysfs 的 show/read 辅助逻辑)。
历史与背景
为了解决什么问题而诞生?
内核里经常需要拼接字符串输出(调试信息、统计信息、状态信息):
- 只用
snprintf/scnprintf:每次都要手动管理“剩余长度、当前偏移”,代码重复且容易写错。
- 只用
seq_file:能力强但更重,必须走 start/next/show/stop 迭代模型,不适合“我就想往一个小 buffer 里拼一段文本”的场景。
seq_buf 就是为这些“固定大小、追加式文本构建”场景提供统一工具,减少重复代码并降低越界风险。
里程碑/迭代方向(从设计形态上看)
- 从“散落的 open-coded snprintf 追加”收敛到统一 API(printf/puts/putc/putmem 等)。
- 引入溢出标记:让调用者能明确判断输出是否被截断,而不是靠返回值推断。
- 引入 readpos:方便在实现 read(2) 时按用户请求偏移分段返回(避免一次性拷贝或重复构建)。
主流应用情况
它属于内核常用的基础小工具:尤其在需要小而确定的输出、或者你不想引入 seq_file 的地方,会看到它。
核心原理与设计
核心工作原理
seq_buf 的核心状态一般包括(字段名可能因版本略有差异,但语义大体一致):
buffer:输出缓冲区起始地址
size:缓冲区容量
len:当前已“追加”的总长度(逻辑长度)
readpos:读位置(用于按需把数据分段拷贝给用户态)
overflow:是否发生过溢出/截断(通常用标志位或让 len > size 来表达)
写入类 API 的共同点:
- 计算剩余空间
avail = size - len(若已满则为 0)
- 只在
avail > 0 时写入,最多写 avail 个字节
- 即使写入被截断,也会设置溢出状态(或让逻辑长度增长到
> size),便于调用者检测
读出类逻辑(如果你实现一个 read 接口时用到):
- 根据
readpos 和用户请求的 count,从 buffer + readpos 拷贝一段出去;
- 更新
readpos;
- 当
readpos >= len_used(有效长度)时表示读完。
主要优势
- 安全:统一处理边界,避免越界写。
- 简单:调用端不需要反复写“offset += scnprintf(…)”这类样板代码。
- 可检测截断:能明确判断输出是否溢出,便于在日志/调试接口里做提示或改大缓冲区。
- 适合实现 read(2) 分段返回:通过
readpos 简化“多次 read 逐步输出”的实现。
劣势/局限
- 固定大小:缓冲区不自动增长,内容可能被截断;需要你合理估算 size,或者改用
seq_file。
- 并发语义由调用者保证:
seq_buf 自身通常不负责锁;多线程/中断并发写同一个 buf 需要你外部加锁。
- 更偏文本:虽然可能有 putmem/hex dump 辅助,但它的主要目标是“拼文本输出”。
使用场景
首选场景(举例)
- sysfs/debugfs/proc 的简单 show/read:输出量可控(几十到几千字节),你希望代码短且不引入
seq_file 迭代框架。
- 驱动/子系统的状态快照:一次构建一段文本,随后可能分段读出。
- 错误信息/诊断信息拼接:在不方便动态分配内存的路径里,使用固定栈/静态 buffer 构建输出。
不推荐使用的场景
- 输出量不可控或可能很大:比如遍历大量对象/条目输出,推荐
seq_file(避免截断,且支持迭代生成)。
- 需要频繁构建且 buffer 很大:大量
vsnprintf 仍有成本;要评估是否需要更结构化输出或缓存策略。
对比分析
seq_buf vs scnprintf/snprintf
seq_buf vs seq_file
总结
关键特性
- 固定缓冲区内的安全追加(printf/puts/putc 等)
- 溢出可检测(避免默默截断不自知)
- 可选的 readpos 支持,方便实现分段 read 输出
seq_buf(基础数据结构):提供“可溢出检测”的顺序写入缓冲区描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #ifndef _LINUX_SEQ_BUF_H #define _LINUX_SEQ_BUF_H
#include <linux/bug.h> #include <linux/minmax.h> #include <linux/seq_file.h> #include <linux/types.h>
struct seq_buf { char *buffer; size_t size; size_t len; };
#define DECLARE_SEQ_BUF(NAME, SIZE) \ struct seq_buf NAME = { \ .buffer = (char[SIZE]) { 0 }, \ .size = SIZE, \ }
#endif
|
seq_buf_clear:清空已写入长度并确保字符串起始为 NUL
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
static inline void seq_buf_clear(struct seq_buf *s) { s->len = 0; if (s->size) s->buffer[0] = '\0'; }
|
seq_buf_init:绑定外部缓冲区并初始化计数器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
static inline void seq_buf_init(struct seq_buf *s, char *buf, unsigned int size) { s->buffer = buf; s->size = size; seq_buf_clear(s); }
|
seq_buf_has_overflowed:以 len>size 作为溢出哨兵判断
1 2 3 4 5 6 7 8 9 10 11 12 13
|
static inline bool seq_buf_has_overflowed(struct seq_buf *s) { return s->len > s->size; }
|
seq_buf_set_overflow:将 len 置为 size+1 以锁定溢出状态
1 2 3 4 5 6 7 8 9 10 11 12
|
static inline void seq_buf_set_overflow(struct seq_buf *s) { s->len = s->size + 1; }
|
seq_buf_buffer_left:计算剩余空间(溢出后返回 0)
1 2 3 4 5 6 7 8 9 10 11 12
|
static inline unsigned int seq_buf_buffer_left(struct seq_buf *s) { if (seq_buf_has_overflowed(s)) return 0;
return s->size - s->len; }
|
seq_buf_used:返回“可读有效数据”长度(对溢出做截断)
1 2 3 4 5 6 7 8 9 10 11 12 13
|
static inline unsigned int seq_buf_used(struct seq_buf *s) { return min(s->len, s->size); }
|
seq_buf_str:强制 NUL 终止并返回可作为 C 字符串读取的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
static inline const char *seq_buf_str(struct seq_buf *s) { if (WARN_ON(s->size == 0)) return "";
if (seq_buf_buffer_left(s)) s->buffer[s->len] = 0; else s->buffer[s->size - 1] = 0;
return s->buffer; }
|
seq_buf_get_buf:获取“可直接写入”的连续空间指针与长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
static inline size_t seq_buf_get_buf(struct seq_buf *s, char **bufp) { WARN_ON(s->len > s->size + 1);
if (s->len < s->size) { *bufp = s->buffer + s->len; return s->size - s->len; }
*bufp = NULL; return 0; }
|
seq_buf_commit:提交之前通过 seq_buf_get_buf 获得的写入长度(负值表示溢出)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
static inline void seq_buf_commit(struct seq_buf *s, int num) { if (num < 0) { seq_buf_set_overflow(s); } else { BUG_ON(s->len + num > s->size); s->len += num; } }
|
seq_buf_pop:回退一个字符(用于撤销末尾输出)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
static inline int seq_buf_pop(struct seq_buf *s) { if (!s->len) return -1;
s->len--; return (unsigned int)s->buffer[s->len]; }
|
seq_buf_can_fit:判断“新增 len 字节”是否仍能容纳(seq_buf.c 内部使用)
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <linux/sprintf.h> #include <linux/string.h>
static bool seq_buf_can_fit(struct seq_buf *s, size_t len) { return s->len + len <= s->size; }
|
seq_buf_vprintf:把格式化输出追加到 seq_buf(溢出则锁定溢出状态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <linux/printk.h> #include <linux/seq_buf.h>
int seq_buf_vprintf(struct seq_buf *s, const char *fmt, va_list args) { int len;
WARN_ON(s->size == 0);
if (s->len < s->size) { len = vsnprintf(s->buffer + s->len, s->size - s->len, fmt, args); if (s->len + len < s->size) { s->len += len; return 0; } } seq_buf_set_overflow(s); return -1; }
|
seq_buf_printf:对外的可变参封装(内部转调 seq_buf_vprintf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
int seq_buf_printf(struct seq_buf *s, const char *fmt, ...) { va_list ap; int ret;
va_start(ap, fmt); ret = seq_buf_vprintf(s, fmt, ap); va_end(ap);
return ret; }
|
seq_buf_puts:追加普通字符串(确保写入 NUL,但不计入容量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
int seq_buf_puts(struct seq_buf *s, const char *str) { size_t len = strlen(str);
WARN_ON(s->size == 0);
len += 1;
if (seq_buf_can_fit(s, len)) { memcpy(s->buffer + s->len, str, len); s->len += len - 1; return 0; } seq_buf_set_overflow(s); return -1; }
|
seq_buf_putc:追加单个字符(溢出则锁定溢出状态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
int seq_buf_putc(struct seq_buf *s, unsigned char c) { WARN_ON(s->size == 0);
if (seq_buf_can_fit(s, 1)) { s->buffer[s->len++] = c; return 0; } seq_buf_set_overflow(s); return -1; }
|
seq_buf_print_seq:把 seq_buf 内容转存到 seq_file
1 2 3 4 5 6 7 8 9 10 11 12
|
int seq_buf_print_seq(struct seq_file *m, struct seq_buf *s) { unsigned int len = seq_buf_used(s);
return seq_write(m, s->buffer, len); }
|
seq_buf_do_printk:按行把 seq_buf 内容输出到 printk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
void seq_buf_do_printk(struct seq_buf *s, const char *lvl) { const char *start, *lf;
if (s->size == 0 || s->len == 0) return;
start = seq_buf_str(s); while ((lf = strchr(start, '\n'))) { int len = lf - start + 1;
printk("%s%.*s", lvl, len, start); start = ++lf; }
if (start < s->buffer + s->len) printk("%s%s\n", lvl, start); }
|
seq_buf_bprintf:把二进制参数按格式串转换为 ASCII 并追加到 seq_buf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #ifdef CONFIG_BINARY_PRINTF
int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary) { unsigned int len = seq_buf_buffer_left(s); int ret;
WARN_ON(s->size == 0);
if (s->len < s->size) { ret = bstr_printf(s->buffer + s->len, len, fmt, binary); if (s->len + ret < s->size) { s->len += ret; return 0; } } seq_buf_set_overflow(s); return -1; } #endif
|
seq_buf_putmem:把原始内存块追加到 seq_buf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
int seq_buf_putmem(struct seq_buf *s, const void *mem, unsigned int len) { WARN_ON(s->size == 0);
if (seq_buf_can_fit(s, len)) { memcpy(s->buffer + s->len, mem, len); s->len += len; return 0; } seq_buf_set_overflow(s); return -1; }
|
seq_buf_putmem_hex:把内存块转为 ASCII 十六进制并追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #define MAX_MEMHEX_BYTES 8U #define HEX_CHARS (MAX_MEMHEX_BYTES * 2 + 1)
int seq_buf_putmem_hex(struct seq_buf *s, const void *mem, unsigned int len) { unsigned char hex[HEX_CHARS]; const unsigned char *data = mem; unsigned int start_len; int i, j;
WARN_ON(s->size == 0);
BUILD_BUG_ON(MAX_MEMHEX_BYTES * 2 >= HEX_CHARS);
while (len) { start_len = min(len, MAX_MEMHEX_BYTES); #ifdef __BIG_ENDIAN for (i = 0, j = 0; i < start_len; i++) { #else for (i = start_len - 1, j = 0; i >= 0; i--) { #endif hex[j++] = hex_asc_hi(data[i]); hex[j++] = hex_asc_lo(data[i]); } if (WARN_ON_ONCE(j == 0 || j / 2 > len)) break;
hex[j++] = ' ';
seq_buf_putmem(s, hex, j); if (seq_buf_has_overflowed(s)) return -1;
len -= start_len; data += start_len; } return 0; }
|
seq_buf_path:把 path 写入 seq_buf(并按需转义)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
int seq_buf_path(struct seq_buf *s, const struct path *path, const char *esc) { char *buf; size_t size = seq_buf_get_buf(s, &buf); int res = -1;
WARN_ON(s->size == 0);
if (size) { char *p = d_path(path, buf, size); if (!IS_ERR(p)) { char *end = mangle_path(buf, p, esc); if (end) res = end - buf; } } seq_buf_commit(s, res);
return res; }
|
seq_buf_to_user:把 seq_buf 的指定范围拷贝到用户空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
int seq_buf_to_user(struct seq_buf *s, char __user *ubuf, size_t start, int cnt) { int len; int ret;
if (!cnt) return 0;
len = seq_buf_used(s);
if (len <= start) return -EBUSY;
len -= start; if (cnt > len) cnt = len;
ret = copy_to_user(ubuf, s->buffer + start, cnt); if (ret == cnt) return -EFAULT;
return cnt - ret; }
|
seq_buf_hex_dump:把数据块按行格式化十六进制转储到 seq_buf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
int seq_buf_hex_dump(struct seq_buf *s, const char *prefix_str, int prefix_type, int rowsize, int groupsize, const void *buf, size_t len, bool ascii) { const u8 *ptr = buf; int i, linelen, remaining = len; unsigned char linebuf[32 * 3 + 2 + 32 + 1]; int ret;
if (rowsize != 16 && rowsize != 32) rowsize = 16;
for (i = 0; i < len; i += rowsize) { linelen = min(remaining, rowsize); remaining -= rowsize;
hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, linebuf, sizeof(linebuf), ascii);
switch (prefix_type) { case DUMP_PREFIX_ADDRESS: ret = seq_buf_printf(s, "%s%p: %s\n", prefix_str, ptr + i, linebuf); break; case DUMP_PREFIX_OFFSET: ret = seq_buf_printf(s, "%s%.8x: %s\n", prefix_str, i, linebuf); break; default: ret = seq_buf_printf(s, "%s%s\n", prefix_str, linebuf); break; }
if (ret) return ret; } return 0; }
|