上周《LINUX设备驱动程序第三版》的第一作者、LWN网络杂志创始人兼主编Jonathan Corbet发表了对于Linux内核的一些看法,他认为虽然内核项目总体发展快速,但内核代码仍然使用1989年版本的C语言标准,而5.18内核可能会结束这种情况。
是的,没错,拥有30年历史的Linux内核C语言要升级了。Linux开源社区已经宣布,2022年五月将把内核C语言版本升级到C11。
源于一次补丁,Linux内核缺陷显现
这一决定很迅速,从问题发起到官方拍板宣布用时短短一周。
这一提案要从 Linux 内核贡献者Jakob Koschel的补丁系列说起。
他在研究阻止与内核链表primitive相关的预测执行漏洞时,发现了这样一个问题。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结构组成的双链列表;一个单独的list_head结构通常被视为这个列表的开头,假设有一个叫做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提交补丁,修复了这个错误。根据列表中发生的情况,该迭代器的内容可能出人意料,即使在没有预测执行的情况下。Koschel重写了相关代码,循环后不再需要通过迭代器解决问题。
Linus拍板:Linux内核C语言将升级为C11
事实上,Linux内核的发明人及该计划的合作者Linus Torvalds开始对于这一补丁并不感冒,也不理解它和预测执行漏洞的关系。
不过,在Koschel进一步解释情况后,Torvalds认同"这只是一个常规的错误,简单明了"。但随后他又谈到了问题的真正根源:传递给列表遍历宏的迭代器必须在循环本身之外的范围内声明。
这种非投机性错误之所以会发生,是因为我们在历史上没有C99风格的"在循环中声明变量"。所以list_for_each_entry()——以及所有其他的——往往会将最后一个HEAD条目从循环中泄露出来,仅仅是因为我们无法在循环本身中声明迭代器变量。
如果有可能编写一个可以声明自己的迭代器的列表遍历宏,那么这个迭代器在循环之外是不可见的,就不会出现这种问题。但是,由于内核停留在C89标准上,在循环中声明变量是不可能的。
Torvalds说,也许现在已经到了考虑转向C99标准的时候了——虽然它已经有了20多年的历史,但依旧是最新的版本,足以允许块级变量声明。正如他所指出的,之所以过去没有这样做,"因为我们在一些古老的gcc版本中遇到了一些问题,破坏了文件的初始化器"。但是,与此同时,内核已经把它的最低GCC要求移到了5.1版本,所以也许那些错误已经不重要了。
Linux内核的开发者Arnd Bergmann也非常赞同这一观点,他认为内核有向前发展的可能。事实上,他建议,在进行改变的同时,有可能达到C11标准(从2011年开始),尽管他不确定C11是否会带来对内核有用的新东西。甚至有可能转移到C17甚至尚未完成的C2x版本的语言。然而,这同样存在一个问题,那就是"会破坏对gcc-5/6/7的支持",而目前内核仍然支持这些版本。将GCC的最低版本提高到8.x,可能会和用户社区目前愿意接受的版本有很大的差距。
不过,转移到C11将不需要改变GCC的最低版本,因此可能更容易做到。Torvalds对这个想法表示赞同,并且希望最终能够取得成果。在Bergmann确认这可行之后,Torvalds宣布在5.18合并窗口进行早期尝试。
值得注意的是,在合并窗口和5.18版本之间可能发生很多事情。迁移到新版本的语言标准,可能导致内核中一些不明显的地方出现小bug,而往往一个小小的bug就有可能导致现在的改变被撤销。但是,如果一切顺利的话,下一个内核版本就可能会被正式迁移到C11上。将list_for_each_entry()的所有用户和变种(在内核中有超过15,000个)转换为不暴露内部迭代器的新版本,不过这可能要花不少时间。
你对这次升级有什么看法呢,你觉得升级后会带来什么改变?欢迎大家在下方留言!
参考资料:https://lwn.net/SubscriberLink/885941/01fdc39df2ecc25f/
《新程序员001-004》全面上市,对话世界级大师,报道中国IT行业创新创造
☞