10 分鐘讓你了解 Linux ABI
熟悉 ABI 的概念、ABI 穩定性的重要性以及 Linux 穩定 ABI 中包含的內容。
LCTT 譯註:昨天,AlmaLinux 稱將 放棄 對 RHEL 的 1:1 兼容性,但將保持對 RHEL 的 ABI 兼容,以便在 RHEL 上運行的軟體可以無縫地運行在 AlmaLinux 上。可能有的同學對 ABI 的概念還不是很清楚,因此翻譯此文供大家了解。
許多 Linux 愛好者都熟悉 Linus Torvalds 的 著名告誡:「我們不破壞用戶空間」,但可能並非每個聽到這句話的人都清楚其含義。
這個「第一規則」提醒開發人員關於應用程序的二進位介面(ABI)的穩定性,該介面用於應用程序與內核之間的通信和配置。接下來的內容旨在使讀者熟悉 ABI 的概念,闡述為什麼 ABI 的穩定性很重要,並討論 Linux 穩定 ABI 中包含了哪些內容。Linux 的持續增長和演進需要對 ABI 進行變更,其中一些變更引起了爭議。
什麼是 ABI?
ABI 表示 應用程序二進位介面 。理解 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 編譯器(如 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 的穩定性保證運作良好。按照 康韋定律 ,在開發過程中出現的煩人技術問題往往是由於不同軟體開發社區之間的誤解或分歧所致,而這些社區都為 Linux 做出了貢獻。不同社區之間的介面可以通過 Linux 包管理器的元數據輕鬆地進行想像,如上圖所示。
Y2038:一個 ABI 破壞的例子
通過考慮當前正在進行的、緩慢發生 的 「Y2038」 ABI 破壞的例子,可以更好地理解 Linux ABI。在 2038 年 1 月,32 位時間計數器將回滾到全零,就像較舊車輛的里程錶一樣。2038 年 1 月聽起來還很遙遠,但可以肯定的是,如今銷售的許多物聯網設備仍將處於運行狀態。像今年安裝的 智能電錶 和 智能停車系統 這樣的普通產品可能採用的是 32 位處理器架構,而且也可能不支持軟體更新。
Linux 內核已經在內部轉向使用 64 位的 time_t
不透明數據類型來表示更晚的時間點。這意味著像 time()
這樣的系統調用在 64 位系統上已經變更了它們的函數簽名。這些努力的艱難程度可以在內核頭文件中(例如 time_types.h)清楚地看到,在那裡放著新的和 _old
版本的數據結構。
Glibc 項目也 支持 64 位時間,那麼就大功告成了,對嗎?不幸的是,根據 Debian 郵件列表中的討論 來看,情況並非如此。發行版面臨難以選擇的問題,要麼為 32 位系統提供所有二進位軟體包的兩個版本,要麼為安裝介質提供兩個版本。在後一種情況下,32 位時間的用戶將不得不重新編譯其應用程序並重新安裝。正如往常一樣,專有應用程序才是一個真正的頭疼問題。
Linux 穩定 ABI 里到底包括什麼內容?
理解穩定 ABI 有些微妙。需要考慮的是,儘管大部分 sysfs 是穩定 ABI,但調試介面肯定是不穩定的,因為它們將內核內部暴露給用戶空間。Linus Torvalds 曾表示,「不要破壞用戶空間」,通常情況下,他是指保護那些 「只想它能工作」 的普通用戶,而不是系統程序員和內核工程師,後者應該能夠閱讀內核文檔和源代碼,以了解不同版本之間發生了什麼變化。下圖展示了這個區別。
普通用戶不太可能與 Linux ABI 的不穩定部分進行交互,但系統程序員可能無意中這樣做。除了 /sys/kernel/debug
以外,sysfs(/sys
)和 procfs(/proc
)的所有部分都是穩定的。
那麼其他對用戶空間可見的二進位介面如何呢,包括 /dev
中的設備文件、內核日誌文件(可通過 dmesg
命令讀取)、文件系統元數據或在內核的 「命令行」 中提供的 「引導參數」(在引導載入程序如 GRUB 或 u-boot 中可見)呢?當然,「這要視情況而定」。
掛載舊文件系統
除了 Linux 系統在引導過程中出現掛起之外,文件系統無法掛載是最令人失望的事情。如果文件系統位於付費客戶的固態硬碟上,那麼問題確實十分嚴重。當內核升級時,一個能夠在舊內核版本下掛載的 Linux 文件系統應該仍然能夠掛載,對嗎?實際上,「這要視情況而定」。
在 2020 年,一位受到傷害的 Linux 開發人員在內核的郵件列表上 抱怨道:
內核已經接受這個作為一個有效的可掛載文件系統格式,沒有任何錯誤或任何類型的警告,而且已經這樣穩定地工作了多年……我一直普遍地以為,掛載現有的根文件系統屬於內核<->用戶空間或內核<->現有系統邊界的範圍,由內核接受並被現有用戶空間成功使用的內容所定義,升級內核應該與現有用戶空間和系統兼容。
但是有一個問題:這些無法掛載的文件系統是使用一種依賴於內核定義,但並未被內核使用的標誌的專有工具創建的。該標誌未出現在 Linux 的 API 頭文件或 procfs/sysfs 中,而是一種 實現細節。因此,在用戶空間代碼中解釋該標誌意味著依賴於「未定義行為」,這是個幾乎會讓每個軟體開發人員都感到戰慄的短語。當內核社區改進其內部測試並開始進行新的一致性檢查時,「man 2 mount」 系統調用突然開始拒絕具有專有格式的文件系統。由於該格式的創建者明確是一位軟體開發人員,因此他未能得到內核文件系統維護者的同情。
線程化內核的 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 程序而不是設備驅動程序補丁,對廣泛的人機介面設備(如滑鼠和鍵盤)提供支持。
隨後進行了一場激烈的討論,但這個問題顯然在 Torvalds 在開源峰會上的評論 中得到解決:
他指出,如果你破壞了「普通(非內核開發人員)用戶使用的真實用戶空間工具」,那麼你需要修復它,無論是否使用了 eBPF。
一致意見似乎正在形成,即希望其 BPF 程序在內核更新後仍能正常工作的開發人員 將需要將其提交到內核源代碼庫中一個尚未指定的位置。敬請關注後繼發展,以了解內核社區對於 BPF 和 ABI 穩定性將採取什麼樣的政策。
結論
內核的 ABI 穩定性保證適用於 procfs、sysfs 和系統調用介面,但也存在重要的例外情況。當內核變更破壞了「樹內」代碼或用戶空間應用程序時,通常會迅速回滾有問題的補丁。對於依賴內核實現細節的專有代碼,儘管這些細節可以從用戶空間訪問,但它並沒有受到保護,並且在出現問題時得到的同情有限。當像 Y2038 這樣的問題無法避免 ABI 破壞時,會以儘可能慎重和系統化的方式進行過渡。而像 BPF 程序這樣的新功能提出了關於 ABI 穩定性邊界的尚未解答的問題。
致謝
感謝 Akkana Peck、Sarah R. Newman 和 Luke S. Crawford 對早期版本材料的有益評論。
(題圖:MJ/da788385-ca24-4be5-bc27-ad7e7ef75973)
via: https://opensource.com/article/22/12/linux-abi
作者:Alison Chaiken 選題:lkxed 譯者:ChatGPT 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive