LWN:将kernel迁移到现代C语言标准!

Linux内核开发者正在考虑升级其代码标准,从1989年的C89转向C99或更现代的标准,如C11。这个改变起源于对内核中linked-list操作安全性的改进,以防止预测执行漏洞。Linus Torvalds认可了这一转变的必要性,尽管这可能导致对较旧GCC版本的支持问题,但随着内核对GCC版本要求的提高,现在可能是合适的时机。迁移可能在5.18内核版本中开始,但全面更新成千上万的list_for_each_entry()等API的使用可能需要更长时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关注了就能看到更多这么棒的文章哦~

Moving the kernel to modern C

By Jonathan Corbet
February 24, 2022
DeepL assisted translation
https://lwn.net/Articles/885941/

尽管 Linux kernel 项目总体上是在快速发展的,但它还是依赖于一些旧工具。虽然批评者总是在关注内核社区对电子邮件的广泛使用这个偏好,但其实有另一个可能更重要的不合时宜之处,也就是内核代码仍在使用 1989 年版本的 C 语言标准。这个标准在 30 多年前内核项目开始之前就已经确定下来。现在看来,这个长期以来的惯例可能会在 5.18 内核(有望在今年 5 月发布)中终结。

Linked-list concerns

这个讨论是从 Jakob Koschel 的一组 patch 开始的,他试图阻止跟内核里的 linked-list 基本操作相关的预测执行的漏洞。内核里大量使用了由 struct list_head 所定义的双向链表:

struct list_head {
    struct list_head *next, *prev;
};

这个结构通常也被嵌入到其他一些结构中。通过这种方式,就可以把任何一个目标结构类型采用双向链表的方式串起来。除了提供了这个结构之外,内核还提供了大量的函数和宏,用来遍历以及操作链表。其中之一就是 list_for_each_entry(),这是一个看起来像是控制结构(control structure)的宏。要想了解这个宏是如何使用的,我们假设内核有下面这样一个的结构:

struct foo {
    int fooness;
    struct list_head list;
};

其中的 list 这个成员变量就可以用来创建一个全部由 foo 结构组成的双链接列表。通常此外还会专门声明一个单独的 list_head structure 作为这个链表的开头,我们假设它名为 foo_list 吧。要想遍历这个列表的话就可以使用这样的代码:

struct foo *iterator;

list_for_each_entry(iterator, &foo_list, list) {
  do_something_with(iterator);
}
/* Should not use iterator here */

其中的参数 list 是用来告诉 list_for_each_entry 这个宏,foo 结构中的 list_head 结构的名字是什么。这个循环将为列表中的每个元素都执行一次,iterator 相应地会指向该元素。

Koschel 提供了一个 patch,用来修复 USB 子系统中的一个 bug,也就是传递给这个宏的 iterator 在宏执行完毕之后仍被使用,这种 bug 是很危险的。根据对 list 中进行的不同操作,这个 iterator 的内容可能是一些出乎意料的内容,哪怕不考虑预测执行(speculative execution)也是有问题的。Koschel 重写了相关代码,不再在循环之后继续使用 iterator,希望能解决这个问题。

The plot twists

Linus Torvalds 不大喜欢这个 patch,也不明白它跟预测执行漏洞有什么关系。不过,在 Koschel 进一步解释情况后,Torvalds 同意 "这只是一个普通的 bug,确实很简单明了",并说它应该不要放在这一大组 patch 里面来 fix。但随后他又开始思考这个问题的真正根源:在这个遍历 list 的宏里使用的 iterator 必须在循环之外的地方声明:

这个跟预测执行无关的 bug 之所以会发生,主要是因为我们当初没有 C99 标准里的 "在循环中声明变量" 的功能。所以 list_for_each_entry() 以及所有其他的类似操作中,基本上总是会将最后一个 HEAD 条目从循环中保留下来,导致泄露,这个 bug 完全是因为我们无法在循环内声明 iterator 变量。

如果能够写出一个遍历 list 的宏,自己声明自己需要的 iterator 变量,那么在循环之外就是无法访问到这个 iterator 的,这种问题就不会出现。但是,由于内核停留在 C89 标准上导致我们无法在循环里声明变量。

Torvalds 说,也许现在已经到了该考虑转向 C99 标准的时候了。尽管 C99 标准也已经有 20 多年的历史了,但至少可以允许 block-level variable 的变量声明了。正如他所指出的,过去没有这样做,是"因为我们在一些古老的 gcc 版本中遇到了一些奇怪的问题,破坏了 documented initializers"。但是,目前内核已经将其对 GCC 版本的最低要求提高到了 5.1,所以也许那些错误已经不会再有了。

Arnd Bergmann 一直在密切关注跨平台的编译器问题,他同意内核可能确实可以往前走了。事实上,他建议在做这次改动的时候能直接走到采用 C11 标准(2011 年的),尽管他不确定 C11 是否会带来什么对内核有用的新功能。甚至有可能切换到 C17 甚至尚未完成的 C2x 版本。然而,这也有一个缺点,那就是 "会破坏对 gcc-5/6/7 的支持",而目前内核仍然支持这些版本。如果将 GCC 的最低支持版本提高到 8.x 的话,用户社区可能现在就不那么太愿意接受了,毕竟跳跃太大了。

不过,转移到 C11 并不需要改变 GCC 的最低要求版本,因此可能更容易做到。Torvalds 赞成这个想法:"考虑到这个问题已经酝酿了很多年,我真的很想在这个问题上取得一些实际进展"。在 Bergmann 确认应该切实可行之后,Torvalds 宣布:"好吧,请大家提醒我,让我们在 5.18 合并窗口的早期就尝试一下"。离 5.18 合并窗口还有不到一个月的时间,所以这是一个可能在不久的将来发生的改动。

但值得注意的是,从合并窗口开启,到 5.18 版本正式发布,之间还会有很多变数。迁移到新版语言标准可能会在内核的一些不明显的地方引出未知数量的意外情况。哪怕只有少数这样的问题,也有可能导致这个改动被 revert。但是如果一切顺利的话,向 C11 的改动就可以在下一个内核版本中完成了。不过,将 list_for_each_entry() 及类似 API 的所有使用位置(在内核中有超过 15,000 个)修改为不会暴露内部 iterator 的新版本,则似乎需要更长的时间了。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

a5a0f48d4430da3e1156938fa58939fd.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值