最简的 #![no_std] 程序

在这部分,我们将写一个可以编译的最简的 #![no_std] 程序。

#![no_std] 是什么意思?

#![no_std] 是一个crate层级的属性,其指出crate将链接到 core 而不是 std crate,然而这对应用来说意味着什么呢?

std crate 是Rust的标准库。它包含的功能需要假设程序将运行在一个操作系统上而不是[直接运行在裸机]上。std 也假设操作系统是一个通用的操作系统,像那些会在服务器和桌面看到的系统。如此,std 在那些经常能在操作系统中遇到的功能:线程,文件,套接字,一个文件系统,进程,等等之上,提供了一个标准的API 。

换句话说,core crate是std crate的一个子集,其不对将运行程序的系统做任何假设。它提供与语言的基本类型,像是浮点数,字符串和切片有关的APIs,也提供像是原子操作和SIMD指令这样的与处理器相关的APIs 。然而它缺少涉及到堆内存分配和I/O有关的APIs。

对于一个应用来说,std 不仅仅只是提供一种方法访问OS抽象。std 在某些情况下,也提供栈溢出保护,处理命令行参数,在一个程序的main函数被启动前打开主线程。一个 #![no_std] 应用缺少上述的所有运行时,因此应用必须在需要的时候初始化它自己的运行时。

由于这些特点,一个 #![no_std] 应用可以成为第一个或者是唯一一个运行在一个系统上的代码。它可以成为许多标准Rust应用无法成为的东西,比如:

  • 一个操作系统的内核。
  • 固件。
  • 一个启动引导。

代码

讲完了,我们可以转向最小的 #![no_std] 程序了:

$ cargo new --edition 2018 --bin app

$ cd app
$ # 把 main.rs 改成这些内容
$ cat src/main.rs
#![allow(unused)]
#![no_main]
#![no_std]

fn main() {
use core::panic::PanicInfo;

#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
    loop {}
}
}

这个程序含有一些在标准的Rust程序中不会出现的东西:

#![no_std] 属性,我们已经讲过了。

#![no_main] 属性,它意味着程序将不会使用标准的 main 函数作为入口。在写这本书的时候,Rust的 main 接口对程序执行的环境做了一些假设: 比如,它假设存在命令行参数,因此,通常它不适合 #![no_std] 程序。

#[panic_handler] 属性。用这个属性标记的函数定义了恐慌的行为,包括库层级的恐慌(core::painc!)和语言层级的恐慌(越界索引)。

这个程序不产生任何有用的东西。事实上,它将产生一个空的二进制项。

$ # 等于 `size target/thumbv7m-none-eabi/debug/app`
$ cargo size --target thumbv7m-none-eabi --bin app
   text	   data	    bss	    dec	    hex	filename
      0	      0	      0	      0	      0	app

在链接之前,crate 包含了恐慌函数的符号。

$ cargo rustc --target thumbv7m-none-eabi -- --emit=obj

$ cargo nm -- target/thumbv7m-none-eabi/debug/deps/app-*.o | grep '[0-9]* [^N] '
00000000 T rust_begin_unwind

不过,它是我们的起点。在下一个部分,我们将搭建一些有用的东西。但是在继续之前,让我们设置一个默认的编译目标避免每次调用Cargo不得不传递--target标志。

$ mkdir .cargo

$ # 把 .cargo/config 改成这些内容
$ cat .cargo/config
[build]
target = "thumbv7m-none-eabi"

eh_personality

如果你配置的不是在恐慌时无条件终止(译者注:panic = "abort"),大多数的有完整的操作系统的目标平台都不是(或者如果你的 客制目标平台 不包含 "panic-strategy": "abort"),那么你必须告诉Cargo要怎么做或者添加一个 eh_personality 函数,后者需要nightly版的编译器。这里是关于它的Rust文档这里是一些关于它的讨论.

在 Cargo.toml 中, 添加:

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

或者,声明 eh_personality 函数。它很简单的没啥特别的,展开时和下面一样:

#![allow(unused)]
#![feature(lang_items)]

fn main() {
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
}

如果没有这么做,你将会收到这个错误 language item required, but not found: 'eh_personality'