Linux Kernel Modules in Rust

Writing kernel modules using Rust-for-Linux.

What is Rust-for-Linux?

Rust-for-Linux is the official effort to enable Rust as a second language for Linux kernel development. It provides:

  • Safe abstractions over kernel APIs
  • Compile-time safety guarantees
  • Integration with existing C code

Rust support in the Linux kernel is still evolving. Check the latest kernel documentation for current APIs.

Requirements

  • Linux kernel 6.1+ with Rust support enabled
  • Rust toolchain (specific version required by kernel)
  • LLVM/Clang for kernel builds
# Check if kernel has Rust support
make LLVM=1 rustavailable

Minimal Kernel Module

// SPDX-License-Identifier: GPL-2.0

//! Minimal Rust kernel module

use kernel::prelude::*;

module! {
    type: MinimalModule,
    name: "minimal",
    author: "Your Name",
    description: "A minimal Rust kernel module",
    license: "GPL",
}

struct MinimalModule;

impl kernel::Module for MinimalModule {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("Minimal Rust module loaded\n");
        Ok(MinimalModule)
    }
}

impl Drop for MinimalModule {
    fn drop(&mut self) {
        pr_info!("Minimal Rust module unloaded\n");
    }
}

The module! Macro

module! {
    type: MyModule,           // Module struct type
    name: "my_module",        // Module name
    author: "Author Name",    // Author
    description: "Description", // Description
    license: "GPL",           // License (required)
    params: {                 // Module parameters (optional)
        my_param: i32 {
            default: 42,
            permissions: 0o644,
            description: "An example parameter",
        },
    },
}

Kernel Printing

use kernel::prelude::*;

fn example() {
    // Different log levels
    pr_emerg!("Emergency message\n");
    pr_alert!("Alert message\n");
    pr_crit!("Critical message\n");
    pr_err!("Error message\n");
    pr_warn!("Warning message\n");
    pr_notice!("Notice message\n");
    pr_info!("Info message\n");
    pr_debug!("Debug message\n");

    // With formatting
    let value = 42;
    pr_info!("Value is {}\n", value);
}

Error Handling

The kernel uses its own error type:

use kernel::prelude::*;
use kernel::error::code;

fn may_fail() -> Result {
    // Return kernel error codes
    if something_wrong {
        return Err(code::EINVAL);
    }
    Ok(())
}

fn use_result() -> Result<i32> {
    let result = may_fail()?;
    Ok(42)
}

Memory Allocation

use kernel::prelude::*;

fn allocate() -> Result {
    // Box equivalent (GFP_KERNEL allocation)
    let boxed = Box::try_new(42)?;

    // Vec equivalent
    let mut vec = Vec::try_with_capacity(10)?;
    vec.try_push(1)?;
    vec.try_push(2)?;

    Ok(())
}

Synchronization Primitives

use kernel::sync::{Mutex, SpinLock};
use kernel::prelude::*;

struct SharedData {
    value: u32,
}

struct MyModule {
    // Mutex for sleeping contexts
    data: Mutex<SharedData>,

    // SpinLock for interrupt contexts
    counter: SpinLock<u32>,
}

impl MyModule {
    fn update_value(&self, new_value: u32) {
        let mut guard = self.data.lock();
        guard.value = new_value;
    }

    fn increment_counter(&self) {
        let mut guard = self.counter.lock();
        *guard += 1;
    }
}

Character Device

use kernel::prelude::*;
use kernel::file::{self, File, Operations};
use kernel::io_buffer::{IoBufferReader, IoBufferWriter};

struct MyDevice;

#[vtable]
impl Operations for MyDevice {
    type Data = ();
    type OpenData = ();

    fn open(_context: &(), _file: &File) -> Result<Self::Data> {
        pr_info!("Device opened\n");
        Ok(())
    }

    fn read(
        _data: (),
        _file: &File,
        writer: &mut impl IoBufferWriter,
        _offset: u64,
    ) -> Result<usize> {
        let message = b"Hello from Rust!\n";
        writer.write_slice(message)?;
        Ok(message.len())
    }

    fn write(
        _data: (),
        _file: &File,
        reader: &mut impl IoBufferReader,
        _offset: u64,
    ) -> Result<usize> {
        let len = reader.len();
        pr_info!("Received {} bytes\n", len);
        Ok(len)
    }
}

Building the Module

Create a Kbuild file:

obj-m := my_module.o

Build with the kernel:

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

Loading and Testing

# Load module
sudo insmod my_module.ko

# Check kernel log
dmesg | tail

# List loaded modules
lsmod | grep my_module

# Unload module
sudo rmmod my_module

Safety Considerations

Aspect Handling
Memory allocation Use try_ variants, handle errors
Null pointers Wrapped in Option/Result
Data races Mutex/SpinLock required
Integer overflow Checked by default in debug
Buffer overflows Slice bounds checked

Current Limitations

  1. API stability - Rust APIs are still evolving
  2. Coverage - Not all kernel subsystems have Rust bindings
  3. Toolchain - Specific Rust version required
  4. Debug info - Some limitations with debugging

Resources

Summary

Component Purpose
module! macro Module definition
pr_info! etc. Kernel logging
Result Error handling
Mutex/SpinLock Synchronization
Registration Device registration

Next Steps

Learn about Drivers and memory-mapped I/O.


Back to top

Rust Programming Guide is not affiliated with the Rust Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.