上周,Linux 内核邮件列表上关于“社区最近讨论了是否为内核采用现代 C 语言标准”的信息引发业内关注。刚刚,Linux 开源社区已正式宣布:内核 C 语言版本将在未来升级到 C11,且预计将在今年 5 月份的 5.18 版本之后生效。
这个突然的决定,也终于让拥有 30 年历史的 Linux 内核 C 语言迎来了升级。
众所周知,想要说服固执的 Linux 之父 Linus Torvalds 绝非易事。那么,这一次 Linus Torvalds 为何终于松口了呢?这里面,似乎还真有那么一点偶然因素。
事件起因还是要回到上周的那次的 Linux 社区讨论。
据悉,当时一位名叫 Jakob Koschel 的博士生正在研究与内核链表原语相关的推测性执行漏洞,过程中他发现了一个问题: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 子系统中出现错误:在退出宏后,传递给该宏的迭代器仍可使用。当然,这是一件非常“危险”的事情。所以,Koschel 提交了一个补丁,重新编写了有问题的代码,通过在循环结束后停止使用迭代器来修复这个错误。随后,Jakob Koschel 将(投机性安全列表迭代器建议)修复的与内核链接表相关的预测执行漏洞的补丁提交给了 Linus Torvalds。
最初,Linus Torvalds 本人似乎对这个补丁并不是很喜欢,也不知道该补丁与推测性执行漏洞有什么关系。但经过 Koschel 详细解释之后,Linus 承认了这只是一个常见的 Bug。然而,事情并非那么简单,Linus 很快就意识到了真正的问题:传递给链表遍历宏的迭代器必须在循环本身之外的范围内声明。而出现这种不可预测的错误的原因是 C89 中没有“在循环中声明变量”。我们知道,虽然 Linux 内核正在快速发展,但它也依赖于一些非常古老的工具,其中之一就是其内核代码仍在使用 1989 年版的 C 语言标准,也就是说,该标准是在内核项目启动 30 多年前编写的。像 list_for_each_entry()这样的宏,基本上总是将最后一个 HEAD 条目泄漏出循环,就是因为不能在循环本身中声明迭代器变量。如果可以编写一个迭代器列表遍历宏来声明自己,那么迭代器在循环外就不可见,也不会出现这样的问题。然而,由于内核停留在C89标准上,因此不可能在循环中声明变量。因此,Linus 决定,“让我们升级一下”,也许是时候升级到 C99 标准了,尽管 C99 也有 20 多年的历史了,但它至少比 C89 更新一点,且可以在循环中声明变量。既然 C89 已经过时了,为什么这么多年都没有改变呢?Linus 解释称,“这是因为我们在一些旧的 gcc 编译器版本上遇到了一些奇怪的问题,这些版本不能随意升级。”然而,现在 Linux 内核已经将 gcc 的最低要求提高到了 5.1 版,过去那些奇怪的 Bug 应该消失了。另一位核心开发者 Arnd Bergmann 也对此事比较关注,他认为可以升级到 C11 甚至更高版本,但升级到 C17 或 C2x 会破坏 gcc-5/6/7 支持,因此升级到 C11 更容易实现。最终,Linus Torvalds 支持了这个想法,并宣布将“在 5.18 版合并窗口的早期尝试一下”。虽然接下来转移到 C11 可能会导致一些意想不到的 Bug 也说不定,但如果一切顺利,下一个 Linux 内核版本将正式转移到 C11。您对此次升级事件有何看法呢?也欢迎在下方交流互动。想成为前端大佬?
测一测你的段位