关注了就能看到更多这么棒的文章哦~
Better string handling for the kernel
By Jonathan Corbet
October 26, 2023
ChatGPT translation
https://lwn.net/Articles/948408/
C 语言中有很多功能在当时看起来是个好主意(甚至可能在那时确实是好主意),但现在已经过时了。大多数人可能会同意,字符串处理和使用以 NUL 结尾的字符串就是其中一个例子。多年来,内核开发人员一直在试图改进字符串处理,以弥补由这方面的错误导致的 bug 和漏洞的不断出现。现在有一个早期讨论是想要在内核的许多地方摆脱以 NUL 结尾的字符串。
以 NUL 结尾的字符串最大的问题在于它们未带有关于它的相关缓冲区大小的信息;这就很容易发生缓冲区溢出。多年来,为解决这个问题,已经创建了许多 API,添加了像 strncpy(), strlcpy(), 和 strscpy() 这样的函数,每个函数都声称比之前的版本更好。然而,它们都没有改变以 NUL 结尾的字符串的核心概念,并且没有一个能让每个人都满意。
多年来,内核已经看到了一系列演变,有很多 patch 都是从一个字符串函数改到另一个字符串函数。有时这些转换引入了自己特有的 bug,维护者并不总是对这项工作感到满意。最近,对于将 strncpy() 调用转换为 strscpy() 的补丁,Christoph Hellwig 质疑了这些更改的价值:
我们不能再继续假装这种直接操作以 NUL 结尾的字符串是个好主意了。我怀疑用一个更好,也许稍微好一点的辅助函数替换另一个的这种改动可能引入的错误比它修复的错误还多。
他说,真正解决这个问题需要使用比 NUL 结尾的字符串更好的方案。他将 "seq_buf" API 描述为在这个方向上的 "一个良好的开始"。
Seq_buf 是在 2014 年为 3.19 内核版本引入的,作为构建跟踪子系统中字符串的一种改进方法。它是围绕这个结构组织:
struct seq_buf {
char *buffer;
size_t size;
size_t len;
loff_t readpos;
};
这里没有什么革命性的东西。`buffer` 指向字符串分配的内存,`size` 是该缓冲区的大小。存储在 `buffer` 中的字符串的长度保存在 `len` 中;`readpos` 字段用于在字符串中进行读取。用户必须单独分配 `buffer` 并通过调用以下函数将其附加到 `seq_buf`:
void seq_buf_init(struct seq_buf *s, char *buf, unsigned int size);
有一整套用于处理这个结构的函数,其中大多数函数的含义都相当直白了:
void seq_buf_clear(struct seq_buf *s);
int seq_buf_printf(struct seq_buf *s, const char *fmt, ...);
int seq_buf_puts(struct seq_buf *s, const char *str);
/* ... */
还有很多其他函数;请参阅 include/linux/seq_buf.h 以获取完整的列表。
对 seq_buf 的操作不会出现溢出缓冲区的情况;如果试图存储太多数据,将记录这个事实(`len` 设置为大于 `size` 的值),并且未来的操作将失败。调用 `seq_buf_has_overflowed()` 将返回一个布尔值,告诉是否 `seq_buf` 处于良好状态。该 API 允许进行一系列的字符串操作,只需要在最后进行一次 `seq_buf_has_overflowed()` 检查,以确保一切正常;这显著简化了错误处理代码。
没有访问字符串本身的访问器函数;用户可以直接访问结构中的 `buffer` 字段。该字符串保持以 NUL 结尾,因此可以传递给期望这样的字符串的函数—当然,它们不应直接修改缓冲区。
`readpos` 字段仅在跟踪代码中使用。为了减少总体上使用 `struct seq_buf` 的内存,Matthew Wilcox 最近发布了 一项补丁,删除了该字段并将读取位置的概念推入使用它的代码。这个补丁似乎可能会被接受,因此 `readpos` 将来可能不会成为这个结构的一部分。
Kent Overstreet 最近发布了 seq_buf 的替代品,名为 "printbuf",采用了稍微不同的方法。在该补丁集的讨论中,他被 建议 改进 seq_buf,但他拒绝了这一建议。因此,printbuf 仍然存在于内核之外,但它似乎出现在 linux-next 中,作为预计将在 6.7 中合并的 bcachefs 系列的一部分。printbuf 是否会被视为 seq_buf 的替代品还不清楚,但社区不太可能希望在长期内维护两种相似的字符串抽象。
内核是否会在长期内大规模使用 seq_buf(或 printbuf)?很难判断这种转变可能会走多远。Kees Cook 说,虽然 seq_buf "表现出色" 用于复杂的字符串操作,但对于简单的情况而言,它带来的麻烦大于它的价值,可以在使用更简单的 API 的代码中轻松验证为正确。然而,Willy Tarreau 表示,具有显式长度计数器的字符串更容易处理,可以得到更简单的代码,并且还可能带来更好的性能。与此同时,Linus Torvalds 似乎主要关心 摆脱内核中剩余的 strlcpy() 调用,对于改变内核对字符串的处理方式并没有表达意见。
这场讨论可能会鼓励一些开发人员开始在以 NUL 结尾的 C 字符串的位置使用 seq_buf。然而,这种转变可能会相对缓慢地开始。如果随着时间的推移,这种方法开始显示出好处,可能最终会推动在更广泛的范围进行转换。但是,无论如何,内核似乎有可能在很长一段时间内管理大量以 NUL 结尾的字符串。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~