作者:卜比

近年来,Rust 语言以内存安全、高可靠性、零笼统等才能获得许多开发者重视,而这些特性恰好是内核编程中所需求的,所以咱们看下怎么用rust来写Linux内核模块。

Rust 与内核模块

虽然 Rust 支撑已经在 LinuxKernel6.1 版别合并到主线了,所以理论上来说,开发者能够运用 Rust 来为 Linux6.1 写内核模块。

但实践开发工作中,内核版别不是最新的,比方 Debian 11 的内核便是 5.10 版别的,那么在这种情况下,该怎么用 Rust 写内核模块呢?

原理

  1. Rust 怎么向内核注册回调、怎么调用内核代码。Rust 和 C 的互操作性
  2. Rust 怎么编译到目标平台上。Rust 的 target 装备
  3. Rust 怎么申明内核模块进口、并增加特别 section。Rust 内核模块的二进制约好

Rust 和 C 的互操作性

第一个问题基本上便是 C 和 Rust 的互操作性了。

得益于 Rust 的笼统层次,C 语言和 Rust 的互相调用都是比较简单的。rust 官方也供给了 bindgen 这样,根据 .h 文件生成 .rs 文件的库。

这样一来,貌似直接运用 bindgen 将内核头文件翻译成 .rs 就能够了?

但还有一个问题,怎么获取内核头文件路径呢?

能够运用一个 dummy 内核模块,在编译过程中把编译参数导出来,其间包含了头文件路径,编译参数等,用于 bindgen 生成代码。

Rust 和target 装备

内核模块和普通的程序相比,主要的不同在于:

  1. 内核模块是 freestanding 的,没有 libc、内存分配也比较原始
  2. 内核模块关于异常处理等有特别约好

Rust 供给了 no_std 机制,能够让 rust 代码编译成 freestanding 二进制;Rust 也供给了自定义 target 的方法,能够自定义声明生成二进制的标准。

内核模块的二进制约好

内核对内核模块有一些约好:

  • 经过 .modinfo 等 section 来声明模块信息
  • 供给 init_module、cleanup_module 来供给内核模块的安装和卸载功用

在这一块,Rust 供给了 link_section 来自定义 section,也支撑 extern “C”来导出函数。

此外,这些底层的操作,能够由内核供给一些 C 语言宏来简化代码,Rust 也供给了宏,能够用来做类似的工作。

一个小比方

说了这么多,咱们来看一个带注释的比方:

#![no_std]
// no_std用于表明没有std库,即freestanding环境
extern crate alloc;
use alloc::borrow::ToOwned;
use alloc::string::String;
// 咱们以printk为底层,供给了println
use linux_kernel_module::println;
// 这个struct代表内核模块
struct HelloWorldModule {
    message: String,
}
// 完成内核模块初始化方法
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(),
        })
    }
}
// 供给内核模块卸载方法
impl Drop for HelloWorldModule {
    fn drop(&mut self) {
        println!("My message is {}", self.message);
        println!("Goodbye kernel module from rust!");
    }
}
// 经过kernel_module宏,export了内核模块的相关信息
linux_kernel_module::kernel_module!(
    HelloWorldModule,
    author: b"Fish in a Barrel Contributors",
    description: b"An extremely simple kernel module",
    license: b"GPL"
);

具体的构建和运行:

$ 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!

如何使用 rust 写内核模块

已在内核 5.10.0-17-amd64 上测试。

具体的代码以及相关装备,能够参阅 GitHub 库房:github.com/robberphex/…

一些小细节

  • VSCode 支撑

因为 rust-analyzer 关于自定义 target,多模块的支撑不够,所以咱们暂时需求手动装备下 settings.json 才能正常开发:

{
    "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"
    ],
}
  • 其他高档功用

比方字符设备、sysctl 等功用,能够参阅项目中相关的测试代码。

更多规划

  • 和 Rust-for-Linux 保持 API 一致。(Rust-for-Linux 比方:github.com/Rust-for-Li…
  • Rust 供给的内存安全性、零笼统等才能,恰好是内核范畴亟需的特性和才能。比方内核态假如呈现内存泄漏、野指针,一会形成很大影响、二来也很难调试。在这个范畴,咱们能够借助Rust的才能来构造愈加安全、更大的项目。

原始项目是 fishinabarrel/linux-kernel-module-rust,但目前提示运用 rust-for-linux,已经 archived。然而,考虑到目前旧版别内核还有许多,所以我从头修正了这个项目的一些环境,让我们在旧版别内核上能够用 Rust 编写内核模块。