無論好壞,C語言已經是內核開發領域的通用語言了。Linux 內核的核心邏輯完全是用 C 語言編寫的(加上一點匯編),它的驅動程序和 module 也是如此。雖然 C 語言因其強大而簡單的語義而受到贊譽,但它是一種古老的語言,缺乏現代語言(如 Rust)中的許多特性。另一方面,BPF 子系統也提供了一個編程環境,工程師能夠編寫可以在內核空間安全運行的程序。在愛爾蘭都柏林舉行的 2022 年 Linux Plumbers Conference 上,Alexei Starovoitov 概述了 BPF 多年來的發展,為內核編程提供了一個新的參考模型。
BPF的使命
Starovoitov 首先描述了他對 BPF 的 "mission statement, 使命宣言":"創新、并啟發大家創新"。內核中的編程歷來是在兩種情況下進行的:
core kernel 開發,包括主要的核心子系統,如內存管理、調度器、read-copy-update,等等。
kernel-module 開發,指的是構建那些不被編譯到 main kernel image 里的內容,由 module loader 在后續加載。例如,驅動程序被寫成一些內核 module,也有其他功能是這么做的,如文件系統、網絡協議等等。
這是內核在很長一段時間內的狀態,直到 3.15 版的內核中加入了最早版本的 extended BPF(eBPF)虛擬機。有了它之后,BPF program 可以用一個受到嚴格限制的 C 語言來編寫,并被編譯成 BPF 字節碼,這將允許用戶編寫的代碼可以經過驗證確保安全,然后再在內核空間運行。
從那時起,BPF 在代碼的規模、用戶及貢獻者社區的規模方面都穩步增長。根據 Starovoitov 的說法,BPF 郵件列表上每天都會收到 50-70 條信息,每月大約收到 2000 封郵件。平均每月里活躍貢獻的 BPF 貢獻者的數量也在同步增長,截至 2022 年 9 月,已達到約 140 人。目前來說,對 BPF 子系統的大部分貢獻都不是來自 Meta BPF 小組了。
BPF編程環境
雖然大多數 BPF 程序是用 C 語言編寫的,并用 LLVM Clang 編譯器編譯,但 BPF program 只是二進制 BPF 字節碼對象文件,并未規定要用某種特定的語言來寫。比如說,BPF 程序可以使用 Aya 來采用 Rust 編寫,甚至可以直接用 BPF 匯編語言編寫。也就是說,C是 BPF 程序的典型(canonical)編程語言;Starovoitov 的演講繼續概述了 BPF program 開發中 C 編程環境是如何演進的。
這個新的編程環境混合使用了 C 語言擴展以及運行時環境的組合實現的,這個運行時環境包含了 Clang、用戶空間的 BPF 加載器庫(libbpf)和內核中的 BPF 子系統。要想創建一個 BPF 程序,用戶只要用 C 語言寫一個程序,由 Clang 的 backend 實現來轉換成 BPF 指令。在運行程序時,libbpf 將 BPF 程序加載到內存中,對程序進行重定位以使其可以跨平臺以及不同的內核版本從而具備良好的可移植性,然后調用 kernel 來加載程序。最后在內核中,verifier 會采用靜態方式驗證該程序是否可以安全運行,然后啟用之。
然而,BPF 的編程環境并不是一上來就這么豐富的。在 BPF 的早期,程序被要求使用 Starovoitov 所說的 "restricted C"。BPF 程序中的所有函數都必須完全是 inline 的,loop 循環、靜態變量和全局變量以及內存分配都是不允許的。也沒有類型信息(type information),所以 BPF 程序只能接收單一的、固定的 input context,用于 tracing 以及 network-filtering 相關功能。
盡管在這樣一個高度限制性的環境中編寫 BPF 程序也是很有用的,但很明顯, BPF 所支持的使用場景還可以得到很大的擴展。其中一個擴展就是允許在 BPF 程序中使用靜態函數。這樣做需要使用 libbpf 在程序加載時對內核 BPF 程序進行重定位。經過多年的設計和嘗試,最終也增加了對有限循環的支持,此外也支持了 iterator。
Extending the programming environment past full C
雖然這些使得 BPF 更接近于完整的 C 語言了,但最終可以看到,BPF 程序需要的一些功能甚至在完整的 C 語言標準中都沒有。于是 BPF 社區開始擴展 BPF 編程環境,從而包括一些傳統 C 語言沒有的新特性。其中一個擴展功能就是 "一次編譯-到處運行"(CO-RE, Compile Once - Run Everywhere)。
CO-RE 使 BPF 程序可以在不同的內核版本和平臺上都可以運行。在 BPF 程序中,訪問內核數據結構是很常見的行為。然而,內核沒有為 struct layer 確保 ABI 不變,因此,如果內核結構在未來的版本或不同的 config 下發生了變化,在固定偏移的地方對內核結構進行讀取的 BPF 程序可能就會讀到錯誤的值。CO-RE 通過利用運行中的內核中的 BPF 類型格式(BTF)數據來解決這個問題。在加載一個程序時,libbpf 對所有的 struct 的訪問都會進行重定位,以便根據當前運行的內核的 BTF 信息讓被訪問的字段的偏移量匹配上。
Starovoitov 還描述了 BPF 編程環境的其他一些有趣的新增功能。其中一個是 kptrs,它允許將內核內存的指針存儲在 BPF map 中。另一個功能是允許程序在加載時訪問內核 config 參數。內核 module 只能使用編譯時設置的 config 值,但 BPF 程序在加載時可以根據當前內核的配置來決定自己的行為。還有一個特點是 "type tags",可以讓程序能對變量進行 annotation,從而描述它們的使用方式。例如,kptrs 可以用 __kptr 和 __kptr_ref type tags 來進行標注,從而表明它們分別是 unreferenced 或者 referenced kptr。當然指針也可以用 __user 或 __percpu 標準,來告訴編譯器和 verifier 這個指針分別指向用戶內存或 per-CPU 內存。
Plans for the future
目前正在設計和實現更多的擴展,包括 lock-correctness 正確性驗證,以及支持 BPF 程序包含 assertion。lock 的驗證乍一看似乎是一個很難解決的問題,而 Dave Marchevsky 和 Kumar Kartikeya Dwivedi 都已經發出了 RFC patch set 來實現用于 lock 驗證的新 map type。Marchevsky 的 patch set 提出了一個新的紅黑樹 map type,而 Dwivedi 的 patch set 提出了一個 list map type。這兩個 patch set 都實現了共同的效果,允許 BPF 程序執行由 verifier 檢查和驗證過的 locking 機制。
assertion 驗證仍處于規劃階段,實現起來可能會很復雜。assertion 將作為給編譯器和 verifier 的信號,assertion 被用來指示程序中的一些不變的因素,這些不變因素的失敗將導致程序中止。Starovoitov 聲稱,弄清如何讓程序中止,這會是一個 "有趣" 的問題,因為它需要安全地對堆棧進行 unwind,調用 kptr destructor,以及其他收尾工作。
Starovoitov 在演講的最后分享了他對 BPF 未來的觀點:會取代內核模塊成為擴展內核的有效方式。早期版本的 BPF 程序看起來更像是帶有固定的 BPF helper function 和固定的 map type 的用戶空間程序,而如今新的 BPF 已經可以讓用戶在更多個性化使用場景下對內核進行擴展。事實上,這樣的使用場景已經在 upstream 社區被提出來了。在 Starovoitov 之后在 LPC 發言的 Benjamin Tissoires,一直在開發一個 patch set,希望用 BPF 程序來 fix 人類輸入設備(HID)的 quirk。到目前為止,還沒有一個內核 module 被 BPF 程序完全取代掉,不過,很期待看到內核的其他一些功能可以在 BPF 程序中實現。
一位聽眾要求了解 Starovoitov 所提到的 lock-correctness 驗證的更多細節。Starovoitov 說,這個工作還在進行當中,但他樂觀地認為可以找到一種方法來進行 static lock checking,從而驗證數據保護是正確的,并保證不會發生死鎖。Dave Miller 回應說,如果鎖可以由 verifier 進行靜態檢查,那么可能可以研究一下 locking 邏輯是否可以由 verifier 自動生成。Starovoitov 回答說,這就是他們希望實現的目標,目前的設計中將 lock 和受保護的數據在同一次 allocation 中放在一起。對于不能跟 lock 放在一起的數據,可以用 BTF type tag 來指定它需要明確進行鎖保護。
審核編輯 :李倩
-
C語言
+關注
關注
180文章
7608瀏覽量
137156 -
編程
+關注
關注
88文章
3628瀏覽量
93818 -
驅動程序
+關注
關注
19文章
839瀏覽量
48098 -
BPF
+關注
關注
0文章
25瀏覽量
4016
原文標題:LWN:讓BPF成為一個更安全的內核編程環境!
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論