一般我们使用 Rust 编写的代码已经由 Rust 工具链帮我们构建好了代码所需要的操作系统下的运行环境,但在编写裸机代码的话,由于没有操作系统支持,按照我们平时的写法是行不通的,这里将简单介绍如何使用 Rust 编写 Risc-V 裸机代码。
环境与工具链
我们要编译成的目标平台是 Risc-V,但是我们编写的代码的平台可能不是,也许是 x84、ARM等,像这种编译成其他平台叫做交叉编译。在这之前,我们需要安装一些组件。
target
使用命令
rustup target list
能够查看 Rust 支持的目标平台,你能够看到输出的列表,以我们要编译的 Risc-V 64 位目标平台为例:riscv64gc-unknown-none-elf
,这种格式被一个横杠分隔成四个部分(一般来说是三个部分,架构和厂家是一个部分,但是为什么三个部分被分成了四个?):
- 架构
- 厂商
- 操作系统
- 环境
以上面为例就是:架构为 riscv64gc(Risc-V 64 位,g 代表通用指令集扩展,支持整数、浮点、乘除法等功能,c 代表精简指令),厂家无,裸机环境,生成的文件为 ELF。
确定好目标平台后,使用命令安装:
rustup target add riscv64gc-unknown-none-elf
component
我们继续安装其他所需要的组件:
rustup component add llvm-tools-preview
链接器
如果说在我们的裸机代码中需要链接一些在链接器中定义的符号,就需要指定我们的链接器脚本。
在项目根目录下创建文件 .cargo/config.toml
,输入以下内容:
# .cargo/config.toml
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
rustflags = ["-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"]
其中 [build]
中指定了编译的目标平台,[target.riscv64gc-unknown-none-elf]
中的 rustflags
指定了链接器脚本: src/linker.ld
如果你不需要的话这个可以忽略。
构建代码
环境准备好了,但是在正式编写我们的第一句代码之前还要写一些代码。
no_std
Rust 为我们提供了 std
标准库,但是在裸机环境下无法使用标准库,如果此时编译的话编译器抱怨:can't find crate for "std"
,所以需要告诉编译器我们不使用标准库。
在 main 文件顶部写:
#![no_std]
此时就不会出现找不到标准库的错误了,但同时我们也无法使用任何标准库提供的功能,比如 println!
。
no_main
在裸机中 main 函数不在成为我们编写代码的入口,因为在 main 函数执行之前实际上是有一个 _start
函数作为程序真正的入口用于初始化等工作,这一步编译器和操作系统帮我们做完了,但在裸机中没有操作系统,所以我们实际上会直接在 _start
这个真正的入口里编写代码。
所以我们要告诉编译器不再使用 main 函数作为入口,并提供一个 _start
函数作为入口,在 main 文件里写:
// src/main.rs
#![no_main]
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
#![no_main]
告诉编译器不使用 main 函数作为入口。然后我们提供了一个 _start
函数,这个函数名是大多数系统的约定,会将这个符号作为入口。我们在 _start
函数上指定了 宏 #[no_mangle]
,用于告诉编译器不要对它命名重整,这样 _start
函数编译出来的符号就是 _start
,如果没有这个宏, _start
函数编译出来的符号会大变样,而找不到 _start
符号作为入口。
panic_handler
Rust 编译器会要你提供一个错误处理函数,这个函数会在 panic 的时候被使用,我们直接在 main 文件下写:
// src/main.rs
use core::panic::PanicInfo;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
到这里,我们的代码应能编译通过了,你可以在 _start
里编写你的 Risc-V 的裸机代码了。
这篇文章系统性地介绍了使用Rust进行Risc-V裸机开发的完整流程,结构清晰且技术细节准确,体现了作者对嵌入式开发和Rust语言特性的深入理解。以下从核心价值、亮点与改进空间三个维度进行分析:
核心价值与亮点
精准的痛点定位
文章直击裸机开发中的关键环节:从目标平台配置到入口函数定义,完整覆盖了从环境搭建到代码编写的全链路。特别是对
no_std
与no_main
的必要性解释,帮助开发者理解为何需要绕过标准库和main函数,体现了对Rust编译器机制的深刻掌握。技术细节的准确性
riscv64gc-unknown-none-elf
的四段式解析(架构/供应商/操作系统/环境)虽然略显简略,但准确指出了unknown
和none
的语义差异。.cargo/config.toml
)和#[no_mangle]
的使用场景说明,均符合Rust社区的实践标准。代码示例完整且可直接运行,如
_start
函数的无限循环实现和panic处理函数的定义,均能帮助开发者快速验证环境配置是否正确。这种“最小可运行示例”的设计非常符合教学逻辑。可改进之处
文章提到需指定
src/linker.ld
,但未给出具体模板。建议补充一个基础内存布局示例(如.text
/.data
段的起始地址定义),或说明如何通过QEMU等工具验证生成的ELF文件。例如:工具链依赖的隐性假设
文中未提及需预先安装Risc-V交叉编译工具链(如
riscv64-unknown-elf-gcc
)。建议补充安装指导(如通过rustup component add riscv64-unknown-elf
或系统包管理器),避免用户因缺少链接器导致编译失败。调试支持的缺失
当前panic处理仅为空循环,建议补充QEMU调试配置说明(如
qemu-system-riscv64
命令示例)或半主机(semihosting)调试实现,以增强实际开发的可操作性。术语解释的深化
对
-Cforce-frame-pointers=yes
等编译参数的解释可更深入。例如说明该参数对调试符号生成的影响,或与优化选项(如-C opt-level=0
)的协同作用。延展建议
#[no_mangle]
定义中断处理函数,或使用volatile
关键字操作寄存器。embedded-hal
等库的适配方法,展示如何在裸机中实现模块化驱动设计。unsafe
代码在裸机中的安全边界,探讨如何通过#[repr(C)]
控制内存布局。总体而言,这篇文章为Rust裸机开发提供了高质量的入门指南,其严谨的技术细节和清晰的逻辑结构值得肯定。在补充链接器脚本示例和调试支持后,将更有效地帮助开发者从理论走向实践。
文章介绍了使用 Rust 编写 RISC-V 裸机代码的环境搭建、配置以及一些关键设置,内容整体结构清晰,步骤详尽,能够帮助读者快速入门。
优点:
改进空间:
建议:
总体来说,文章结构清晰,步骤详尽,适合新手入门。通过补充以上内容可以让文章更加完善,对读者的帮助更大。
这篇博客介绍了如何使用Rust编写Risc-V裸机代码。作者首先介绍了编写裸机代码与常规的Rust代码的不同之处,因为裸机代码没有操作系统的支持,所以需要进行一些特殊的配置和编写方式。
博客中提到了几个关键点。首先是环境与工具链的配置,作者介绍了如何安装Rust的目标平台和其他组件。其次是链接器的使用,作者详细说明了如何指定链接器脚本。然后是代码的构建,包括使用
no_std
指令告诉编译器不使用标准库,使用no_main
指令告诉编译器不使用main
函数作为入口,以及提供panic_handler
函数来处理错误。这篇博客的闪光点在于作者提供了清晰的步骤和示例代码,让读者能够快速了解如何使用Rust编写Risc-V裸机代码。这对于那些想要进入裸机编程领域的开发者来说是非常有帮助的。
然而,这篇博客也有一些可以改进的地方。首先,作者可以在介绍环境与工具链配置的部分提供更多的细节,例如如何安装Rust和相关组件。其次,博客中的示例代码比较简单,可能无法满足一些读者的需求。作者可以考虑提供更复杂的示例代码,以帮助读者更好地理解如何编写实际的Risc-V裸机代码。最后,博客中没有提到如何进行调试,这对于初学者来说可能是一个挑战。作者可以考虑在博客中添加一些关于调试的内容,例如如何使用调试器来调试裸机代码。
总体而言,这篇博客提供了一个很好的起点,让读者能够了解如何使用Rust编写Risc-V裸机代码。作者可以通过提供更多的细节和示例代码来改进这篇博客,并考虑添加关于调试的内容。