- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
本篇技术指南以 RT-Thread 仓库中 components/rust/docs/5.rt-macro/README.md 为骨架,系统讲解rt_macros过程宏库的设计目标、四个核心宏(rt_thread_main!、rt_component_export!、rt_app_export!、msh_cmd_export!)的签名约束、链接段机制与完整使用示例,并深入其源码实现(rt_macros)与配套示例工程,帮助读者在no_std的嵌入式场景中,把 Rust 函数无缝接入 RT-Thread 的启动初始化流程与 msh 命令行体系。
背景与目标:为什么no_std下需要过程宏导出入口
RT-Thread 是开源物联网实时操作系统(RTOS),其内核与组件由 C 语言编写,启动流程依赖约定的链接段(link section)机制:系统复位后按阶段遍历各初始化段并依次调用注册的函数指针。Rust 侧要在这样的环境中工作,面临一个基本问题——no_std模式下不存在传统意义上的main函数,RT-Thread 无法直接"看见"Rust 逻辑的入口。
rt_macros正是为解决这一问题而设计的 Rust 过程宏库:它在编译期把用户编写的普通 Rust 函数"翻译"为符合 C ABI 的包装入口,并生成放入特定链接段的数据条目,使 RT-Thread 能在启动期(初始化段扫描)或命令表扫描阶段发现并调用对应的 Rust 逻辑。宏库入口定义于 components/rust/rt_macros/src/lib.rs,四个属性宏分别委托给 components/rust/rt_macros/src/macros/mod.rs 下的main、component、app、cmd四个模块实现。
提供的宏:与 RT-Thread 声明机制的对应关系
rt_macros共提供四个#[proc_macro_attribute]属性宏,全部对齐 RT-Thread 的初始化声明与 Shell 命令声明约定。
1.rt_thread_main!:Rust 主线程入口
- 函数签名:
fn()(无参数、无返回值)。 - 约束:不支持
const、unsafe、async、变参;不可使用显式 ABI;不可含泛型;函数可见性必须为默认(非pub)。 - 作用:导出可由 RT-Thread 识别并调用的主线程入口,通常对应
main函数。
值得说明的是,从源码 main.rs 可以看到,宏还会解析可选的name属性;若未提供name,会直接报错 "must have attributename"。因此该宏实际推荐配合name = "..."使用,用于生成内部符号名(如__{name}_main_func)。
2.rt_component_export!:组件初始化入口
- 函数签名:
fn(),约束同主线程宏。 - 可选参数:
name = "...",用于生成内部符号名(宏要求必填)。 - 链接段:将函数指针落入
.rti_fn.4段,供 RT-Thread 组件初始化阶段遍历调用。
3.rt_app_export!:应用初始化入口
- 函数签名:
fn(),约束同上。 - 可选参数:
name = "..."。 - 链接段:将函数指针落入
.rti_fn.6段,供 RT-Thread 应用初始化阶段调用。
4.msh_cmd_export!:Shell 命令导出
- 函数签名:
fn(args: vec::IntoIter<rt_rust::param::ParamItem>),即接收一个参数迭代器。 - 属性参数:
name(必填,命令名)、desc(可选,命令描述,缺省为"No desc")。 - 链接段与数据:
- 生成命令描述结构体并落入
FSymTab段; - 命令名与描述以
\0结尾的字节数组形式落入.rodata.name段。
- 生成命令描述结构体并落入
- 调用封装:生成
extern "C"包装函数(argc, argv),把argv转换为Vec<ParamItem>后交给原始 Rust 命令函数处理。
链接段与启动流程:初始化与命令扫描机制
RT-Thread 通过固定的链接段组织初始化条目与命令表,rt_macros生成的条目落入以下四个段:
| 段名 | 用途 | 对应宏 |
|---|---|---|
.rti_fn.4 | 组件初始化入口 | rt_component_export! |
.rti_fn.6 | 应用初始化入口 | rt_app_export! |
FSymTab | Shell 命令表 | msh_cmd_export! |
.rodata.name | 命令名 / 描述字符串 | msh_cmd_export! |
RT-Thread 在启动或命令表扫描时,会遍历这些段对应的条目并完成注册或调用,从而将 Rust 编写的逻辑纳入系统。这意味着:只要链接脚本保留了这些段(RT-Thread BSP 的链接脚本普遍保留FSymTab与.rti_fn.*段),Rust 导出的初始化函数和命令就会与 C 侧INIT_COMPONENT_EXPORT、INIT_APP_EXPORT、MSH_CMD_EXPORT声明的条目一样被自动发现。
从源码看初始化宏的展开形态
以组件导出宏为例,component.rs 生成的展开代码包含三个关键部分:
- 一个
#[no_mangle] pub unsafe extern "C" fn __{name}_component_func() -> i32包装函数,内部调用原始 Rust 函数并返回0; - 一个包着函数指针的
struct __{name}_component_seg_struct(*const ()),并为其实现unsafe impl Sync; - 一个标记了
#[link_section = ".rti_fn.4"]的static条目,将包装函数地址以*const ()形式放入段中。
应用导出宏 app.rs 的展开形态完全一致,只是链接段改为.rti_fn.6。这一设计让 Rust 侧初始化函数与 C 侧INIT_COMPONENT_EXPORT/INIT_APP_EXPORT走完全相同的注册通道。
从源码看命令宏的展开形态
命令导出宏 cmd.rs 生成的展开代码更复杂,包含:
- 参数解析包装函数
#[no_mangle] pub unsafe extern "C" fn __{name}_main_func(argc: u32, argv: *const *const u8):遍历argv指针数组,逐项按\0计算长度,通过ParamItem::new(core::slice::from_raw_parts::<'static, _>(...))构造ParamItem,收集为Vec<ParamItem>后调用原始 Rust 命令函数; - 字符串数据:
#[link_section = ".rodata.name"]下的[u8; N]静态字节数组,分别保存\0结尾的命令名与描述; - 命令描述结构体:
#[repr(C)] struct __{name}_cmd_seg_struct { name, desc, opt, func },字段布局与 RT-Threadstruct msh_command(msh命令表项)对齐; - 命令表条目:
#[link_section = "FSymTab"] static,其中func指向一个返回i32的extern "C"包装__wrap_main。
其中opt字段固定为core::ptr::null()。命令名与描述通过Literal::byte_string直接生成字节字面量(cmd.rs),保证\0结尾且内容精确。
参数类型:ParamItem
命令函数接收的参数类型定义在 components/rust/core/src/param.rs:
#[derive(Debug)] pub struct ParamItem(&'static [u8]); impl Deref for ParamItem { type Target = [u8]; fn deref(&self) -> &Self::Target { return self.0; } } impl ParamItem { pub fn new(raw: &'static [u8]) -> Self { ParamItem(raw) } } pub type Param = <Vec<ParamItem> as IntoIterator>::IntoIter;ParamItem封装一段'static的字节切片(对应 msh 传入的单个命令行参数),通过Deref可以直接按&[u8]使用;Param则是Vec<ParamItem>的迭代器别名,即命令函数实际收到的参数类型。
使用示例:四种宏的完整写法
以下示例均可在 components/rust/examples 目录下找到对应工程。
主线程入口
use rt_macros::rt_thread_main; #[rt_thread_main(name = "main")] fn main() { // 在此编写主线程逻辑 }组件导出
use rt_macros::rt_component_export; #[rt_component_export(name = "rust_component_registry")] fn my_component_init() { // 组件初始化逻辑 }仓库自带的示例工程 examples/component/component_registry/src/lib.rs 展示了真实用法:在enable-logfeature 开启时,用#[rt_component_export(name = "rust_component_registry")]导出统一组件注册入口,函数体内调用println!与log!、info!、warn!等日志宏;feature 未开启时则提供一个空实现的pub extern "C" fn component_init()保证库仍可链接。
应用导出
use rt_macros::rt_app_export; #[rt_app_export(name = "rust_app_example")] fn my_app_init() { // 应用初始化逻辑 }Shell 命令导出
use rt_macros::msh_cmd_export; #[msh_cmd_export(name = "hello", desc = "Say hello")] fn hello_cmd(args: vec::IntoIter<rt_rust::param::ParamItem>) { // 命令处理逻辑,例如解析 args 并打印输出 }与 C 代码的交互
当需要从 C 侧调用导出的 Rust 入口时,应在 C 侧声明原型并使用extern "C"指定调用约定,例如:
extern "C" void rust_function_name(void);命令导出宏会生成(argc, argv)形式的extern "C"包装函数(__{name}_main_func),RT-Thread 的 msh 命令系统通过FSymTab表中的func字段调用该包装,并将命令行参数经argc/argv传入;包装函数内部完成argv到Vec<ParamItem>的转换,再把参数迭代器交给原始 Rust 函数。Rust 侧命令函数因此拿到的是安全、类型化的参数视图,而非裸指针。
常见问题与诊断
- 编译期报错:宏会在函数签名不符合约束时给出明确的错误信息。例如
rt_thread_main!/rt_component_export!/rt_app_export!要求fn()(见 main.rs 等处的签名检查),msh_cmd_export!要求fn(args: vec::IntoIter<rt_rust::param::ParamItem>)(见 cmd.rs)。请按上述签名要求调整函数。 name属性缺失:三个入口类宏与命令宏都要求name属性,缺失时宏会直接报错 "must have attributename"。- 可见性要求:入口函数应保持默认(非
pub)可见性,以满足宏的签名约束检查(源码中通过f.vis == Visibility::Inherited校验)。 alloc依赖:Shell 命令宏内部使用alloc::vec::Vec,需确保运行环境提供全局分配器(RT-Thread 通常可满足)。rt_rust核心库的分配器实现在 components/rust/core/src/allocator.rs。- 链接段保留:若导出条目未被 RT-Thread 发现,请确认链接脚本保留了
.rti_fn.4、.rti_fn.6、FSymTab、.rodata.name段,且未因链接优化(如--gc-sections)被回收。
进一步阅读
- 宏库入口与模块划分:components/rust/rt_macros/src/lib.rs、components/rust/rt_macros/src/macros/mod.rs
- 四个宏的独立实现:main.rs、component.rs、app.rs、cmd.rs
- 参数类型与运行时支持:components/rust/core/src/param.rs、components/rust/core/src/allocator.rs
- 完整示例工程:components/rust/examples/application(thread / mutex / semaphore / queue / param / fs / loadlib)、components/rust/examples/component/component_registry、components/rust/examples/modules/example_lib
- Rust 支持相关文档:components/rust/docs/2.applications/README.md、components/rust/docs/3.components/README.md、components/rust/docs/4.modules/README.md
- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
相关推荐
RT-Thread Rust 宏库 rt_macros 实战指南:no_std 入口导出与 MSH 命令注册
RT Thread Rust 宏库 rt_macros 实战指南:no_std 入口导出与 MSH 命令注册 rt_macros 是 RT Thread Rus
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread 中的 Rust 用户应用开发实战:从例程编译到命令导出
RT Thread 中的 Rust 用户应用开发实战:从例程编译到命令导出 本篇技术指南以 RT Thread 仓库中 components/rust/docs
操作系统嵌入式物联网嵌入式OSRTOSpowerline-config 命令完全指南:tmux 与 Shell 配置探测、环境初始化的实战解析
powerline config 命令完全指南:tmux 与 Shell 配置探测、环境初始化的实战解析 powerline config 是 Powerlin
开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考