LWN: Rust 中對(duì)于Linux kernel有用的關(guān)鍵特性!
關(guān)注了就能看到更多這么棒的文章哦~
Key Rust concepts for the kernel
By Jonathan Corbet
September 17, 2021
Kangrejos
DeepL assisted translation
https://lwn.net/Articles/869317/
Kangrejos online 會(huì)議的第一天主要是介紹為了將 Rust 編程語(yǔ)言引入 Linux 內(nèi)核而做的那些工作。第二天,會(huì)議組織者 Miguel Ojeda 轉(zhuǎn)向了介紹 Rust 語(yǔ)言本身,強(qiáng)調(diào) Rust 可以為內(nèi)核開(kāi)發(fā)而提供什么。會(huì)議最終為那些對(duì)這個(gè)項(xiàng)目感到好奇,但還沒(méi)有時(shí)間熟悉 Rust 的人提供了一些有用的信息。
Ojeda 一開(kāi)始就強(qiáng)調(diào),這個(gè)講座并不算是教程。要真正學(xué)習(xí)這門(mén)語(yǔ)言的話,應(yīng)該去找一本好書(shū),然后書(shū)中開(kāi)始學(xué)習(xí)。在一個(gè)小時(shí)的演講中不可能涵蓋所有必需內(nèi)容(隨著時(shí)間的推移,也證明了確實(shí)如此),但他希望能夠展示 Rust 背后的一些關(guān)鍵想法。但最終,真正理解這種語(yǔ)言的唯一方法仍是坐下來(lái)寫(xiě)一些代碼。
Reading material
對(duì)于想要學(xué)習(xí) Rust 的開(kāi)發(fā)者,有很多資源可以利用,有一些書(shū)籍可用。最權(quán)威的書(shū)似乎是《Rust Programming Language》,可以在網(wǎng)上免費(fèi)獲得。他認(rèn)為這本書(shū)對(duì) Rust 進(jìn)行了很好的介紹,同時(shí)也可以作為 Rust 項(xiàng)目中的文檔風(fēng)格的一個(gè)展示。對(duì)內(nèi)核開(kāi)發(fā)特別感興趣的開(kāi)發(fā)者不需要讀完這本書(shū)。比如其中關(guān)于并發(fā)(concurrency)的許多討論并不適用于內(nèi)核環(huán)境。
他說(shuō),《Programming Rust》也相當(dāng)不錯(cuò)。它是針對(duì)有經(jīng)驗(yàn)的程序員的,因此可能對(duì)內(nèi)核開(kāi)發(fā)者有用。不過(guò),這本書(shū)是他給出的書(shū)單上唯一不是免費(fèi)的資源。對(duì)于那些想直接進(jìn)來(lái)嘗試的讀者,《Rust by Example》給出了一系列的練習(xí),每一個(gè)練習(xí)里都介紹了一個(gè)新的語(yǔ)言特性。
然后,還有《The Rustonomicon》。這本書(shū)并不完整,它著重關(guān)注了編寫(xiě) unsafe Rust 會(huì)面臨的挑戰(zhàn)。如果 Rust-for-Linux 項(xiàng)目的目標(biāo)最終能夠完全實(shí)現(xiàn),那么驅(qū)動(dòng)程序開(kāi)發(fā)者就不會(huì)需要編寫(xiě) unsafe Rust,這樣的話可能就不需要讀這本書(shū)了。此書(shū)中有很多具體細(xì)節(jié)以及底層信息,但它也證明了一個(gè)關(guān)鍵觀點(diǎn):大多數(shù) Rust 開(kāi)發(fā)者都不應(yīng)該涉及到這么底層的 Rust 應(yīng)用。
最后,《The Rust Reference》這本書(shū)也不是一個(gè)完整的手冊(cè),而是一本為那些 "喜歡閱讀語(yǔ)言標(biāo)準(zhǔn)(language standard)的人" 所準(zhǔn)備的非常棒的書(shū)籍。大多數(shù) Rust 開(kāi)發(fā)者都不會(huì)需要讀這本書(shū)。Ojeda 在這些書(shū)籍介紹之后也進(jìn)行了總結(jié),他建議大多數(shù)開(kāi)發(fā)者使用《The Rust Programming Language》或《Programming Rust》就好。
當(dāng)然,Rust 項(xiàng)目也提供了很多其他形式的文檔。Ojeda 說(shuō),對(duì)于 C 語(yǔ)言來(lái)說(shuō),C 語(yǔ)言的標(biāo)準(zhǔn)就是其文檔,但 Rust 則不同。到處都是文檔,其中包含了非常多的解釋以及例子,而且經(jīng)常是直接從代碼中生成的。在 Linux kernel 中添加 Rust 的這個(gè)工作中產(chǎn)生的文檔也采用了類似的方法,不過(guò)是用 rustdoc 來(lái)編寫(xiě)的,而不是使用內(nèi)核里的 Sphinx 文檔系統(tǒng)。如何整合這兩套文檔,目前仍然沒(méi)有確定。
Tools
Ojeda 接著介紹了一些工具,在查看 Rust 代碼以及它編譯生成的機(jī)器代碼時(shí)很有用。其中之一是 Rust playground,這個(gè)工具可以用來(lái)編譯代碼、運(yùn)行 linter 檢查、查看匯編代碼等等。

他本人更喜歡使用 Compiler Explorer。這個(gè)工具同樣提供了上述那些功能,但不僅僅限于 Rust。例如,它可以用來(lái)比較不同語(yǔ)言所產(chǎn)生的匯編代碼,針對(duì)這個(gè)場(chǎng)合還提供了一個(gè)漂亮的框圖顯示界面。Compiler Explorer 也可以執(zhí)行相關(guān)代碼并顯示結(jié)果。
The Language
雖然有些人把 Rust 描述為 "a safer C",但 Ojeda 并不喜歡這個(gè)說(shuō)法。這更像是一個(gè)具有更安全的類型系統(tǒng)(safer type system)的 C 語(yǔ)言新版本,這跟 safety-critical (把安全作為重心)是不一樣的。他說(shuō),Rust 也可以被描述為一個(gè) "cleaned-up C"。他展示了三種語(yǔ)言中類似的代碼片段來(lái)說(shuō)明這一點(diǎn):
map(v, [](const auto & x) { return x.get(); }); // C++
map(v, lambda x: x.get()) # Python
map(v, |x| x.get()); // Rust
他說(shuō),Rust 版本更干凈、更容易。C++ 可能包含了很多有用的功能,但開(kāi)發(fā)者往往害怕使用那些功能。Rust 讓這一切變得更容易了。
他花了一些時(shí)間來(lái)討論 Rust 中的 "safety" 這個(gè)概念,這部分與第一天的討論有不少重復(fù)的內(nèi)容。大家所說(shuō)的 Rust 是 "safe" 的,在于它沒(méi)有那些 undefined behavior,也就是未定義的行為;也不存在那些 "編譯器可以隨意做各種瘋狂的動(dòng)作" 的情況。許多人們不希望出現(xiàn)的情況下的行為,包括 kernel panic、內(nèi)存泄漏和整數(shù)的溢出等等,都被很好地定義了出來(lái),從這一點(diǎn)來(lái)說(shuō) Rust 是 safe 的(不過(guò)編譯器還是可以檢查溢出的)。當(dāng)然,邏輯錯(cuò)誤也是 "safe" 的,語(yǔ)言并不能保證程序會(huì)按照開(kāi)發(fā)者的意圖運(yùn)行。
但是許多其他類型的問(wèn)題都可以通過(guò) safe Rust 代碼來(lái)避免。比如下面 Ojeda 展示了一個(gè)簡(jiǎn)單的 C 函數(shù)來(lái)說(shuō)明一類 undefined behavior:
int f(int a, int b) {
return a/b;
}
這個(gè)函數(shù)可能會(huì)出現(xiàn)兩種方式的未定義行為。最明顯的就是調(diào)用者給 b 傳遞了 0 這種情況,此時(shí)編譯器可以按自己的喜好來(lái)產(chǎn)生任何不確定的處理方式。另一種未定義的情況是,如果 a 是 INT_MIN,b 是-1。在用二進(jìn)制補(bǔ)碼表示法中,最大的負(fù)整數(shù)在正數(shù)范圍內(nèi)沒(méi)有對(duì)應(yīng)的數(shù)字,所以除法的結(jié)果就不能被表示了。同樣,當(dāng)這種情況發(fā)生時(shí)編譯器就可以按自己的喜好來(lái)實(shí)現(xiàn)后續(xù)處理了。
如果我們用 Rust 來(lái)寫(xiě)一個(gè)類似的函數(shù):
pub fn f(a: i32, b: i32) -> i32 {
a/b
}
Ojeda 使用 Compiler Explorer 來(lái)展示了一下這個(gè)函數(shù)的編譯結(jié)果,其中已經(jīng)帶有了包含了對(duì)上述兩種未定義行為情況的檢測(cè)。如果遇到這兩種情況,程序就會(huì)中止。這可能不是程序員想要的結(jié)果,但繼續(xù)運(yùn)行從而產(chǎn)生未定義行為也并不是一個(gè)好結(jié)果。
此時(shí)大家對(duì)所有這些檢查對(duì)性能帶來(lái)的開(kāi)銷進(jìn)行了一些簡(jiǎn)要討論。這肯定是有開(kāi)銷的,但似乎沒(méi)有人測(cè)量過(guò)在性能非常敏感的情況下的影響到底有多大。Ojeda 指出,最壞的情況下,還是可以把代碼放到 unsafe 的代碼部分中,這樣就可以去除所有這些檢查。在這次會(huì)議上,"unsafe" 經(jīng)常被用來(lái)作為一個(gè) "逃生艙門(mén)把手,escape hatch",每當(dāng)語(yǔ)言的限制變得過(guò)于難以接受時(shí),這種方法 kqyi 去除掉許多限制。
也還有各種技巧可以用來(lái)向編譯器表明某些情況是不可能出現(xiàn)的,這時(shí)它將自動(dòng)省略掉這部分檢查。例如,如果一個(gè)人定義了一個(gè)有界整數(shù)類型(bounded-integer type),編譯器就知道它的值是將在其接受范圍內(nèi),那可能就不需要執(zhí)行溢出檢查了。這種技巧通常會(huì)需要把相應(yīng)的必要檢查放到構(gòu)造函數(shù)中,在那里只需要做一次檢查就好。
Ojeda 還提供了一個(gè)例子,是一個(gè)函數(shù)中的 C 代碼,看起來(lái)像這樣:
int x; /* not initialized */
while (f())
x = 42;
return x;
通過(guò)查看這個(gè)函數(shù)的匯編輸出,可以看到編譯后的代碼只是返回一個(gè)常數(shù) 42。賦值給 x 的循環(huán)體可能從未執(zhí)行過(guò),但在這種情況下引用 x 的話就會(huì)使程序產(chǎn)生未定義行為。編譯器被允許假設(shè)這種情況永遠(yuǎn)不會(huì)發(fā)生。在 C 語(yǔ)言的這種世界觀下,肯定會(huì)被賦值為 42。相反,Rust 會(huì)在這種情況下拋出一個(gè)編譯時(shí)錯(cuò)誤,并強(qiáng)制對(duì)該變量進(jìn)行顯式的初始化。
Laurent Pinchart 指出,無(wú)論使用哪種語(yǔ)言,開(kāi)發(fā)人員都必須訓(xùn)練他們自己的大腦來(lái)避免在開(kāi)發(fā)中產(chǎn)生未定義行為。這里的區(qū)別是,Rust 編譯器會(huì)抓住錯(cuò)誤,而 C 編譯器往往會(huì)什么都不說(shuō)。他說(shuō),在用 Rust 這樣的語(yǔ)言訓(xùn)練你的大腦后,通常也可以幫助你寫(xiě)出更好的 C 語(yǔ)言代碼。
會(huì)議的時(shí)間到此為止,但 Ojeda 又用一系列幻燈片介紹了與內(nèi)核開(kāi)發(fā)者相關(guān)的其他 Rust 語(yǔ)言特性。其中包括不允許訪問(wèn)錯(cuò)誤成員的 union 類型,動(dòng)態(tài)分配對(duì)象的隱式釋放功能,處理有符號(hào)的整數(shù)溢出,避免數(shù)據(jù)競(jìng)態(tài)的機(jī)制,等等。簡(jiǎn)而言之,Rust 中為系統(tǒng)開(kāi)發(fā)者提供了很多東西,遠(yuǎn)遠(yuǎn)超出一個(gè)簡(jiǎn)短的會(huì)議所能涵蓋的內(nèi)容。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長(zhǎng)按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開(kāi)源社區(qū)的各種新近言論~
