In recent years, the Rust language has gained a lot of developer attention for its memory safety, high reliability, zero abstraction, and other capabilities that happen to be needed in kernel programming, so let’s look at how to write Linux kernel modules in rust.

Rust and Kernel Modules

Although Rust support has been merged into the mainline in Linux Kernel version 6.1, so theoretically developers can use Rust to write kernel modules for Linux 6.1.

However, in actual development work, the kernel version is not the latest, for example, the Debian 11 kernel is version 5.10, so in this case, how to write kernel modules with Rust?

Principle

  1. how Rust registers callbacks with the kernel, how to call kernel code. interoperability of Rust and C.
  2. how to compile Rust to the target platform. the target configuration of Rust.
  3. how Rust declares kernel module entries and adds special sections. binary conventions for Rust kernel modules.

Interoperability of Rust and C

The first issue is basically the interoperability of C and Rust.

Thanks to Rust’s abstraction level, it is easy to call both C and Rust. rust also provides an official library like bindgen, which generates .rs files from .h files.

So, it looks like it is possible to translate the kernel headers directly to .rs using bindgen?

But there is still one problem: how to get the kernel header path?

You can use a dummy kernel module to derive the compilation parameters during the compilation process, which contains the header paths, compilation parameters, etc., for bindgen to generate the code.

Rust and target configurations

The main differences between kernel modules and ordinary programs are:

  1. the kernel module is freestanding, no libc, and memory allocation is primitive
  2. kernel modules have special conventions for exception handling, etc.

Rust provides a no_std mechanism that allows rust code to be compiled into freestanding binary; Rust also provides a way to customize the target, so that you can customize the declaration to generate the binary specification.

Binary conventions for kernel modules

The kernel has some conventions for kernel modules.

  • Declaring module information through sections such as .modinfo
  • provide init_module, cleanup_module to provide kernel module installation and uninstallation

In this regard, Rust provides link_section to define sections, and extern “C” to export functions.

In addition, these underlying operations can be simplified by the kernel providing some C macros, and Rust also provides macros that can be used to do similar things.

A small example

Having said that, let’s look at an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#![no_std]
// no_std is used to indicate that there is no std library, i.e. freestanding environment
extern crate alloc;

use alloc::borrow::ToOwned;
use alloc::string::String;

// We use printk as the base layer and provide println
use linux_kernel_module::println;

// This struct represents the kernel module
struct HelloWorldModule {
    message: String,
}

// Implementing the kernel module initialization method
impl linux_kernel_module::KernelModule for HelloWorldModule {
    fn init() -> linux_kernel_module::KernelResult<Self> {
        println!("Hello kernel module from rust!");
        Ok(HelloWorldModule {
            message: "on the heap!".to_owned(),
        })
    }
}

// Provide kernel module uninstall method
impl Drop for HelloWorldModule {
    fn drop(&mut self) {
        println!("My message is {}", self.message);
        println!("Goodbye kernel module from rust!");
    }
}

// With the kernel_module macro, information about the kernel module is exported
linux_kernel_module::kernel_module!(
    HelloWorldModule,
    author: b"Fish in a Barrel Contributors",
    description: b"An extremely simple kernel module",
    license: b"GPL"
);

Specific builds and runs.

1
2
3
4
5
6
7
8
9
$ cd linux-kernel-module-rust/hello-world
$ RUST_TARGET_PATH=$(pwd)/.. cargo +nightly xbuild --target x86_64-linux-kernel-module
$ make
$ insmod helloworld.ko
$ rmmod helloworld
$ dmesg | tail -n 3
[521088.916091] Hello kernel module from rust!
[521174.204889] My message is on the heap!
[521174.204891] Goodbye kernel module from rust!

Building and running linux kernel modules

Tested on kernel 5.10.0-17-amd64.

For the specific code and related configuration, please refer to: https://github.com/robberphex/linux-kernel-module-rust

Some details

  • VSCode support

    Since rust-analyzer does not have enough support for custom targets and multiple modules, we need to manually configure settings.json for the time being in order to develop properly.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    
    {
        "rust-analyzer.cargo.extraEnv": {
            "RUST_TARGET_PATH": "/root/linux-kernel-module-rust"
        },
        "rust-analyzer.cargo.target": "x86_64-linux-kernel-module",
        "rust-analyzer.server.extraEnv": {
            "RA_LOG": "lsp_server=debug",
            "RUST_TARGET_PATH": "/root/linux-kernel-module-rust"
        },
        "rust-analyzer.trace.server": "verbose",
        "rust-analyzer.linkedProjects": [
            "hello-world/Cargo.toml",
            "Cargo.toml"
        ],
    }
    
  • Other advanced features

    For example, character devices, sysctl and other functions, you can refer to the relevant test code in the project.

More Planning

The original project is fishinabarrel/linux-kernel-module-rust, but it is currently prompted to use rust-for-linux, which has been archived. however, given that there are still a lot of older kernels out there, I have re-fixed some of the environment of this project to allow people to write in Rust on older kernels kernel module.