Building an out-of-tree kernel module in Rust
In this post I will guide you through building an external kernel module in Rust. Using the mainline Linux kernel.
Compiling a kernel with Rust support📎
To load rust modules we need to be on a kernel that has been compiled with Rust support. Most distribution kernels do not have Rust support1, which means we have to compile a kernel ourselves. If you are on ArchLinux, you could install linux-rust2 and skip ahead.
Obtaining the kernel source📎
First we need to download the linux sourcecode. You can find the latest version on https://kernel.org.
Install the latest version:
$ wget https://cdn.kernel.org/pub/linux/kernel/vA.x/linux-A.B.C.tar.xz
Then extract it:
$ tar -xvf linux-A.B.C.tar.xz
Now change into the directory and clean it:
$ cd linux-A-B-C
$ make mrproper
Installing the toolchain3📎
Before we can start configuring and compiling the kernel we have to ensure that we have the correct tools installed. From this point I will assume you have rustup
installed for managing Rust toolchains.
To check if we already meet the version requirements of the toolchain, run the rustavailable
target:
$ make LLVM=1 rustavailable
This will most likely reveal that your toolchain is either too new, too old or missing components.
Let's first install the correct toolchain:
$ rustup override set $(scripts/min-tool-version.sh rustc)
The override
subcommand will configure the new toolchain only for your current directory.
Now install the Rust standard library:
$ rustup component add rust-src
And install bindgen
:
$ cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen
Note: bindgen
(and Rust support itself) requires llvm
4 to be installed. You also need lldb
.
We can now confirm that we got the right toolchain by running rustavailable
again:
$ make LLVM=1 rustavailable
Rust is available!
Configuring the kernel📎
We are now ready to configure the kernel. Most distribution kernels are build with the CONFIG_IKCONFIG_PROC
option, which allows us to view the config of the current kernel using zcat /proc/config.gz
. This is a good starting point for our own configuration.
Let's copy the current configuration over:
$ zcat /proc/config.gz > .config
Now we can dive into the configuration ourselves:
$ make LLVM=1 nconfig
You need to enable the Rust support
option (CONFIG_RUST
):
General setup
-> Rust support
If the option is missing from General setup
, it is likely another option is blocking its availability. Within nconfig
you can use F8
to search for an option and view its dependencies.
Now that you have Rust support enabled, you can optionally include some Rust samples in the compilation.
You can find those here:
Kernel hacking
-> Sample kernel code
-> Rust samples
Let's also give our kernel a name. Near the top of General setup
is a CONFIG_LOCALVERSION
option. I recommend setting it to -rust
.
General setup
-> Local version
After having configured everything we want, we can move onto compiling.
Compiling our kernel📎
To compile our kernel simply execute make:
$ make LLVM=1
You most likely want to specify the amount of parallel jobs to run with -jx
.
After that finishes compiling for a day or so, we can move on to installing our brand new kernel.
Installing our kernel📎
First we will install the modules, run the modules_install
target as root:
# make modules_install
After that we install the image:
# cp arch/x86/boot/bzImage /boot/vmlinuz-rust
Building the initial ramdisk📎
Updating the bootloader📎
Now it's time to update our bootloader to list our new kernel option. I am on ArchLinux and using grub so I run:
# grub-mkconfig -o /boot/grub/grub.cfg
This should work for most systems using grub but on debian-based system it is preferred to run update-grub
.
Booting our new kernel📎
After you reboot into our new kernel we can test the two sample modules that we included.
First load the modules:5
# modprobe rust_minimal
# modprobe rust_print
Now we can view the log output:
# dmesg
...
[ 78.124956] rust_minimal: Rust minimal sample (init)
[ 78.124960] rust_minimal: Am I built-in? false
[ 82.828561] rust_print: Rust printing macros sample (init)
[ 82.828570] rust_print: Emergency message (level 0) without args
[ 82.828579] rust_print: Alert message (level 1) without args
[ 82.828581] rust_print: Critical message (level 2) without args
[ 82.828583] rust_print: Error message (level 3) without args
[ 82.828585] rust_print: Warning message (level 4) without args
[ 82.828587] rust_print: Notice message (level 5) without args
[ 82.828589] rust_print: Info message (level 6) without args
[ 82.828590] rust_print: A line that is continued without args
[ 82.828597] rust_print: Emergency message (level 0) with args
[ 82.828603] rust_print: Alert message (level 1) with args
[ 82.828605] rust_print: Critical message (level 2) with args
[ 82.828607] rust_print: Error message (level 3) with args
[ 82.828610] rust_print: Warning message (level 4) with args
[ 82.828612] rust_print: Notice message (level 5) with args
[ 82.828614] rust_print: Info message (level 6) with args
[ 82.828616] rust_print: A line that is continued with args
[ 82.828624] rust_print: 1
[ 82.828626] rust_print: "hello, world"
[ 82.828634] rust_print: [samples/rust/rust_print.rs:34] c = "hello, world"
Loadable kernel modules in Rust, awesome!
Writing our module📎
Getting the documentation📎
Before we start writing any code it is useful to have some documentation at hand. In our build directory we can choose to build the kernel rustdoc:
$ make LLVM=1 rustdoc
$ xdg-open rust/doc/kernel/index.html
Alternatively you can find the latest version here: https://rust-for-linux.github.io/docs/kernel/index.html. But most modules you see here aren't merged into mainline yet.
Viewing the sample sourcecode📎
Two basic kernel modules can be found as samples within the linux sourcecode.
$ ls samples/rust
Kconfig
Makefile
hostprogs/
rust_minimal.rs
rust_print.rs
Find the latest versions here: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/samples/rust.
The rust_minimal
module is as follows:
// SPDX-License-Identifier: GPL-2.0
//! Rust minimal sample.
use kernel::prelude::*;
module! {
type: RustMinimal,
name: "rust_minimal",
author: "Rust for Linux Contributors",
description: "Rust minimal sample",
license: "GPL",
}
struct RustMinimal {
numbers: Vec<i32>,
}
impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));
let mut numbers = Vec::new();
numbers.try_push(72)?;
numbers.try_push(108)?;
numbers.try_push(200)?;
Ok(RustMinimal { numbers })
}
}
impl Drop for RustMinimal {
fn drop(&mut self) {
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
}
}
Interestingly, instead of creating an exit handler function, we utilize the modules drop method instead.
Writing the module📎
Sadly, the mainline kernel currently only supports primitives and printing. So let's make a very minimal kernel module:
Create a hello_world.rs
:
//! Hello World module
use kernel::prelude::*;
module! {
type: HelloWorld,
name: "hello_world",
author: "LevitatingBusinessMan",
description: "Prints 'Hello World!'",
license: "GPL",
}
struct HelloWorld;
impl kernel::Module for HelloWorld {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Hello World!\n");
Ok(HelloWorld)
}
}
Create a Makefile
:
KDIR ?= /lib/modules/`uname -r`/build
all:
$(MAKE) LLVM=1 -C $(KDIR) M=$$PWD
Create a Kbuild
:
obj-m := hello_world.o
And now compile and load the module:
$ make
# insmod hello_world.ko
View the output:
# dmesg
...
[ 3647.831202] hello_world: Hello World!
It's that easy! Once you've compiled a kernel with Rust support, building a rust module is as easy as building any kernel module (if not easier).
Conclusion📎
The Rust support in the mainline kernel is still very minimal. Rust-For-Linux has much more abstractions created as a prototype which have been frozen in their rust
branch. You can compare the documentation between this prototype branch and the mainline:
Or at the examples present in Rust-For-Linux's rust
branch:
Merging the Rust-For-Linux project into the mainline kernel has been a very slow process. In the future I might make a post about writing a more interesting Rust kernel module using the old Rust-For-Linux branch itself.
-
You can check for Rust support in your current kernel by looking for
CONFIG_RUST
inzcat /proc/config.gz
. ↩ -
The maintainer of which rnestler, managed to compile an external module just a day before I started writing this blogpost. He actively maintains the package and I verified it's
PKGBUILD
andconfig
. However he did write a patch for the Makefile that I haven't found to be necessary. And I also haven't found the manual installation of metadata likescripts/target.json
necessary like is done in the PKGBUILD. ↩ -
The following commands are from https://docs.kernel.org/rust/quick-start.html#requirements-building. Refer to this if you're facing issues. ↩
-
AKA the dragon compiler ↩
-
The third sample is actually a userland program which can be executed from the build directory as
samples/rust/hostprogs/single
. ↩
Created: 2023-07-18
Updated: 2023-10-11