Back

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 llvm4 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.


  1. You can check for Rust support in your current kernel by looking for CONFIG_RUST in zcat /proc/config.gz

  2. 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 and config. 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 like scripts/target.json necessary like is done in the PKGBUILD. 

  3. The following commands are from https://docs.kernel.org/rust/quick-start.html#requirements-building. Refer to this if you're facing issues. 

  4. AKA the dragon compiler 

  5. The third sample is actually a userland program which can be executed from the build directory as samples/rust/hostprogs/single.  

me@levitati.ng

Created: 2023-07-18

Updated: 2023-10-11

login