根据 Linux 内核邮件列表的消息,Linux 社区近日讨论了是否要为内核采用现代 C 语言标准。
Linux 内核在快速发展的同时,但它还依赖着一些非常古老的工具,其中之一就是内核代码仍在使用 1989 年版本的 C 语言标准。该标准在 30 多年前内核项目启动之前就已经编写完成了,可以说早已过时。作为社区的讨论结果,这一情况有望在 5.18 版本内核中发生改变。
Jakob Koschel 近期在向 Linus Torvalds 递交的补丁 (https://lkml.org/lkml/2022/2/17/1032) 中修复了内核链表相关的预测执行漏洞。其原因是 Jakob 发现了一个问题,Linux 内核广泛使用由 struct list_head 定义的双向链表:struct list_head {
struct list_head *next, *prev;
};
这种结构体通常被嵌入到其他结构体中,通过这种方式,开发者可以使用任何感兴趣的结构类型制作链表。除此之外,内核还提供了大量可用于遍历和操作链表的函数和宏。其中之一是 list_for_each_entry(),这是一个伪装成控制结构的宏。要了解如何使用此宏,请假设内核包含如下结构:struct foo {
int fooness;
struct list_head list;
};
list 成员可用于创建 foo 结构体的双向链表,假设我们有一个叫做 foo_list 的结构声明作为此类链表的头,使用以下代码可以遍历此列表:struct foo *iterator;
list_for_each_entry(iterator, &foo_list, list) {
do_something_with(iterator);
}
/* Should not use iterator here */
list 参数告诉宏在 foo 结构中 list_head 结构体的名称。此循环将为列表中的每个元素执行一次,迭代器指向该元素。由此导致了 USB 子系统中的一个 bug:传递给该宏的迭代器在退出宏后还能被使用。Koschel 通过重新编写有问题的代码,以在循环后停止使用迭代器来解决问题。不过 Linus 却对补丁修复的问题表示不解,也没有看到它与预测执行漏洞的关系。Koschel 对此进行了进一步解释,对此 Linus 认为这只是一个普通的 bug。但不久之后 Linus 发现了问题的根源所在:传递给列表遍历宏的迭代器,必须在循环本身之外的范围内声明。随后,Linus 认为也许可以采用更直接的修复如块级变量声明。但 C89 不支持,而 1999 年发布的 C99 标准支持。所以 Linux 内核也许是时候转向使用 C99 标准了。Linus 说到,内核代码一直停留在 C89 的原因之一是编译器 gcc 的旧版本会出现奇怪的问题,导致初始化程序被破坏。不过现在内核要求的 GCC 最低版本已经提高到了 v5.1,那些 bug 可能不再相关了。Linux内核开发人员Arnd Bergmann认可这项计划的可行性,并补充称不如直接升级到2011年的C11标准。这主要是考虑到C99的流行度并不太高,而C11引入了标准化多线程支持并增强了安全性,所以最好能一步到位。
直上C11并不困难。就连Linux内核中的最小C编译器GCC 5.1都能够支持C11。这个理由说服了Torvalds,“这个问题已经酝酿了很多年,我真的很希望能迈出这一步。”
尽管他不确定 C11 是否会带来任何对内核有用的新内容。不过如果升级到 C17 或 C2x,会破坏对 gcc-5/6/7 的支持,因此升级到 C11 更容易实现,而且跨越太大内核社区未必接受。
Linus 赞成了这个想法,在 Bergmann 确认应该可以这样做之后,Linus 宣布将在下一个内核版本 v5.18 中尝试使用 C11 标准。如果一切顺利,下一个内核版本使用的 C 语言标准有望升级到 C11。内核开发人员和记者 Jonathan Corbet 警告说:“需要注意的是,在合并窗口和 5.18 版本之间可能会发生很多事情。迁移到新版本的语言标准可能会带来很多惊喜“。
来源:https://www.sobyte.net/post/2022-02/linux-kernel-to-modern-c/