使用Rust进行嵌入式开发的教程

369 阅读4分钟

在过去的几年里,Rust在程序员中获得了热烈的追捧。技术潮流来来去去,所以很难将仅仅因为某项新技术而产生的兴奋与对某项技术的优点的兴奋区分开来,但我认为Rust是一种真正设计良好的语言。它的目标是帮助开发者建立可靠和高效的软件,而且它从一开始就为这个目的而设计。有一些你会听到的关于Rust的关键特性,在这篇文章中,我证明了许多这些特性正是Rust也恰好适合嵌入式系统的原因。下面是一些例子。

  • 高性能。它速度快,内存利用率高
  • 可靠性。在编译过程中可以消除内存错误
  • 生产力。伟大的文档,友好的编译器,有用的错误信息,以及一流的工具。它有一个集成的包管理器和构建工具,支持智能多编辑器,具有自动完成和类型检查功能,还有一个自动格式化功能,等等。

为什么使用Rust进行嵌入式开发?

Rust是为了保证安全和高性能而设计的。嵌入式软件会有一些问题,主要是由于内存问题。从某种程度上说,Rust是一种面向编译器的语言,所以你可以确保在编译时安全使用内存。以下是使用Rust在嵌入式设备上开发的一些好处。

  • 强大的静态分析
  • 灵活的内存
  • 无畏的并发性
  • 互操作性
  • 可移植性
  • 社区驱动

在这篇文章中,我使用开源的RT-Thread操作系统来演示如何将Rust用于嵌入式开发。

如何在C语言中调用Rust

在C代码中调用Rust代码时,你必须将Rust源代码打包成静态库文件。当C代码编译时,将其链接进来。

用Rust创建一个静态库

在这个过程中,有两个步骤。

1.使用cargo init --lib rust_to_c,在Clion中建立一个lib库。将下面的代码添加到lib.rs 。 下面的函数评估两个i32类型的值的总和,并返回结果。

#![no_std]
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
    a + b
}
#[panic_handler]
fn panic(_info:&PanicInfo) -> !{
    loop{}
}

2.在你的Cargo.toml 文件中加入以下代码,告诉Rustc要生成什么类型的库。

[lib]
name = "sum"
crate-type = ["staticlib"]
path = "src/lib.rs"

交叉编译

你可以针对你的目标进行交叉编译。假设你的嵌入式系统是基于Arm的,步骤很简单。

$ rustup target add armv7a-none-eabi

2.生成静态库文件。

$ cargo build --target=armv7a-none-eabi --release --verbose
Fresh rust_to_c v0.1.0
Finished release [optimized] target(s) in 0.01s

生成头文件

你也需要头文件。

1.安装cbindgencbindgen 工具从Rust库中生成一个C或C++11的头文件。

$ cargo install --force cbindgen

2.在你的项目文件夹下创建一个新的cbindgen.toml 文件。

3.生成一个头文件。

$ cbindgen --config cbindgen.toml --crate rust_to_c --output sum.h

调用Rust库文件

现在你可以对你的Rust库进行调用了。

1.把生成的sum.hsum.a 文件放到rt-thread/bsp/qemu-vexpress-a9/applications 目录中。

2.修改SConscript 文件并添加一个静态库。

   from building import *
   cwd     = GetCurrentDir()
   src     = Glob('*.c') + Glob('*.cpp')
   CPPPATH = [cwd]
   LIBS = ["libsum.a"]
   LIBPATH = [GetCurrentDir()]
   group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH, LIBS = LIBS, LIBPATH = LIBPATH)
   Return('group')

3.在主函数中调用sum函数,获得返回值,并将该值printf

   #include 
   #include 
   #include 
   #include 
   #include "sum.h"
   int main(void)
   {
       int32_t tmp;
       tmp = sum(1, 2);
       printf("call rust sum(1, 2) = %d\n", tmp);
       return 0;
   }

4.在RT-ThreadEnv环境下,使用scons ,编译项目并运行。

$ scons -j6
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
[...]
scons: done building targets.
$ qemu.sh
 \ | /
- RT -     Thread Operating System
 / | \     4.0.4 build Jul 28 2021
2006 - 2021 Copyright by rt-thread team
lwIP-2.1.2 initialized!
[...]
call rust sum(1, 2) = 3

加、减、乘、除

你可以在Rust中实现一些复杂的数学运算。在lib.rs 文件中,使用Rust语言来实现加、减、乘、除。

#![no_std]
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
#[no_mangle]
pub extern "C" fn subtract(a: i32, b: i32) -> i32 {
    a - b
}
#[no_mangle]
pub extern "C" fn multiply(a: i32, b: i32) -> i32 {
    a * b
}
#[no_mangle]
pub extern "C" fn divide(a: i32, b: i32) -> i32 {
    a / b
}
#[panic_handler]
fn panic(_info:&PanicInfo) -> !{
    loop{}
}

建立你的库文件和头文件,并把它们放在应用程序目录中。使用scons ,进行编译。如果在链接过程中出现错误,请在官方Github页面上找到解决方案。

修改rtconfig.py 文件,并添加链接参数--allow-multiple-definition

       DEVICE = ' -march=armv7-a -marm -msoft-float'
       CFLAGS = DEVICE + ' -Wall'
       AFLAGS = ' -c' + DEVICE + ' -x assembler-with-cpp -D__ASSEMBLY__ -I.'
       LINK_SCRIPT = 'link.lds'
       LFLAGS = DEVICE + ' -nostartfiles -Wl,--gc-sections,-Map=rtthread.map,-cref,-u,system_vectors,--allow-multiple-definition'+\
                         ' -T %s' % LINK_SCRIPT
       CPATH = ''
       LPATH = ''

编译并运行QEMU以查看你的工作。

在Rust中调用C语言

Rust可以在C代码中调用,但在Rust代码中调用C呢?下面是一个在Rust代码中调用rt_kprintf C函数的例子。

首先,修改lib.rs 文件。

    // The imported rt-thread functions list
    extern "C" {
        pub fn rt_kprintf(format: *const u8, ...);
    }
    #[no_mangle]
    pub extern "C" fn add(a: i32, b: i32) -> i32 {
        unsafe {
            rt_kprintf(b"this is from rust\n" as *const u8);
        }
        a + b
    }

接下来,生成库文件。

$ cargo build --target=armv7a-none-eabi --release --verbose
Compiling rust_to_c v0.1.0
Running `rustc --crate-name sum --edition=2018 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type staticlib --emit=dep-info,link -C opt-level=3 -C embed-bitcode=no -C metadata=a
Finished release [optimized] target(s) in 0.11s

而现在,为了运行代码,将Rust生成的库文件复制到应用程序目录中,然后重建。

$ scons -j6 scons: Reading SConscript files ... scons: done reading SConscript files. [...]
scons: Building targets ... scons: done building targets.

再次运行QEMU,在你的嵌入式图像中看到结果。

你可以拥有这一切

在你的嵌入式开发中使用Rust,你可以获得Rust的所有功能,而不需要牺牲灵活性或稳定性。今天就在你的嵌入式系统上试试Rust吧。