在这里插入图片描述

[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 的共同点:

  1. 计算剩余空间 avail = size - len(若已满则为 0)
  2. 只在 avail > 0 时写入,最多写 avail 个字节
  3. 即使写入被截断,也会设置溢出状态(或让逻辑长度增长到 > 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

  • 实现方式

    • scnprintf:每次调用返回写入量,调用者手动维护 offset/剩余空间。
    • seq_buf:内部维护 offset/溢出状态,调用者只管追加。
  • 性能开销:两者最终都可能走 vsnprintf,主要差异在于 seq_buf 减少了调用者侧重复计算与易错逻辑(可读性收益更明显)。

  • 资源占用:都用调用者提供的固定 buffer。

seq_buf vs seq_file

  • 定位

    • seq_buf:固定 buffer、一次性累积文本(可能截断)。
    • seq_file:面向“可能很长/迭代式”的输出,按需生成,支持大量数据且更适合 /proc 这类接口。
  • 隔离/结构seq_file 框架更规范、约束更多;seq_buf 更轻、更自由。

  • 启动速度(使用复杂度)seq_buf 通常更快上手;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>

/**
* @brief 顺序缓冲区描述符。
*
* 设计要点:
* - 允许“记录溢出状态”:用 s->len > s->size 作为溢出哨兵条件;
* - 写入函数在空间不足时不再继续写入有效数据,但会把溢出状态保留下来;
* - 最终通过 seq_buf_str() 强制 NUL 终止,保证可安全当作 C 字符串读取。
*/
struct seq_buf {
char *buffer; /**< 指向外部提供的缓冲区首地址。 */
size_t size; /**< 缓冲区总容量(字节)。 */
size_t len; /**< 已写入长度;若 len > size 表示已发生溢出。 */
};

/**
* @brief 声明并初始化一个 seq_buf(在栈/静态区内联分配字符数组)。
* @param NAME seq_buf 变量名
* @param SIZE 内联字符数组大小
*
* 说明:
* - 该宏把字符数组直接嵌入初始化表达式中,避免额外分配;
* - 适用于“生命周期明确”的场景(例如函数内临时缓冲)。
*/
#define DECLARE_SEQ_BUF(NAME, SIZE) \
struct seq_buf NAME = { \
.buffer = (char[SIZE]) { 0 }, \
.size = SIZE, \
}

#endif /* _LINUX_SEQ_BUF_H */

seq_buf_clear:清空已写入长度并确保字符串起始为 NUL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief 清空 seq_buf 内容。
* @param s seq_buf 句柄
*
* 关键点:
* - 把 len 置 0;
* - 若 size>0,则写入 buffer[0]='\0',保证“空字符串”语义成立。
*/
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
/**
* @brief 初始化 seq_buf。
* @param s seq_buf 句柄
* @param buf 外部缓冲区首地址
* @param size 外部缓冲区容量
*
* 关键点:
* - seq_buf 本身不分配内存,只记录指针与容量;
* - 初始化会调用 seq_buf_clear(),形成确定的初始字符串状态。
*/
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
/**
* @brief 判断是否已发生溢出。
* @param s seq_buf 句柄
* @return true 表示已溢出
*
* 实现技巧:
* - 不使用单独的“溢出标志位”,而是复用 len 计数:
* 一旦溢出,强制设置 len = size+1,使判断恒成立。
*/
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
/**
* @brief 将 seq_buf 置为溢出状态。
* @param s seq_buf 句柄
*
* 关键点:
* - len=size+1 是“不可达的正常长度”,因此可作为稳定的溢出哨兵值;
* - 后续 buffer_left() 会返回 0,避免继续写入造成越界。
*/
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
/**
* @brief 返回可用剩余空间(字节)。
* @param s seq_buf 句柄
* @return 剩余字节数;若已溢出则为 0
*/
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
/**
* @brief 返回已写入且可安全读取的字节数。
* @param s seq_buf 句柄
* @return min(len, size)
*
* 关键点:
* - 溢出时 len 会被置到 size+1;
* - 用 min(len,size) 截断,保证外部读取不会越界。
*/
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
/**
* @brief 获取 NUL 结尾字符串指针。
* @param s seq_buf 句柄
* @return 指向 s->buffer 的字符串指针;若 size==0 返回空串常量
*
* 实现要点:
* - 未溢出且仍有空间:写 buffer[len]=0;
* - 溢出或无剩余空间:写 buffer[size-1]=0,保证“最后一个字节”是 NUL;
* - 即使溢出,len 仍可能保持 size+1,溢出状态不会被清除。
*/
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
/**
* @brief 获取可写入缓冲区片段。
* @param s seq_buf 句柄
* @param bufp 输出:返回可写入起始指针
* @return 可写入字节数;空间不足返回 0 且 *bufp=NULL
*
* 关键点:
* - 允许 s->len==s->size+1 的溢出哨兵存在,因此做 WARN 检查;
* - 只有在 len<size 时才返回可写空间,避免写入越界。
*/
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
/**
* @brief 提交写入长度。
* @param s seq_buf 句柄
* @param num 提交字节数;若为负数表示写入失败或空间不足
*
* 实现技巧:
* - 用“num<0”作为调用者上报失败/溢出的统一入口;
* - 正常提交路径用 BUG_ON 强约束:调用者不得提交超过可用空间的 num。
*/
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
/**
* @brief 弹出最后写入的字符。
* @param s seq_buf 句柄
* @return 弹出的字符(以 unsigned int 返回);若为空返回 -1
*
* 说明:
* - 仅回退 len 计数,不负责额外的 NUL 维护;
* - 常与 seq_buf_str() 组合使用以获得最终字符串。
*/
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>

/**
* @brief 判断新增数据是否可完全写入(不触发溢出)。
* @param s seq_buf 句柄
* @param len 新增数据长度(字节)
* @return true 表示可容纳
*/
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>

/**
* @brief 以 va_list 形式追加格式化字符串。
* @param s seq_buf 句柄
* @param fmt 格式串
* @param args 参数列表
* @return 0 成功;-1 表示溢出
*
* 关键点:
* - 仅在 s->len < s->size 时才尝试写入,避免已满还写导致越界;
* - vsnprintf 返回“本应写入的长度”,据此判断是否完全写入;
* - 一旦发现无法完全容纳,调用 seq_buf_set_overflow() 固化溢出状态。
*/
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
/**
* @brief 追加格式化字符串(可变参版本)。
* @param s seq_buf 句柄
* @param fmt 格式串
* @return 0 成功;-1 溢出
*/
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
/**
* @brief 追加一个 C 字符串。
* @param s seq_buf 句柄
* @param str 输入字符串
* @return 0 成功;-1 溢出
*
* 实现技巧:
* - 复制时把末尾 '\0' 一并复制进缓冲区,保证中间态也可安全当字符串读取;
* - 但 len 不把这个 '\0' 计入“已用容量”,避免容量被 NUL 消耗。
*/
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
/**
* @brief 追加一个字符。
* @param s seq_buf 句柄
* @param c 字符
* @return 0 成功;-1 溢出
*/
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
/**
* @brief 将 seq_buf 的有效内容写入 seq_file。
* @param m 目标 seq_file。
* @param s 源 seq_buf。
* @return seq_write() 的返回值。
*/
int seq_buf_print_seq(struct seq_file *m, struct seq_buf *s)
{
unsigned int len = seq_buf_used(s); /**< 溢出时也只写入 size 范围内的有效部分,避免越界读取。 */

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
/**
* @brief 将 seq_buf 作为多行文本逐行 printk 输出。
* @param s seq_buf 句柄。
* @param lvl printk 级别字符串(如 KERN_INFO 对应的前缀)。
*/
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); /**< 确保缓冲区以 NUL 结尾,便于 strchr/printk 使用。 */
while ((lf = strchr(start, '\n'))) {
int len = lf - start + 1; /**< 包含 '\n' 的行长度。 */

printk("%s%.*s", lvl, len, start); /**< 使用精度控制输出单行,避免依赖中间临时 NUL。 */
start = ++lf; /**< 跳过 '\n',开始下一行。 */
}

if (start < s->buffer + s->len)
printk("%s%s\n", lvl, start); /**< 最后一行若无 '\n' 结尾,则补一个换行。 */
}

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
/**
* @brief 使用二进制参数数组完成格式化,并把结果追加到 seq_buf。
* @param s seq_buf 句柄。
* @param fmt 格式串(受 bstr_printf 约束)。
* @param binary 二进制参数数组。
* @return 0 成功;-1 溢出。
*/
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
/**
* @brief 追加原始内存到 seq_buf(不做字符串语义处理)。
* @param s seq_buf 句柄。
* @param mem 源内存。
* @param len 拷贝长度(字节)。
* @return 0 成功;-1 溢出。
*/
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)

/**
* @brief 将原始内存转换为十六进制 ASCII 并追加到 seq_buf。
* @param s seq_buf 句柄。
* @param mem 源内存起始。
* @param len 源内存长度(字节)。
* @return 0 成功;-1 溢出。
*/
int seq_buf_putmem_hex(struct seq_buf *s, const void *mem, unsigned int len)
{
unsigned char hex[HEX_CHARS]; /**< 每次最多 8 字节 -> 16 个 hex 字符 + 1 个分隔空格。 */
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]); /**< 取高 4 bit 的 ASCII hex 字符。 */
hex[j++] = hex_asc_lo(data[i]); /**< 取低 4 bit 的 ASCII hex 字符。 */
}
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
/**
* @brief 将路径写入 seq_buf,并按 esc 指定字符集合进行转义处理。
* @param s seq_buf 句柄。
* @param path 要输出的路径对象。
* @param esc 需要转义的字符集合。
* @return 成功返回写入字节数;失败返回 -1。
*/
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); /**< 把路径写入 buf,返回指向路径字符串起始位置的指针或错误指针。 */
if (!IS_ERR(p)) {
char *end = mangle_path(buf, p, esc); /**< 在 buf 内对需要的字符做转义/处理,返回结束位置。 */
if (end)
res = end - buf; /**< 计算实际写入长度,用于 commit。 */
}
}
seq_buf_commit(s, res); /**< res<0 表示错误/空间不足,commit 会把 seq_buf 置为溢出状态。 */

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
/**
* @brief 将 seq_buf 的内容拷贝到用户空间缓冲区。
* @param s seq_buf 句柄。
* @param ubuf 目标用户空间地址。
* @param start 起始偏移。
* @param cnt 拷贝字节数上限。
* @return 成功返回实际拷贝字节数;失败返回负 errno。
*/
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); /**< 溢出时也只按 size 范围内可用数据计算。 */

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
/**
* @brief 把缓冲区内容以十六进制转储格式写入 seq_buf。
* @param s seq_buf 句柄。
* @param prefix_str 每行前缀字符串(对齐空格由调用者提供)。
* @param prefix_type 前缀类型:地址/偏移/无。
* @param rowsize 每行字节数(16 或 32;否则强制为 16)。
* @param groupsize 分组大小(1/2/4/8)。
* @param buf 数据起始。
* @param len 数据长度。
* @param ascii 是否附加 ASCII 区域。
* @return 0 成功;非 0 表示 seq_buf_printf 溢出等错误。
*/
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]; /**< 单行最大缓存:hex 区 + 分隔 + ASCII 区 + NUL。 */
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; /**< seq_buf_printf 返回非 0 通常表示溢出,立即终止。 */
}
return 0;
}