使用 Rust 進行嵌入式開發(fā)
Rust 的高性能、高可靠性和高生產(chǎn)力使它適合于嵌入式系統(tǒng)。
在過去的幾年里,Rust 在程序員中獲得了熱情的追捧。技術(shù)潮流來來去去,所以很難將僅僅因為某項新技術(shù)而產(chǎn)生的興奮與對某項技術(shù)的優(yōu)點的興奮區(qū)分開來,但我認(rèn)為 Rust 是一種真正設(shè)計良好的語言。它的目標(biāo)是幫助開發(fā)者建立可靠和高效的軟件,而且它從一開始就是為這個目的設(shè)計的。你可能聽過一些 Rust 的關(guān)鍵特性,在這篇文章中,我會證明這些特性正是 Rust 也恰好適合嵌入式系統(tǒng)的原因。比如:
- 高性能:它速度快,內(nèi)存利用率高
 - 可靠性:在編譯過程中可以消除內(nèi)存錯誤
 - 生產(chǎn)力:很棒的文檔,友好的編譯器,有用的錯誤信息,以及一流的工具化。它有一個集成的包管理器和構(gòu)建工具,智能的多編輯器支持自動補完和類型檢查、自動格式化等等。
 
為什么使用 Rust 進行嵌入式開發(fā)?
Rust 的設(shè)計是為了保證安全和高性能。嵌入式軟件會出現(xiàn)的問題主要是內(nèi)存的問題。從某種程度上說,Rust 是一種面向編譯器的語言,所以你可以確保在編譯時安全使用內(nèi)存。以下是使用 Rust 在嵌入式設(shè)備上開發(fā)的一些好處:
- 強大的靜態(tài)分析
 - 靈活的內(nèi)存
 - 無畏的并發(fā)性
 - 互操作性
 - 可移植性
 - 社區(qū)驅(qū)動
 
在這篇文章中,我使用開源的 RT-Thread 操作系統(tǒng) 來演示如何使用 Rust 進行嵌入式開發(fā)。
如何在 C 語言中調(diào)用 Rust
在 C 代碼中調(diào)用 Rust 代碼時,你必須將 Rust 源代碼打包成靜態(tài)庫文件。當(dāng) C 代碼編譯時,將其鏈接進去。
用 Rust 創(chuàng)建一個靜態(tài)庫
在這個過程中,有兩個步驟:
1、使用 cargo init --lib rust_to_c 在 Clion 中建立一個 lib 庫。在 lib.rs 中加入以下代碼。下面的函數(shù)計算兩個類型為 i32 的值的總和并返回結(jié)果:
#![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"
交叉編譯
你可以針對你的目標(biāo)平臺進行交叉編譯。假設(shè)你的嵌入式系統(tǒng)是基于 Arm 的,步驟很簡單:
$ rustup target add armv7a-none-eabi
生成靜態(tài)庫文件:
$ cargo build --target=armv7a-none-eabi --release --verboseFresh rust_to_c v0.1.0Finished release [optimized] target(s) in 0.01s
生成頭文件
你也需要頭文件:
1、安裝 cbindgen。cbindgen 工具會從 Rust 庫中生成一個 C 或 C++11 的頭文件:
$ cargo install --force cbindgen
2、在你的項目文件夾下創(chuàng)建一個新的 cbindgen.toml 文件。
3、生成一個頭文件:
$ cbindgen --config cbindgen.toml --crate rust_to_c --output sum.h
調(diào)用 Rust 庫文件
現(xiàn)在你可以對你的 Rust 庫進行調(diào)用了。
1、把生成的 sum.h 和 sum.a 文件放到 rt-thread/bsp/qemu-vexpress-a9/applications 目錄下。
2、修改 SConscript 文件并添加一個靜態(tài)庫:
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、在主函數(shù)中調(diào)用 sum 函數(shù),得到返回值,并 printf 該值:
#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <rtthread.h>#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-Thread Env 環(huán)境中,使用 scons 來編譯項目并運行:
$ scons -j6scons: 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 20212006 - 2021 Copyright by rt-thread teamlwIP-2.1.2 initialized![...]call rust sum(1, 2) = 3
加、減、乘、除
你可以在 Rust 中實現(xiàn)一些復(fù)雜的數(shù)學(xué)運算。在 lib.rs 文件中,使用 Rust 語言來實現(xiàn)加、減、乘、除:
#![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{}}
構(gòu)建你的庫文件和頭文件,并把它們放在應(yīng)用程序目錄中。使用 scons 來編譯。如果在鏈接過程中出現(xiàn)錯誤,請在官方 Github 頁面 中找到解決方案。
修改 rtconfig.py 文件,并添加鏈接參數(shù) --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_SCRIPTCPATH = ''LPATH = ''
編譯并運行 QEMU 來看看你的工作。
在 Rust 中調(diào)用 C 語言
Rust 可以在 C 代碼中調(diào)用,但是如何在你的 Rust 代碼中調(diào)用 C 呢?下面是一個在 Rust 代碼中調(diào)用 rt_kprintf C 函數(shù)的例子。
首先,修改 lib.rs 文件:
// The imported rt-thread functions listextern "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 --verboseCompiling rust_to_c v0.1.0Running `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=aFinished release [optimized] target(s) in 0.11s
而現(xiàn)在,要運行代碼,將 Rust 生成的庫文件復(fù)制到應(yīng)用程序目錄中,然后重新構(gòu)建:
$ scons -j6 scons: Reading SConscript files ... scons: done reading SConscript files. [...]scons: Building targets ... scons: done building targets.
再次運行 QEMU,可以在你的嵌入式鏡像中看到結(jié)果。
你可以擁有這一切
在你的嵌入式開發(fā)中使用 Rust,你可以獲得 Rust 的所有功能,而不需要犧牲靈活性或穩(wěn)定性。今天就在你的嵌入式系統(tǒng)上試試 Rust 吧。關(guān)于嵌入式 Rust 的過程(以及 RT-Thread 本身)的更多信息,請查看 RT-Thread 項目的 YouTube 頻道。請記住,嵌入式也可以是開放的。















 
 
 


 
 
 
 