ESC
输入关键词搜索文章标题和内容

3分钟深度讲解Rust:嵌入式与Linux内核开发,C语言之后的最优解

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

3分钟深度讲解Rust:嵌入式与Linux内核开发,C语言之后的最优解

导读

Rust已正式进入Linux内核主线,成为继C之后第二门内核官方支持语言。本文用3分钟,从Rust核心特性、与C语言对比、到手把手带你写一个真正的Linux内核模块(insmod → dmesg看日志 → rmmod),帮你快速理解Rust凭什么能进内核主线。所有代码已在Ubuntu 24.04(Kernel 6.17)验证通过,可直接复制运行。

为什么要花几分钟了解Rust

Linux内核已正式支持Rust

2022年Linux 6.1内核首次合并Rust支持,2026年Rust内核驱动框架趋于成熟。这意味着:写Linux驱动,你有了C之外的选择。

底层开发的三大痛点,Rust全解决

痛点 C语言现状 Rust方案
内存泄漏 手动malloc/free,忘记释放就泄漏 所有权机制,编译期自动释放
野指针 释放后继续访问,运行时才崩溃 借用检查器,编译期杜绝
数据竞争 多线程共享变量无保护,结果不确定 所有权+Send/Sync trait,编译期防止

零成本抽象:不牺牲性能的安全

Rust没有垃圾回收器(GC),所有安全检查在编译期完成,运行时开销为零。这意味着:Rust的安全,不需要用性能换。

Rust核心特性

特性一:所有权——不用GC也能内存安全

Rust所有权只有三条规则:

  1. 每个值只有一个所有者
  2. 赋值会转移所有权(Move语义)
  3. 所有者离开作用域,值自动释放
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;           // s1的所有权转移给s2
    // println!("{}", s1); // 编译报错!s1已失效
    println!("{}", s2);    // 正常
}

对比C语言的等价操作:

char *s1 = malloc(6);
strcpy(s1, "hello");
char *s2 = s1;       // 两个指针指向同一块内存
free(s1);
printf("%s", s2);    // 野指针!运行时才崩溃

Rust在编译期就阻止了这类错误,而不是等到运行时才崩溃。

特性二:借用与引用——灵活访问不丧失安全

fn calculate_length(s: &String) -> usize {  // 不可变借用
    s.len()
}

fn append_world(s: &mut String) {           // 可变借用
    s.push_str(", world");
}

借用规则(编译期强制):

  • 同一时间只能有一个可变引用,或多个不可变引用
  • 可变与不可变引用不能共存

两条规则就够了,编译器帮你拦住所有数据竞争。

特性三:Option与Result——告别空指针和错误码

C语言用NULL和返回值表示错误,Rust用类型系统强制处理:

// Option:替代null
let score: Option<i32> = Some(85);
let no_score: Option<i32> = None;

// 必须显式处理"无值"的情况
match score {
    Some(s) => println!("成绩: {}", s),
    None => println!("无成绩"),
}

// Result:替代错误码
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("除数不能为0"))
    } else {
        Ok(a / b)
    }
}

// ?运算符:简化错误传播
fn complex_calc() -> Result<f64, String> {
    let a = divide(10.0, 2.0)?;   // 出错直接返回
    let b = divide(a, 3.0)?;
    Ok(b * 2.0)
}

特性四:Trait——零成本的多态

trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> &str;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
    fn name(&self) -> &str { "圆形" }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
    fn name(&self) -> &str { "矩形" }
}

泛型在编译期展开(单态化),运行时没有虚函数表开销——这就是零成本抽象:

来自 linuxros.cn · linuxROS
// 编译器自动生成具体函数,无运行时分发
fn get_larger<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
// 编译后等价于:
// fn get_larger_i32(a: i32, b: i32) -> i32 { ... }
// fn get_larger_f64(a: f64, b: f64) -> f64 { ... }

特性五:并发安全——编译期防止数据竞争

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for i in 0..5 {
        let counter_clone = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter_clone.lock().unwrap();
            *num += 1;
            println!("线程 {} 执行,当前值: {}", i, *num);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
    println!("最终计数: {}", *counter.lock().unwrap()); // 确定是5
}

对比C语言的数据竞争:

int shared_data = 0;
void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        shared_data++;  // 数据竞争!结果不确定
    }
    return NULL;
}

Rust的Arc<Mutex<T>>在类型层面强制线程安全——不加锁,代码编译不过。

Rust vs C:核心对比

维度 C语言 Rust
内存管理 手动malloc/free 所有权自动释放
空指针 运行时崩溃(段错误) Option类型,编译期杜绝
错误处理 返回错误码,容易忽略 Result类型,强制处理
数据竞争 无保护,结果不确定 编译期阻止
泛型 宏或void*,类型不安全 编译期单态化,类型安全
FFI互操作 — 可调用C函数,无缝对接
运行时开销 无 无(零成本抽象)
学习曲线 低 较高(借用检查器)

关键结论:Rust的安全不牺牲性能,代价是编译期的学习成本。

完整Demo:Rust Linux内核驱动

这个Demo展示Rust如何写一个真实的Linux内核模块,通过C包装层调用Rust代码。

项目结构

rust_linux_driver/
├── .cargo/
│   └── config.toml   # Rust编译配置(static relocation)
├── Cargo.toml        # Rust项目配置
├── src/
│   └── lib.rs        # Rust核心代码
├── wrapper.c         # C语言包装层
└── Makefile          # 编译脚本

1. Cargo.toml

[package]
name = "rust_linux_driver"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib"]

[profile.release]
panic = "abort"
opt-level = "s"

2. src/lib.rs

#![no_std]                    // 内核环境不支持标准库

use core::ffi::c_int;

const HELLO_MSG: &[u8] = b"[rust_driver] Hello Rust Linux Kernel Driver!\n\0";
const BYE_MSG: &[u8] = b"[rust_driver] Goodbye Rust Linux Driver!\n\0";

#[no_mangle]
pub extern "C" fn rust_init_module() -> c_int {
    unsafe {
        _printk(HELLO_MSG.as_ptr() as *const u8);
    }
    0
}

#[no_mangle]
pub extern "C" fn rust_cleanup_module() {
    unsafe {
        _printk(BYE_MSG.as_ptr() as *const u8);
    }
}

extern "C" {
    fn _printk(fmt: *const u8, ...) -> c_int;  // 内核导出的是_printk
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

关键:内核导出的符号是 _printk 而非 printk(printk 是C宏),Rust FFI必须声明为 _printk。

3. .cargo/config.toml(Rust编译配置)

[build]
rustflags = ["-C", "relocation-model=static"]

关键:relocation-model=static 避免生成内核不支持的 GOTPCREL 重定位。

4. wrapper.c

#include <linux/module.h>
#include <linux/kernel.h>

extern int rust_init_module(void);
extern void rust_cleanup_module(void);

int init_module(void)
{
    pr_info("C wrapper: calling Rust init\n");
    return rust_init_module();
}

void cleanup_module(void)
{
    pr_info("C wrapper: calling Rust cleanup\n");
    rust_cleanup_module();
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("Rust Linux Kernel Driver with C wrapper");

5. Makefile

obj-m += rust_linux_driver.o
rust_linux_driver-objs := wrapper.o rust_driver.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    rustc --edition 2021 --crate-type staticlib --emit=obj \
        src/lib.rs -o rust_driver.o \
        --target x86_64-unknown-none \
        -C relocation-model=static -C panic=abort
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
    rm -f rust_driver.o *.o

关键:用 rustc --emit=obj 直接生成 .o 文件,避免 cargo build 静态库中 compiler_builtins 的 GOTPCREL 重定位问题。

6. 编译、加载、测试

# 安装依赖
sudo apt install -y build-essential linux-headers-$(uname -r) llvm clang lld libelf-dev
rustup target add x86_64-unknown-none

# 编译
make

# 加载驱动
sudo insmod rust_linux_driver.ko
sudo dmesg | tail -n 5
# 输出: [rust_driver] Hello Rust Linux Kernel Driver!

# 卸载驱动
sudo rmmod rust_linux_driver
sudo dmesg | tail -n 5
# 输出: [rust_driver] Goodbye Rust Linux Driver!

Demo技术要点

要点 说明
#![no_std] 禁用标准库,内核环境不支持std
_printk 内核导出符号为 _printk,非 printk(C宏)
relocation-model=static 避免 GOTPCREL 重定位,内核模块加载器不支持
--emit=obj 直接编译为 .o 目标文件,跳过静态库提取
#[no_mangle] 防止函数名被编译器混淆,C可调用
wrapper.c C包装层,桥接内核模块框架与Rust代码

总结

Rust继承了C的零开销理念,补上了内存安全和并发安全的短板:

  1. 内存安全:所有权机制在编译期杜绝泄漏、野指针、重复释放
  2. 并发安全:类型系统在编译期防止数据竞争
  3. 零成本抽象:安全不牺牲性能,无GC无运行时开销
  4. 内核就绪:Linux 6.1+官方支持,C包装层可兼容旧内核
  5. 生态成熟:Cargo包管理、Clippy检查、Rustfmt格式化

C语言在底层开发领域用了40年,Rust可能是下一个40年的答案。

再往远想:AI写代码的能力还在涨,LLVM和GCC的后端本来就是IR到二进制——哪天AI能从自然语言直接出正确的机器码,语言本身就退居二线了,C和Rust都是中间态。但那是以后的事。现在,编译器还得干活,类型系统还得兜底,Rust是今天安全+性能最务实的选择。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页