rust no_std+alloc稳定和使用演示

前言

这篇文章好些天前就想写了, 因为之前一直在追踪stable rust在no_std环境下使用alloc相关的github issue, 前段时间 #102318 这个issue正式合并了, 相关的改动将在rust 1.68中稳定.

no_std下可以使用alloc一方面可以让一些裸机firmware或操作系统内核可以使用堆内存. 另一方面, 一些嵌入式linux的library/app的开发也可以使用no_std + alloc来做, 因为一些嵌入式linux经常在memory和flash空间都比较小的环境, 这种环境如果不使用no_std会导致编译出来的elf过大, no_std + alloc就可以解决这个问题, 这样能使用rust来写一些lib/app. 但是在之前想要使用alloc, 必须依赖unstable的alloc_error_handler feature, 导致其不能在stable上使用.

在stable rust 1.68之后, 当在no_std环境下, alloc失败时默认会直接panic, 不再需要alloc_error_handler. 在stable rust就可以编写no_std + alloc的rust crate了. 这相当于有了在生产环境使用这种模式编写应用的基础了. 下面我们来演示下用no_std + alloc写linux app的方法

no_std linux app

首先安装libc, 另外我们使用libc-print crate来进行println演示, 你也可以自己实现print macro

1
2
$ cargo add libc --no-default-features
$ cargo add libc-print

cargo.toml配置在panic时直接abort, 这样不需要使用unstable的lang item, panic直接abort也符合常规的嵌入式linux的用法:

1
2
3
4
5
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#![no_std]
#![no_main]

use libc_print::std_name::println;

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
unsafe {
libc::abort();
}
}

#[no_mangle]
pub extern "C" fn main() -> i32 {
println!("hello world!");
0
}

这样就可以编译出所需的app了, 文件的体积也非常小. 写rust代码大多数情况下会比用c舒服很多. 就算是在no_std的情况下也是.

no_std + alloc linux app

如果还需要使用alloc, 则我们在引入一个crate, 或者也可以自己去实现一个基于libc的global allocator, 这里为了方便演示还是使用extern crate:

1
cargo add libc_alloc

之后编写如下代码, 代码演示了alloc的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#![no_std]
#![no_main]

extern crate alloc;

use alloc::{boxed::Box, vec};
use libc_alloc::LibcAlloc;
use libc_print::std_name::println;

#[global_allocator]
static GLOBAL_ALLOC: LibcAlloc = LibcAlloc;

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
unsafe {
libc::abort();
}
}

#[no_mangle]
pub extern "C" fn main() -> i32 {
println!("hello world!");
let test1 = vec![1, 2, 3];
for v in test1 {
println!("{v}")
}
let test2 = Box::new(10);
println!("{test2}");
0
}

我们需要使用global_allocator过程宏引入一个全局的分配器, 然后我们需要显式的用extern crate alloc来引入alloc crate, 对于标准库里面的crate, cargo不能自动帮忙引入, 所以需要手动引入. 另外Box, Vec这些在std下的preclude在no_std下是不会preclude的, 所以需要手动导入才能使用.

以上就是no_std + alloc的用法. 相信随着rust 1.68的到来, rust在嵌入式领域会有很多crate慢慢的都不再需要nightly编译器了.


2023-01-15更新:
由于#106045的合并, 导致linux环境下no_std + alloc会编译失败, bare-metal环境不受影响. 目前提了一个新的issue: #106864, 不确定什么时候能合并. 不知道是否会影响到1.68上实现这个功能.


2023-01-20更新:
根据和rust开发者在#106864中的沟通可以发现, 之前在OS based toolchain环境下no_std + alloc可以编译通过存在一些幸运, 这是连接器的GC帮忙把一些不应该引入的eh_personality去掉了, 所以编译通过了. 但是当做一些进一步的测试就能发现, 就算是之前可以编译过的nightly, 引入某些code之后, 也会编译失败. 后续可能需要等rust开发者有时间了之后再来修正这个问题了. 在stable下写有heap支持的linux的嵌入式软件似乎还得再等等了. 具体要等多久就不清楚了. unstable环境下只需要用cargo的build-std功能重新编译libcore是可以编译通过的.