# Rust for Linux 基本介紹,關於 Kbuild ### Rust for Linux 簡介 Rust for Linux 為在 Linux Kernel 中使用 Rust 撰寫 Kernel Module 的一個專案 Rust for Linux 現況在 Git 主線上的更新並不多,大部份為升級一些工具的版本 (但是不會升級到最新的版本),基於 Git 主線的模組開發可以參照 Rust for Linux 在 Lore 中郵件信箱中參考。 在 `rust/macros/lib.rs` 中可以看到我們定義了一個 proc_macro,傳入的參數為 TokenStream ```rust #[proc_macro] pub fn module(ts: TokenStream) -> TokenStream { module::module(ts) } ``` 這邊傳入的 TokenStream,指的就是 `module!{}` 裡面我們所傳入的資訊,包含 type, name, author 等等,傳入的參數定義如下面所表示 ```rust use kernel::prelude::*; module!{ type: MyModule, /// #(require) name: "my_kernel_module", /// #(require) author: "Rust for Linux Contributors", description: "My very own kernel module!", license: "GPL", /// #(require) params: { my_i32: i32 { default: 42, permissions: 0o000, description: "Example of i32", }, writeable_i32: i32 { default: 42, permissions: 0o644, description: "Example of i32", }, }, } ``` 接著會呼叫 `rust/macros/module.rs` 這個檔案中 `module` 函式。 ```rust pub(crate) fn module(ts: TokenStream) -> TokenStream { let mut it = ts.into_iter(); let info = ModuleInfo::parse(&mut it); let mut modinfo = ModInfoBuilder::new(info.name.as_ref()); if let Some(author) = info.author { modinfo.emit("author", &author); } if let Some(description) = info.description { modinfo.emit("description", &description); } modinfo.emit("license", &info.license); if let Some(alias) = info.alias { modinfo.emit("alias", &alias); } ... ``` ### Rust for Linux 提供框架 #### 關於 Binding Rust for Linux 對於 Linux Kernel 中核心基礎建設提供了一個基本的封裝,封裝的部份是基於 Bindings 和 helper 對使用 C 語言撰寫的 Linux Kernel 的界面封裝成 Rust 能夠呼叫的界面。 由於大部分 Linux Kernel 所提供的界面尚未被 Rust for Linux 進行封裝,如果我們要自行添加界面借助 Bindings 機制去產生對應的 Rust 界面,我們可以通過 `binding_helpers.h` 或是 `helper.c` 產生出對應的界面。 :::info - `binding_helper.h`: 大部分 C 的標頭檔我們都可以使用這個檔案去產生出對應的 Rust 界面,產生的方式為呼叫 Rust Bindgen 這個工具。 - `helper.c`: 對於 C 語言中的巨集定義,我們則會使用這個檔案去產生出對應的 Rust 界面 (但目前情況仍然相當不穩定,如 `le16_to_cpu` 等等)。 ::: #### `Binding_helpers.h` 的使用 以下為 `Binding_helper.h` 內容 ```c /* SPDX-License-Identifier: GPL-2.0 */ /* * Header that contains the code (mostly headers) for which Rust bindings * will be automatically generated by `bindgen`. * * Sorted alphabetically. */ #include <kunit/test.h> #include <linux/amba/bus.h> #include <linux/cdev.h> #include <linux/clk.h> #include <linux/errname.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/fs_parser.h> #include <linux/gpio/driver.h> #include <linux/hw_random.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/irq.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/netfilter_arp.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv6.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/poll.h> #include <linux/random.h> #include <linux/refcount.h> #include <linux/security.h> #include <linux/slab.h> #include <linux/sysctl.h> #include <linux/uaccess.h> #include <linux/uio.h> /* `bindgen` gets confused at certain things. */ const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO; const __poll_t BINDINGS_EPOLLIN = EPOLLIN; const __poll_t BINDINGS_EPOLLOUT = EPOLLOUT; const __poll_t BINDINGS_EPOLLERR = EPOLLERR; const __poll_t BINDINGS_EPOLLHUP = EPOLLHUP; const loff_t BINDINGS_MAX_LFS_FILESIZE = MAX_LFS_FILESIZE; ``` 如果我們要加入其他標投檔產生出對應的 Rust 界面,我們可以將 C 標投檔放置到 `include` 底下,如 `include/linux/example/example.h` ```c ... #include <linux/example/example.h> const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO; const __poll_t BINDINGS_EPOLLIN = EPOLLIN; const __poll_t BINDINGS_EPOLLOUT = EPOLLOUT; const __poll_t BINDINGS_EPOLLERR = EPOLLERR; const __poll_t BINDINGS_EPOLLHUP = EPOLLHUP; const loff_t BINDINGS_MAX_LFS_FILESIZE = MAX_LFS_FILESIZE; ``` 接著在編譯時,我們會在編譯輸出中看到我們藉由 bindgen 工具產生出兩個檔案,分別為 `bindgens_generated.rs` 以及 `bindings_helpers_generated.rs` 這兩個檔案,檔案內容即為對應到的 Rust 界面。 以下為 `bindgens_generated.rs` 中部份內容 ```rust #[repr(C)] #[derive(Copy, Clone)] pub struct list_head { pub next: *mut list_head, pub prev: *mut list_head, } impl Default for list_head { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } #[repr(C)] #[derive(Copy, Clone)] pub struct hlist_head { pub first: *mut hlist_node, } impl Default for hlist_head { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } ... extern "C" { pub fn seq_hlist_start(head: *mut hlist_head, pos: loff_t) -> *mut hlist_node; } extern "C" { pub fn seq_hlist_start_head(head: *mut hlist_head, pos: loff_t) -> *mut hlist_node; } extern "C" { pub fn seq_hlist_next( v: *mut core::ffi::c_void, head: *mut hlist_head, ppos: *mut loff_t, ) -> *mut hlist_node; } ``` 函式呼叫的部份使用到 FFI 呼叫。 而如果要在我們使用 Rust 撰寫的核心模組中使用 `bindgens_generated.rs` 中對應的界面,則可以加上 `use kernel::bindings::*;` 使用。 使用 `bindgens_generated.rs` 提供的界面,實際上相當於直接呼叫 C 語言所撰寫的函式,而對於這類型的呼叫我們需要以 `unsafe` 進行標注。