LCTT 译注:昨天,AlmaLinux 称将 放弃对 RHEL 的 1:1 兼容性,但将保持对 RHEL 的 ABI 兼容,以便在 RHEL 上运行的软件可以无缝地运行在 AlmaLinux 上。可能有的同学对 ABI 的概念还不是很清楚,因此翻译此文供大家了解。
许多 Linux 爱好者都熟悉 Linus Torvalds 的 著名告诫:“我们不破坏用户空间”,但可能并非每个听到这句话的人都清楚其含义。
这个“第一规则”提醒开发人员关于应用程序的二进制接口(ABI)的稳定性,该接口用于应用程序与内核之间的通信和配置。接下来的内容旨在使读者熟悉 ABI 的概念,阐述为什么 ABI 的稳定性很重要,并讨论 Linux 稳定 ABI 中包含了哪些内容。Linux 的持续增长和演进需要对 ABI 进行变更,其中一些变更引起了争议。
什么是 ABI?
ABI 表示 应用程序二进制接口Applications Binary Interface。理解 ABI 概念的一种方式是考虑它与其他概念的区别。对于许多开发人员来说,应用程序编程接口Applications Programming Interface(API)更为熟悉。通常,库的头文件和文档被认为是其 API,以及还有像 HTML5这样的标准文档。调用库或交换字符串格式数据的程序必须遵守 API 中所描述的约定,否则可能得到意外的结果。
ABI 类似于 API,因为它们规定了命令的解释和二进制数据的交换方式。对于 C 程序,ABI 通常包括函数的返回类型和参数列表、结构体的布局,以及枚举类型的含义、顺序和范围。截至 2022 年,Linux 内核仍然几乎完全是 C 程序,因此必须遵守这些规范。
“内核系统调用接口” 的描述可以在《Linux 手册第 2 节》中找到,并包括了可从中间件应用程序调用的类似mount和sync的 C 版本函数。这些函数的二进制布局是 Linux ABI 的第一个重要组成部分。对于问题 “Linux 的稳定 ABI 包括哪些内容?”,许多用户和开发人员的回答是 “sysfs(/sys)和 procfs(/proc)的内容”。而实际上,官方 Linux ABI 文档确实主要集中在这些虚拟文件系统上。
前面着重介绍了 Linux ABI 在程序中的应用方式,但未涵盖同等重要的人为因素。正如下图所示,ABI 的功能需要内核社区、C 编译器(如 GCC或clang)、创建用户空间 C 库(通常是glibc)的开发人员,以及按照可执行与链接格式(ELF)布局的二进制应用程序之间的合作努力。
为什么我们关注 ABI?
来自 Torvalds 本人的 Linux ABI 的稳定性保证,使得 Linux 发行版和个人用户能够独立更新内核,而不受操作系统的影响。
如果 Linux 没有稳定的 ABI,那么每次内核需要修补以解决安全问题时,操作系统的大部分甚至全部内容都需要重新安装。显然,二进制接口的稳定性是 Linux 的可用性和广泛采用的重要因素之一。
如上图所示,内核(在 linux-libc-dev中)和 Glibc(在libc6-dev中)都提供了定义文件权限的位掩码。显然,这两个定义集必须一致!apt软件包管理器会识别软件包提供每个文件。Glibc ABI 的潜在不稳定部分位于bits/目录中。
在大部分情况下,Linux ABI 的稳定性保证运作良好。按照 康韦定律Conway's Law,在开发过程中出现的烦人技术问题往往是由于不同软件开发社区之间的误解或分歧所致,而这些社区都为 Linux 做出了贡献。不同社区之间的接口可以通过 Linux 包管理器的元数据轻松地进行想象,如上图所示。
Y2038:一个 ABI 破坏的例子
通过考虑当前正在进行的、缓慢发生的 “Y2038” ABI 破坏的例子,可以更好地理解 Linux ABI。在 2038 年 1 月,32 位时间计数器将回滚到全零,就像较旧车辆的里程表一样。2038 年 1 月听起来还很遥远,但可以肯定的是,如今销售的许多物联网设备仍将处于运行状态。像今年安装的智能电表和智能停车系统这样的普通产品可能采用的是 32 位处理器架构,而且也可能不支持软件更新。
Linux 内核已经在内部转向使用 64 位的 time_t不透明数据类型来表示更晚的时间点。这意味着像time这样的系统调用在 64 位系统上已经变更了它们的函数签名。这些努力的艰难程度可以在内核头文件中(例如time_types.h)清楚地看到,在那里放着新的和_old版本的数据结构。
一致意见似乎正在形成,即希望其 BPF 程序在内核更新后仍能正常工作的开发人员 将需要将其提交到内核源代码库中一个尚未指定的位置。敬请关注后继发展,以了解内核社区对于 BPF 和 ABI 稳定性将采取什么样的政策。
结论
内核的 ABI 稳定性保证适用于 procfs、sysfs 和系统调用接口,但也存在重要的例外情况。当内核变更破坏了“树内”代码或用户空间应用程序时,通常会迅速回滚有问题的补丁。对于依赖内核实现细节的专有代码,尽管这些细节可以从用户空间访问,但它并没有受到保护,并且在出现问题时得到的同情有限。当像 Y2038 这样的问题无法避免 ABI 破坏时,会以尽可能慎重和系统化的方式进行过渡。而像 BPF 程序这样的新功能提出了关于 ABI 稳定性边界的尚未解答的问题。
致谢
感谢 Akkana Peck、Sarah R. Newman和Luke S. Crawford对早期版本材料的有益评论。