☰
RT-Thread Rust 过程宏(rt_macros)实战指南:no_std 环境下导出主线程、初始化段与 Shell 命令
2026/10/4 1:58:19 网站建设 项目流程
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/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!
FSymTabShell 命令表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 生成的展开代码更复杂,包含:

  1. 参数解析包装函数#[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 命令函数;
  2. 字符串数据:#[link_section = ".rodata.name"]下的[u8; N]静态字节数组,分别保存\0结尾的命令名与描述;
  3. 命令描述结构体:#[repr(C)] struct __{name}_cmd_seg_struct { name, desc, opt, func },字段布局与 RT-Threadstruct msh_command(msh命令表项)对齐;
  4. 命令表条目:#[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/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载
上一篇:CANN/Ascend C SIMD浮点转整型
下一篇:终极虚幻引擎dump工具:UEDumper完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询