編者按:近些年來 Rust 語言由于其內(nèi)存安全性和性能等優(yōu)勢得到了很多關(guān)注,尤其是 Linux 內(nèi)核也在準(zhǔn)備將其集成到其中,因此,我們特邀阿里云工程師蘇子彬為我們介紹一下如何在 Linux 內(nèi)核中集成 Rust 支持。
2021 年 4 月 14 號,一封主題名為《Rust support[1]》的郵件出現(xiàn)在 LKML 郵件組中。這封郵件主要介紹了向內(nèi)核引入 Rust 語言支持的一些看法以及所做的工作。郵件的發(fā)送者是 Miguel Ojeda[2],為內(nèi)核中 Compiler attributes、.clang-format 等多個模塊的維護者,也是目前 Rust for Linux 項目的維護者。
Rust for Linux 項目目前得到了 Google 的大力支持[3],Miguel Ojeda[4] 當(dāng)前的全職工作就是負(fù)責(zé) Rust for Linux 項目。
長期以來,內(nèi)核使用 C 語言和匯編語言作為主要的開發(fā)語言,部分輔助語言包括 Python、Perl、shell 被用來進行代碼生成、打補丁、檢查等工作。2016 年 Linux 25 歲生日時,在對 Linus Torvalds 的一篇 采訪[5]中,他就曾表示過:
這根本不是一個新現(xiàn)象。我們有過使用 Modula-2 或 Ada 的系統(tǒng)人員,我不得不說 Rust 看起來比這兩個災(zāi)難要好得多。
我對 Rust 用于操作系統(tǒng)內(nèi)核并不信服(雖然系統(tǒng)編程不僅限于內(nèi)核),但同時,毫無疑問,C 有很多局限性。
在最新的對 Rust support[6] 的 RFC 郵件的回復(fù)中,他更是說:
所以我對幾個個別補丁做了回應(yīng),但總體上我不討厭它。
沒有用他特有的回復(fù)方式來反擊,應(yīng)該就是暗自喜歡了吧。
目前 Rust for Linux 依然是一個獨立于上游的項目,并且主要工作還集中的驅(qū)動接口相關(guān)的開發(fā)上,并非一個完善的項目。
項目地址:https://github.com/Rust-for-Linux/linux
為什么是 Rust
在 Miguel Ojeda[7] 的第一個 RFC 郵件中,他已經(jīng)提到了 “Why Rust”,簡單總結(jié)下:
在安全子集safe subset中不存在未定義行為,包括內(nèi)存安全和數(shù)據(jù)競爭;
更加嚴(yán)格的類型檢測系統(tǒng)能夠進一步減少邏輯錯誤;
明確區(qū)分 safe 和 unsafe 代碼;
更加面向未來的語言:sum 類型、模式匹配、泛型、RAII、生命周期、共享及專屬引用、模塊與可見性等等;
可擴展的獨立標(biāo)準(zhǔn)庫;
集成的開箱可用工具:文檔生成、代碼格式化、linter 等,這些都基于編譯器本身。
編譯支持 Rust 的內(nèi)核
根據(jù) Rust for Linux 文檔[8],編譯一個包含 Rust 支持的內(nèi)核需要如下步驟:
安裝 rustc 編譯器。Rust for Linux 不依賴 cargo,但需要最新的 beta 版本的 rustc。使用 rustup命令安裝:
rustup default beta-2021-06-23
安裝 Rust 標(biāo)準(zhǔn)庫的源碼。Rust for Linux 會交叉編譯 Rust 的 core 庫,并將這兩個庫鏈接進內(nèi)核鏡像。
rustup component add rust-src
安裝 libclang 庫。libclang 被 bindgen 用做前端,用來處理 C 代碼。libclang 可以從 llvm 官方主頁[9] 下載預(yù)編譯好的版本。
安裝 bindgen 工具,bindgen 是一個自動將 C 接口轉(zhuǎn)為 RustFFI 接口的庫:
cargo install --locked --version 0.56.0 bindgen
克隆最新的 Rust for Linux 代碼:
git clone https://github.com/Rust-for-Linux/linux.git
配置內(nèi)核啟用 Rust 支持:
Kernel hacking -》 Sample kernel code -》 Rust samples
構(gòu)建:
LIBCLANG_PATH=/path/to/libclang make -j LLVM=1 bzImage
這里我們使用 clang 作為默認(rèn)的內(nèi)核編譯器,使用 gcc 理論上是可以的,但還處于 早期實驗[10] 階段。
Rust 是如何集成進內(nèi)核的
目錄結(jié)構(gòu)
為了將 Rust 集成進內(nèi)核中,開發(fā)者首先對 Kbuild 系統(tǒng)進行修改,加入了相關(guān)配置項來開啟/關(guān)閉 Rust 的支持。
此外,為了編譯 rs 文件,添加了一些 Makefile 的規(guī)則。這些修改分散在內(nèi)核目錄中的不同文件里。
Rust 生成的目標(biāo)代碼中的符號會因為 Mangling 導(dǎo)致其長度超過同樣的 C 程序所生成符號的長度,因此,需要對內(nèi)核的符號長度相關(guān)的邏輯進行補丁。開發(fā)者引入了 “大內(nèi)核符號”的概念,用來在保證向前兼容的情況下,支持 Rust 生成的目標(biāo)文件符號長度。
其他 Rust 相關(guān)的代碼都被放置在了 rust 目錄下。
在 Rust 中使用 C 函數(shù)
Rust 提供 FFI(外部函數(shù)接口Foreign Function Interface)用來支持對 C 代碼的調(diào)用。Bindgen[11] 是一個 Rust 官方的工具,用來自動化地從 C 函數(shù)中生成 Rust 的 FFI 綁定。內(nèi)核中的 Rust 也使用該工具從原生的內(nèi)核 C 接口中生成 Rust 的 FFI 綁定。
quiet_cmd_bindgen = BINDGEN $@ cmd_bindgen = $(BINDGEN) $《 $(shell grep -v ‘^#|^$$’ $(srctree)/rust/bindgen_parameters) --use-core --with-derive-default --ctypes-prefix c_types --no-debug ‘.*’ --size_t-is-usize -o $@ -- $(bindgen_c_flags_final) -DMODULE$(objtree)/rust/bindings_generated.rs: $(srctree)/rust/kernel/bindings_helper.h $(srctree)/rust/bindgen_parameters FORCE $(call if_changed_dep,bindgen)
ABI
Rust 相關(guān)的代碼會單獨從 rs 編譯為 .o,生成的目標(biāo)文件是標(biāo)準(zhǔn)的 ELF 文件。在鏈接階段,內(nèi)核的鏈接器將 Rust 生成的目標(biāo)文件與其他 C 程序生成的目標(biāo)文件一起鏈接為內(nèi)核鏡像文件。因此,只要 Rust 生成的目標(biāo)文件 ABI 與 C 程序的一致,就可以無差別的被鏈接(當(dāng)然,被引用的符號還是要存在的)。
Rust 的 alloc 與 core 庫
目前 Rust for Linux 依賴于 core 庫。在 core 中定義了基本的 Rust 數(shù)據(jù)結(jié)構(gòu)與語言特性,例如熟悉的 Option《》 和 Result《》 就是 core 庫所提供。
這個庫被交叉編譯后被直接鏈接進內(nèi)核鏡像文件,這也是導(dǎo)致啟用 Rust 的內(nèi)核鏡像文件尺寸較大的原因。在未來的工作中,這兩個庫會被進一步被優(yōu)化,去除掉某些無用的部分,例如浮點操作,Unicode 相關(guān)的內(nèi)容,F(xiàn)utures 相關(guān)的功能等。
之前的 Rust for Linux 項目還依賴于 Rust 的 alloc 庫。Rust for Linux 定義了自己的 GlobalAlloc 用來管理基本的堆內(nèi)存分配。主要被用來進行堆內(nèi)存分配,并且使用 GFP_KERNEL 標(biāo)識作為默認(rèn)的內(nèi)存分配模式。
不過在在最新的 拉取請求[12] 中,社區(qū)已經(jīng)將移植并修改了 Rust的 alloc 庫,使其能夠在盡量保證與 Rust 上游統(tǒng)一的情況下,允許開發(fā)者定制自己的內(nèi)存分配器。不過目前使用自定義的 GFP_ 標(biāo)識來分配內(nèi)存依然是不支持的,但好消息是這個功能正在開發(fā)中。
“Hello World” 內(nèi)核模塊
用一個簡單的 Hello World 來展示如何使用 Rust 語言編寫驅(qū)動代碼,hello_world.rs:
#?。踤o_std]#?。踗eature(allocator_api, global_asm)]use kernel::*;module! { type: HelloWorld, name: b“hello_world”, author: b“d0u9”, description: b“A simple hello world example”, license: b“GPL v2”,}struct HelloWorld;impl KernelModule for HelloWorld { fn init() -》 Result《Self》 { pr_info?。ā癏ello world from rust!
”); Ok(HelloWorld) }}impl Drop for HelloWorld { fn drop(&mut self) { pr_info?。ā癇ye world from rust!
”); }}
與之對應(yīng)的 Makefile:
obj-m := hello_world.o
構(gòu)建:
make -C /path/to/linux_src M=$(pwd) LLVM=1 modules
之后就和使用普通的內(nèi)核模塊一樣,使用 insmod 工具或者 modprobe 工具加載就可以了。在使用體驗上是沒有區(qū)別的。
module! { } 宏
這個宏可以被認(rèn)為是 Rust 內(nèi)核模塊的入口,因為在其中定義了一個內(nèi)核模塊所需的所有信息,包括:Author、License、Description 等。其中最重要的是 type 字段,在其中需要指定內(nèi)核模塊結(jié)構(gòu)的名字。在這個例子中:
module! { 。。。 type: HelloWorld, 。。。}struct HelloWorld;
module_init() 與 module_exit()
在使用 C 編寫的內(nèi)核模塊中,這兩個宏定義了模塊的入口函數(shù)與退出函數(shù)。在 Rust 編寫的內(nèi)核模塊中,對應(yīng)的功能由 trait KernelModule 和 trait Drop 來實現(xiàn)。trait KernelModule 中定義 init() 函數(shù),會在模塊驅(qū)動初始化時被調(diào)用;trait Drop 是 Rust 的內(nèi)置 trait,其中定義的 drop() 函數(shù)會在變量生命周期結(jié)束時被調(diào)用。
編譯與鏈接
所有的內(nèi)核模塊文件會首先被編譯成 .o 目標(biāo)文件,之后由內(nèi)核鏈接器將這些 .o 文件和自動生成的模塊目標(biāo)文件 .mod.o 一起鏈接成為 .ko 文件。這個 .ko 文件符合動態(tài)庫 ELF 文件格式,能夠被內(nèi)核識別并加載。
其他
完整的介紹 Rust 是如何被集成進內(nèi)核的文章可以在 我的 Github[13] 上找到,由于寫的倉促,可能存在一些不足,還請見諒。
作者:蘇子彬,阿里云 PAI 平臺開發(fā)工程師,主要從事 Linux 系統(tǒng)及驅(qū)動的相關(guān)開發(fā),曾為 PAI 平臺編寫 FPGA 加速卡驅(qū)動。
[1]Rust support:https://lkml.org/lkml/2021/4/14/1023
[2]Miguel Ojeda:https://ojeda.dev/
[3]Google 的大力支持:https://www.zdnet.com/article/rust-in-the-linux-kernel-just-got-a-big-boost-from-google/
[4]Miguel Ojeda:https://ojeda.dev/
[5]采訪:https://www.infoworld.com/article/3109150/linux-at-25-linus-torvalds-on-the-evolution-and-future-of-linux.html
[6]Rust support:https://lkml.org/lkml/2021/4/14/1023
[7]Miguel Ojeda:https://ojeda.dev/
[8]Rust for Linux 文檔:https://github.com/Rust-forLinux/linux/blob/rust/Documentation/rust/quick-start.rst
[9]llvm 官方主頁:https://github.com/llvm/llvm-project/releases
[10]早期實驗:https://github.com/Rust-for-Linux/linux/blob/rust/Documentation/rust/quick-start.rst#building
[11]Bindgen:https://github.com/rust-lang/rust-bindgen
[12]拉取請求:https://github.com/Rust-for-Linux/linux/pull/402
[13]我的 Github:https://github.com/d0u9/Linux-Device-Driver-Rust/tree/master/00_Introduction_to_Rust_Module_in_Linux
編輯:jq
-
Google
+關(guān)注
關(guān)注
5文章
1765瀏覽量
57536 -
python
+關(guān)注
關(guān)注
56文章
4797瀏覽量
84694 -
C 語言
+關(guān)注
關(guān)注
0文章
18瀏覽量
14227 -
Rust
+關(guān)注
關(guān)注
1文章
228瀏覽量
6611
原文標(biāo)題:如何用 Rust 編寫一個 Linux 內(nèi)核模塊
文章出處:【微信號:gh_3980db2283cd,微信公眾號:開關(guān)電源芯片】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論