Linux ABI 10 分钟指南

熟悉 ABI 的概念、ABI 稳定性的重要性以及 Linux 稳定 ABI 中包含的内容。
7 位读者喜欢这篇文章。
How Linux got to be Linux: Test driving 1993-2003 distros

Internet Archive Book Images。由 Opensource.com 修改。CC BY-SA 4.0

许多 Linux 爱好者都熟悉 Linus Torvalds 的著名告诫,“我们不破坏用户空间”,但也许并非所有认识到这句话的人都确定它意味着什么。

“#1 规则”提醒开发人员应用程序二进制接口的稳定性,应用程序通过该接口与内核通信和配置内核。以下内容旨在使读者熟悉 ABI 的概念,描述 ABI 稳定性为何重要,并精确讨论 Linux 稳定 ABI 中包含的内容。Linux 的持续增长和发展需要对 ABI 进行更改,其中一些更改引起了争议。

什么是 ABI?

ABI 代表应用程序二进制接口 (Applications Binary Interface)。理解 ABI 概念的一种方法是考虑它不是什么。应用程序编程接口 (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 编译器(如 GCCclang)、创建实现系统调用的用户空间 C 库的开发人员(最常见的是 glibc)以及二进制应用程序的共同持续努力,二进制应用程序必须按照可执行和链接格式 (ELF) 进行布局。

Cooperation within the development community

(Alison Chaiken,CC BY-SA 4.0)

我们为什么关心 ABI?

来自 Torvalds 本人的 Linux ABI 稳定性保证使 Linux 发行版和个人用户能够独立于操作系统更新内核。

如果 Linux 没有稳定的 ABI,那么每次内核需要修补以解决安全问题时,操作系统的很大一部分(如果不是全部)都需要重新安装。显然,二进制接口的稳定性是 Linux 的可用性和广泛采用的主要贡献因素。

Terminal output

(Alison Chaiken,CC BY-SA 4.0)

正如第二张图所示,内核 (在 linux-libc-dev 中) 和 Glibc (在 libc6-dev 中) 都提供了定义文件权限的位掩码。显然,这两组定义必须一致!apt 包管理器标识了每个文件由哪个软件项目提供。Glibc 的 ABI 中潜在不稳定的部分位于 bits/ 目录中。

在大多数情况下,Linux ABI 稳定性保证运行良好。与 康威定律 一致,在开发过程中出现的令人烦恼的技术问题最常发生的原因是,为 Linux 做出贡献的不同软件开发社区之间存在误解或分歧。可以通过 Linux 包管理器元数据轻松地设想社区之间的接口,如上图所示。

Y2038:ABI 破坏的示例

通过考虑正在进行的慢动作“Y2038”ABI 破坏示例,可以更好地理解 Linux ABI。在 2038 年 1 月,32 位时间计数器将像旧车辆的里程表一样回滚到全零。2038 年 1 月听起来还很遥远,但可以肯定的是,2022 年销售的许多 IoT 设备仍将运行。像智能电表智能停车系统这样的日常产品,今年安装的可能具有也可能不具有 32 位处理器架构,并且可能支持也可能不支持软件更新。

Linux 内核已经在内部迁移到 64 位 time_t 不透明数据类型,以表示更晚的时间点。这意味着像 time() 这样的系统调用已经在 64 位系统上更改了其函数签名。在内核标头(如 time_types.h)中可以很容易地看到这些工作的艰巨性,其中包括数据结构的新版本和“_old”版本。

Glibc 项目也支持 64 位时间,所以太棒了,我们完成了,对吗?不幸的是,没有,正如 Debian 邮件列表上的讨论所表明的那样。发行版面临着一个令人羡慕的选择:要么为 32 位系统提供所有二进制包的两个版本,要么提供两个版本的安装介质。在后一种情况下,32 位时间的用户将不得不重新编译他们的应用程序并重新安装。与往常一样,专有应用程序将是一个真正的难题。

Linux 稳定 ABI 到底包含什么?

理解稳定的 ABI 有点微妙。考虑到,虽然 sysfs 的大部分是稳定的 ABI,但调试接口保证是稳定的,因为它们向用户空间公开内核内部结构。一般来说,Linus Torvalds 声明,通过“不破坏用户空间”,他的意思是保护“只想让它工作”的普通用户,而不是系统程序员和内核工程师,他们应该能够阅读内核文档和源代码,以弄清楚版本之间发生了哪些变化。下图说明了这种区别。

Stability guarantee

(Alison Chaiken,CC BY-SA 4.0)

普通用户不太可能与 Linux ABI 的不稳定部分交互,但系统程序员可能会无意中这样做。sysfs (/sys) 和 procfs (/proc) 的所有内容都保证稳定,除了 /sys/kernel/debug

但是,其他用户空间可见的二进制接口呢,包括 /dev 中的设备文件、内核日志文件(可以使用 dmesg 命令读取)、文件系统元数据或在内核“命令行”上提供的“bootargs”(在 GRUB 或 u-boot 等引导加载程序中可见)等其他 ABI 位呢?当然,“视情况而定”。

挂载旧文件系统

除了观察 Linux 系统在启动序列期间挂起外,文件系统无法挂载是最令人失望的事情。如果文件系统位于付费客户的 SSD 上,则问题确实很严重。当然,使用旧内核版本挂载的 Linux 文件系统在内核升级后仍然可以挂载,对吗?实际上,“视情况而定”。

在 2020 年,一位不满的 Linux 开发人员在 内核邮件列表中抱怨

内核已经接受此格式作为有效的可挂载文件系统格式,没有任何错误或警告,并且多年来一直稳定地这样做。 。 。我通常认为,挂载现有的根文件系统属于内核<->用户空间或内核<->现有系统边界的范围,如内核接受的内容和现有用户空间已成功使用的内容所定义的那样,并且升级内核应该与现有用户空间和系统一起工作。

但有一个问题:无法挂载的文件系统是使用专有工具创建的,该工具依赖于内核定义但未使用的标志。该标志未出现在 Linux 的 API 标头文件或 procfs/sysfs 中,而是 实现细节。因此,在用户空间代码中解释该标志意味着依赖于“未定义的行为”,这个词组会让几乎所有软件开发人员都不寒而栗。当内核社区改进其内部测试并开始进行新的一致性检查时,“man 2 mount”系统调用突然开始拒绝使用专有格式的文件系统。由于格式创建者显然是软件开发人员,因此他没有得到内核文件系统维护人员的多少同情。

Construction sign reading crews working in trees

(树内工作的内核开发人员受到 ABI 更改的保护。Alison Chaiken,CC BY-SA 4.0)

线程化内核 dmesg 日志

/dev 中文件的格式是否保证稳定?dmesg 命令从文件 /dev/kmsg 读取。在 2018 年,一位开发人员使 dmesg 的输出线程化,使内核“能够将一系列 printk() 消息打印到控制台,而不会受到来自中断和/或其他线程的并发 printk() 的干扰。”听起来很棒!通过向 /dev/kmsg 输出的每一行添加线程 ID,使线程化成为可能。密切关注的读者会意识到,此添加更改了 /dev/kmsg 的 ABI,这意味着解析该文件的应用程序也需要更改。由于许多发行版在编译其内核时未启用新功能,因此 /bin/dmesg 的大多数用户不会注意到,但此更改破坏了 GDB 调试器读取内核日志的能力。

可以肯定的是,精明的读者会认为 GDB 的用户运气不佳,因为调试器是开发人员工具。实际上,并非如此,因为需要更新以支持新的 /dev/kmsg 格式的代码是“树内”的,这意味着内核自身 Git 源代码存储库的一部分。单个存储库中的程序无法协同工作,这对于任何理智的项目来说都只是一个彻头彻尾的错误,并且合并了一个使 GDB 与线程化 /dev/kmsg 一起工作的补丁

BPF 程序呢?

BPF 是一个强大的工具,可以动态监视甚至配置正在运行的内核。BPF 的最初目的是通过允许系统管理员立即从命令行修改数据包过滤器来支持即时网络配置。Alexei Starovoitov 和其他人极大地扩展了 BPF,使其能够跟踪任意内核函数。跟踪显然是开发人员而不是普通用户的领域,因此它当然不受任何 ABI 保证的约束(尽管 bpf() 系统调用具有与其他系统调用相同的稳定性承诺)。另一方面,创建新功能的 BPF 程序提出了“取代内核模块作为扩展内核的事实标准方法”的可能性。内核模块使设备、文件系统、加密、网络等工作,因此显然是“只想让它工作”的用户所依赖的工具。问题在于,BPF 程序传统上不是“树内”的,而大多数开源内核模块都是。“在 2022 年春季,一项通过小型 BPF 程序而不是设备驱动程序补丁来提供对大量人机接口设备 (HID)(如鼠标和键盘)的支持”的提案使这个问题成为关注焦点。

随后进行了相当激烈的讨论,但这个问题显然通过 Torvalds 在开源峰会上的评论得到了解决

他明确指出,如果你破坏了“普通(非内核开发人员)用户使用的真实用户空间工具”,那么你需要修复它,无论它是否使用 eBPF。

似乎正在形成一种共识,即期望其 BPF 程序能够经受内核更新的开发人员将需要将它们提交到内核源代码存储库中尚未指定的某个位置。请继续关注,以了解内核社区对 BPF 和 ABI 稳定性采取的策略。

结论

内核 ABI 稳定性保证适用于 procfs、sysfs 和系统调用接口,但有一些重要的例外。当“树内”代码或用户空间应用程序因内核更改而“损坏”时,通常会快速回滚有问题的补丁。当专有代码依赖于用户空间可以访问的内核实现细节时,它不受保护,并且在损坏时几乎不会得到同情。当像 Y2038 那样,无法避免 ABI 破坏时,转换会尽可能周到和有条不紊地进行。像 BPF 程序这样的新功能提出了关于 ABI 稳定性边界究竟在哪里等尚未解答的问题。


致谢

感谢 Akkana PeckSarah R. NewmanLuke S. Crawford 对本文早期版本的有益评论。

标签
User profile image.
Alison Chaiken 是一位软件开发人员,在加利福尼亚州山景城骑自行车。她的日常工作是在 Aurora Innovation 维护 Linux 内核并用 C++ 编写操作系统监控应用程序。Alison 为 u-boot、kernel、bazel 和 systemd 贡献了上游代码,并在 Embedded Linux Conference、Usenix、linux.conf.au 和 Southern California Linux Expo 上发表了演讲。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.