LWN: kernel 快要有 Rust 了?
關(guān)注了就能看到更多這么棒的文章哦~
Rust heads into the kernel?
By Jake Edge
April 21, 2021
DeepL assisted translation
https://lwn.net/Articles/853423/
在給 linux-kernel 郵件列表的一封長長的郵件中,Miguel Ojeda "介紹" 了 Rust for Linux 這個項目。很可能大多數(shù)內(nèi)核開發(fā)者之前已經(jīng)聽說過這項工作了。例如 2020 年的 Linux Plumbers 會議上就對該項目進行了廣泛的討論。以前也在列表中被提出來過。現(xiàn)在,該項目正在詢問內(nèi)核社區(qū)對其計劃有些什么反饋,因此在 4 月 14 日的時候發(fā)布了 RFC。
Adding Rust
Ojeda 首先很老實地表態(tài)說,他也理解在內(nèi)核中新增另一種語言總會有一定的破壞性,所以需要有很好的理由才會這樣做。內(nèi)核已經(jīng)是一個高度復(fù)雜的代碼綜合產(chǎn)物,對大家理解代碼和修改代碼都增加了不少困難。要在其中加入第二種語言以及其相關(guān)的復(fù)雜問題,只會使情況變得更糟。"盡管如此,我們相信,即使就在眼下,如果我們使用 Rust 的話,優(yōu)勢也是超過成本的。"
這些好處主要來自于 Rust 語言的內(nèi)存安全(memory-safety)特性。我們希望通過避免這些問題來減少內(nèi)核中的 bug 數(shù)量(至少是那些用 Rust 語言實現(xiàn)的部分代碼)。目前,對 Rust 實現(xiàn)的代碼的定位仍是遠離核心內(nèi)核(core kernel)代碼:
請注意,至少在可預(yù)見的未來,支持 Rust 的目的是讓人們能夠用 Rust 編寫驅(qū)動和類似的 "leaf" 模塊。具體來說,我們不打算重寫內(nèi)核核心或那些主要的子系統(tǒng)(例如,`kernel/`,`mm/`,`sched/`…)。相反,Rust 的支持是會建立在這些基礎(chǔ)之上。
內(nèi)核 C 代碼和 Rust 之間有很多不匹配的地方,需要一些方式來處理好。為了從 Rust 的安全特性中獲得最大的好處,那就需要盡量減少不安全的部分中的起支持作用的內(nèi)核代碼的數(shù)量,并仔細記錄在文檔中。其中一個既定的目標(biāo)就是要能采用自動機制來確保執(zhí)行文檔記錄原則:
通過利用 Rust 現(xiàn)有工具,我們在這個項目中一直在確保執(zhí)行迄今為止建立好的所有文檔準(zhǔn)則。例如,我們要求對所有公共 API、安全前提條件(safety preconditions)、"unsafe" block 和類型不變性(type invariants) 進行記錄。
到目前為止,這些工作主要集中在實現(xiàn)底層組件(building blocks)和開始對內(nèi)核 API 進行抽象和封裝,但還有很多工作需要去做:"要想覆蓋整個內(nèi)核的 API 接口,這會需要很長的時間來開發(fā)穩(wěn)定。" RFC 中重申了這個任務(wù),但同時也指出有一些工具可以對這個過程有所幫助:
[……]用 Rust 編寫的模塊不應(yīng)該直接使用 C kernel API。在內(nèi)核中使用 Rust 的根本意義就是我們要開發(fā)出安全的抽象層,使這些 module 更加容易模塊更容易推理,因此,審查、重構(gòu)等等。
此外,內(nèi)核的 C 這一側(cè)的綁定(binding)是通過`bindgen`(官方的 Rust 工具)即時(on-the-fly)生成的。我們使用這個工具,就可以避免在 Rust 這一側(cè)更新 binding。
在谷歌安全博客(Google security blog)的一篇文章中(這篇文章在 LWN 發(fā)布時引起了大量的評論),Linux 的 Rust 維護者之一 Wedson Almeida Filho 詳細描述了 Ojeda 的 RFC 中一個示例驅(qū)動程序。這是一個實現(xiàn) semaphore 的字符設(shè)備,主要用于演示的目的。RFC 還包含了一個重新實現(xiàn)過 Android Binder 進程間通信機制,盡管這個尚未完成,但它讓我們進一步了解了在內(nèi)核中使用 Rust 可能會有哪些用處:
目前,我們幾乎將 Binder 所需的所有那些通用內(nèi)核功能都干凈利落地封裝在安全的 Rust 抽象中了[…] 。
我們還將繼續(xù)推進修改 Binder 這個原型,來實現(xiàn)更多的抽象,并處理好一些目前處理得比較粗糙的地方。這是一個激動人心的時刻,也是一個難得的機會,很可能會影響到 Linux 內(nèi)核的開發(fā),同時也可以有利于 Rust 語言的發(fā)展。
RFC 指出,目前的 Rust 支持代碼在內(nèi)核已經(jīng)實現(xiàn)了相當(dāng)多的代碼,但隨著時間的推移,會逐步縮減這些改動。在完全支持 Rust 的情況下,"我們在 CI 中使用的小型 x86_64 環(huán)境" 的內(nèi)核 size 增加了大約 4%。Rust 版本的 semaphore 驅(qū)動比 C 版本的驅(qū)動大 50%左右,而 Binder 驅(qū)動的 size 基本上保持相當(dāng)。然而,"請注意,雖然 Rust 版本尚未完全實現(xiàn) C 語言的原始模塊中的全部功能,但已經(jīng)足夠接近了,可以作為粗略的估計之用"。
Reaction
總的來說,人們對 RFC 的反響還是不錯的,盡管有一些例外。當(dāng)然對現(xiàn)有代碼也有一些疑問和擔(dān)憂。Linus Torvalds 似乎專注于這些代碼中調(diào)用的 BUG(),很明顯,他看到這些時感到很不贊同。內(nèi)核哪怕面對 error 時會努力繼續(xù)往下走,但碰到 BUG() 調(diào)用的話就會直接放棄,并且打印出 backtrace 以及觸發(fā) kernel crash。其中有一個例子里,Rust 標(biāo)準(zhǔn)庫中的一些 intrinsic 操作("panicking intrinsics")是內(nèi)核所不支持的,只要調(diào)用這些操作,就可以通過觸發(fā) BUG() 從而馬上讓內(nèi)核崩潰。Torvalds 建議在編譯的時候就讓這些調(diào)用直接報錯,Ojeda 同意這是一個更好的方法,但也指出,隨著時間的推移,更多的標(biāo)準(zhǔn)庫(standard library)會被刪除,很可能這些問題也會被消滅。
Torvalds 還指出了內(nèi)存分配代碼中的 panic!()調(diào)用,這在他看來是 "根本性的錯誤":
如果 Rust 編譯器偷偷進行了分配(allocation),然后會出現(xiàn) panic,那么我們加入 Rust 的一個主要好處就不復(fù)存在了。這跟 build 時候的 memory-safe 要求是背道而馳的。
隨便在哪個驅(qū)動程序中,分配內(nèi)存失敗絕不可以是編譯器觸發(fā) panic 的理由。它必須被捕捉下來并且進行同步處理(handled synchronously),返回一個 ENOMEM 錯誤。
Ojeda 再次表示同意。他指出,要調(diào)整好 Rust 的標(biāo)準(zhǔn)庫供內(nèi)核使用,還有很多工作要做:
這里的情況是,我們暫時使用 'alloc',它是 Rust 標(biāo)準(zhǔn)庫的一部分。然而,我們后續(xù)會根據(jù)需要定制、重寫 'alloc',從而指定其類型(如'Box'、'Vec'等),這樣我們就可以用來做一些其他工作,比如傳遞一個 allocation flag,確保我們總是可以支持這些會出錯的 allocation,或者可以重用一些內(nèi)核數(shù)據(jù)結(jié)構(gòu),等等。
用 Binder 驅(qū)動作為例子,這是 Torvalds 比較關(guān)心的另一點。他希望看到一個 "真正的代碼的例子,是真正在做一些有意義的事"。Ojeda 說,計劃會增加一些真正同硬件交互的驅(qū)動程序。Matthew Wilcox 有一個想法,可以從哪里開始:
我建議把 NVMe 作為一個目標(biāo)。它是現(xiàn)成的,真實硬件和 qemu 等虛擬環(huán)境中都有支持。它的 spec 也是免費提供的,而且大多數(shù)設(shè)備的實現(xiàn)都非常接近 spec,除非你要追求一些細節(jié)。此外,你還可以做性能測試,看看可以對于性能在哪些方面來集中優(yōu)化。
Greg Kroah-Hartman 同意 Torvalds 的觀點,認為 Binder 并不是一個特別好的驅(qū)動實例,但他對該項目到目前為止所取得的成就印象深刻:
[……]這組 patch set 是一個偉大的開始,它提供了 "如何在內(nèi)核構(gòu)建系統(tǒng)中構(gòu)建 rust" 的最主要部分的展示,這是一項非同小可的工程。向他們致敬,我所要做的工作就是僅僅在我的系統(tǒng)上成功安裝合適的 rust 編譯器(這不是這些開發(fā)者的錯),然后就成功地編譯了內(nèi)核代碼。這是一個重大的成就。
他還認為 NVMe 可能是一個不錯的選擇,但也建議試試 "驅(qū)動作者每天都要處理的一些基本問題(platform driver、gpio driver、pcspkr driver、替換/dev/zero 等)" 。看來 Rust for Linux 項目將在不久之后致力于開發(fā)一些符合這種期望的 "真正的" 驅(qū)動程序。
雖然 Torvalds 重申了他對一些個別 patch 的抱怨,但他也說。"總的來說,我并不討厭這組 patch"。但另一方面,Peter Zijlstra 似乎從根本上反對在內(nèi)核中增加第二種實現(xiàn)語言的想法。RFC 指出,內(nèi)核工具一直專注于 C 語言,"包括 compiler plugins、sanitizers、Coccinelle、lockdep、sparse",但是 "如果 Rust 在內(nèi)核中的使用隨著時間的推移而增加,Rust 相關(guān)的工具可能會得到改善"。Zijlstra 對此進行了歸納,并問道:
在重構(gòu)的時候,我們可以不受限制地移除這些 .rs 內(nèi)容嗎?如果我們不能在沒有 Rust 這堆東西的情況下啟動 x86_64,那么情況會怎樣?
我們可以把這個問題當(dāng)作未來的問題來暫時忽略,但我認為現(xiàn)在討論這個問題才是合理的。我真的不關(guān)心未來,而且在我看來,在內(nèi)核中加入 Rust 或其他任何第二語言都會失敗。
也許不出意料,他對項目使用的文檔格式(代碼中的 Markdown 轉(zhuǎn)換為 HTML)強烈反對。他對所使用的代碼格式也不滿意,根據(jù) RFC 的說法,至少目前還是遵循 "Rust 的習(xí)慣性風(fēng)格",但 "真的非常非常難讀"。除了這些,他還想知道 Rust 遵循什么內(nèi)存模型,以及它與 Linux 內(nèi)核內(nèi)存模型(LKMM,Linux kernel memory model)如何 "保持一致(或干脆就不一致?)"。
Memory model
Boqun Feng 說,Rust 目前使用 C11 內(nèi)存模型,主要是因為 LLVM 編譯器默認支持它,但人們很想知道其內(nèi)存模型與內(nèi)核的內(nèi)存模型是否能很好配合。現(xiàn)在,"在 C 這一側(cè)和 Rust 這一側(cè)之間沒有需要保持同步的代碼,所以我們目前沒有問題",但這個情況最終會發(fā)生變化的,所以有計劃讓 Rust 和內(nèi)核中相關(guān)的人員一起討論這個問題。Almeida 指出,最終希望是讓內(nèi)核中的大多數(shù) Rust 代碼只需要關(guān)注 Rust 的內(nèi)存模型:
我們不打算將 C 語言的數(shù)據(jù)結(jié)構(gòu)直接暴露給那些(在 kernel crate 之外的) Rust 代碼。相反,我們打算提供封裝函數(shù),對外暴露安全接口,即使底層的實現(xiàn)可能會使用了 unsafe block。所以我們希望絕大多數(shù)的 Rust 代碼只關(guān)心 Rust 的內(nèi)存模型。
我們承認還沒有實現(xiàn)很多封裝函數(shù),但我們目前的已經(jīng)足夠?qū)崿F(xiàn) Binder 的主要功能了,到目前為止還不錯。我們確實打算最終也能夠處理其他類別的驅(qū)動,這些驅(qū)動可能會向我們展示出一些此前未預(yù)見到的困難,我們慢慢來吧。
Almeida 不同意 Zijlstra 關(guān)于 HTML 是一種無效的文檔格式的說法,他認為這是一種個人偏好。對于代碼格式,如果有很好的理由,他不反對脫離 Rust 風(fēng)格,但認為 Zijlstra 的批評沒有說服力。" '在 if-clause 表達式周圍沒有小括號完全是垃圾' 這種說法對我來說聽起來并不是一個好的理由。"
Al Viro 試圖解釋對 HTML 文檔的厭惡,語氣非常的直接,這使?fàn)幷摃簳r偏離了軌道。Zijlstra 說,沒有好方法來使用 ASCII 閱讀 HTML 文檔:"沒有什么是一個合理的 ASCII 文檔不能完成的,有必要的話可以再加上一些 ASCII art 圖。" 他還解釋了為什么他對格式的看似隨意的抱怨實際上很重要:
當(dāng)然有關(guān)系;我大腦里的詞法分析一直在對我大喊 '這里有語法錯誤',當(dāng)我不能理智地閱讀代碼時,我怎么能理解它呢?
你越是讓它看起來像(內(nèi)核里的)C 代碼,我們這些 C 語言的人員就越容易真正讀懂它。我的眼睛已經(jīng)讀了近 30 年的 C 語言了,就好象內(nèi)置在視神經(jīng)中有個相應(yīng)的詞法分析器,閱讀那些看起來隱約像 C 語言但絕對不是 C 語言的東西是一種痛苦的經(jīng)歷。
你是在要求加入我們,而不是反過來。我在沒有 Rust 的世界里過得很好。
Zijlstra 還提出,許多被吹捧的 Rust 功能都可以在 C 語言中實現(xiàn)。Almeida 同意確實是可以實現(xiàn)的,但 Rust 的價值是人們不可能錯誤地忽略這些功能,不像 C 語言(至少在不改變編譯器的情況下)那樣:
在 Rust 中,這是不可能的:受鎖保護的數(shù)據(jù)只有在獲取鎖之后才能被訪問。所以開發(fā)者不可能意外地犯這樣的錯誤。而且這是在編譯時確保的,因此在運行時沒有額外開銷。
他還提出了 C 語言中的 ownership 問題:在 C 語言中沒有辦法轉(zhuǎn)移一個對象的 ownership,但在 Rust 中卻可以直接做到:
在 Rust 中,有一種干凈的現(xiàn)成方式來轉(zhuǎn)交一個 guard(或任何什么 object)的 ownership,這樣在所有權(quán)轉(zhuǎn)移后,以前的所有者就不能繼續(xù)使用它了。同樣,這也是在編譯時確保的。
但是 Zijlstra 寧愿看到一個支持所有權(quán)的 C extension,而不是將 Rust 添加到內(nèi)核中:
這就意味著我們要比以前更積極地推動新的 C 語言編譯器,但至少我們?nèi)允鞘褂脝我灰环N語言。對新東西的轉(zhuǎn)換可以逐步進行,并且在有意義的地方進行,新的 extension 可以針對性地評估性能影響。
Almeida 并不反對這個想法,事實上,恰恰相反:
我鼓勵你繼續(xù)這樣做。我們都會從更好的 C 語言中受益。我很樂意對那些被認為等同于或者優(yōu)于 Rust 的 extension 進行 review 并提供反饋。
我的背景也是 C 語言。我不是 Rust 的粉絲,我只是以我認為的實用主義的觀點來看待現(xiàn)有的選項。
然而,有點難以想象內(nèi)核會因為一個尚不存在的 C extension 而停止繼續(xù)調(diào)查是否能將 Rust 加入內(nèi)核中。但是還有很多工作需要 Rust for Linux 來完成,其中一些工作很可能需要先完成,之后 Torvalds 才會愿意 merge。例如,似乎需要更多的 "real" driver 實例,以及刪除那些導(dǎo)致 BUG()調(diào)用的代碼。但是,Rust for Linux 顯然越來越接近于成為現(xiàn)實。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
