RT_thread的工作原理及应用
一、 RT_thread的工作原理
1. RT-Thread 的工作原理基础函数说明
RT-Thread 作为实时操作系统(RTOS),任务(线程)是其核心调度单元,涉及关键函数:
rt_thread_create
:用于创建任务(线程) ,会为任务分配运行所需的内存空间(如栈空间),并设置任务初始状态(比如就绪、挂起等),让任务具备独立运行的 “硬件” 基础。rt_thread_startup
:作用是将创建好的任务添加至系统就绪队列 ,任务进入就绪队列后,RT-Thread 的调度器就可以依据调度规则(如优先级、时间片等),安排任务获得 CPU 运行权。
2. RT-Thread 启动流程整体逻辑
了解代码常从启动环节切入,RT-Thread 支持多种硬件平台(如 STM32、RK3568 等)和编译器(MDK - ARM、GCC 等 ),rtthread_startup()
是它规定的统一启动入口 ,执行顺序为:
系统先执行底层启动文件(不同平台启动文件不同,像 MDK - ARM 下的汇编启动文件 ),完成最基础的硬件初始化(如栈、中断向量表等),接着进入 RT-Thread 启动函数 rtthread_startup()
,完成操作系统内核相关初始化(如定时器、调度器、内存管理等 ),最后才进入用户编写的 main()
函数,让用户代码开始执行业务逻辑。
3. 裸机(以 MDK - ARM + STM32 为例 )与 RT-Thread 启动流程对比
- 裸机启动流程:
以常见的 MDK - ARM 开发环境、STM32 芯片为例,用户程序入口是main()
函数,代码一般放在main.c
文件。系统上电或复位后,先从汇编启动文件(如startup_stm32f103xe.s
)开始运行 :- 初始化堆栈:设置栈的大小和起始地址,栈是函数调用、局部变量存储的关键区域,决定了函数嵌套、局部变量使用的 “可用空间”。
- 配置中断向量表:中断向量表记录了不同中断对应的处理函数地址,系统响应中断时,会依据此表找到并执行对应的中断服务程序,确保按键中断、定时器中断等能被正确处理。
- 基础系统初始化与跳转 main:执行
SystemInit
函数(ST 官方库函数 ),里面会调用SetSysClock()
配置系统时钟,还会初始化其他基础硬件;完成后,汇编代码通过跳转指令,进入main()
函数,让用户代码开始执行。
- RT-Thread 启动延伸:
在 RT-Thread 环境下,启动流程前期和裸机类似(执行汇编启动文件、基础硬件初始化 ),但后续进入rtthread_startup()
函数,它会额外做很多内核初始化工作:比如初始化线程调度器,让多任务能被调度;初始化内核对象(如信号量、互斥锁 );创建系统空闲线程(没其他任务运行时,空闲线程执行,用于系统低功耗、资源回收等 )。完成这些后,才最终进入用户main()
函数,此时系统已具备多任务调度能力,能更好地支持复杂程序逻辑。
4. 关于 “时钟为什么是 72MHz(以 STM32F103 为例常见情况 )”
以 STM32F103 系列芯片为例,系统时钟配置为 72MHz ,通常是因为:
- 硬件设计与芯片手册推荐:ST 官方针对 STM32F103 系列,在参考手册、开发指南里,会给出基于其内部 PLL(锁相环 )、晶振(一般外接 8MHz 无源晶振 )的时钟配置方案,通过合理设置 PLL 倍频、分频系数,能稳定输出 72MHz 系统时钟,这是经过验证、兼容性和稳定性较好的配置。
- 外设兼容性平衡:72MHz 的系统时钟,能较好适配 STM32F103 大部分外设需求。比如 SPI 总线、I2C 总线、USART 串口等,在 72MHz 系统时钟下,通过设置对应的分频器,可让外设工作在合适频率(如 USART 常用的 9600、115200 波特率 ),既保证外设性能,又不会因时钟过高导致信号干扰、稳定性下降。
- 性能与功耗 trade-off:72MHz 是一个在性能和功耗间取得较好平衡的频率。相比更低频率(如 36MHz ),它能让 CPU 更快执行指令,提升程序运行效率;对比更高频率(如 100MHz 以上,部分 STM32 高配置型号支持 ),功耗增加相对可控,适合很多对性能、功耗都有要求的嵌入式场景(如工业控制、消费电子 )。
不同 STM32 型号、不同应用场景,系统时钟也可能配置成其他值(比如有些对性能要求极高的场景,会配置到芯片支持的最高频率 ),但 72MHz 是 STM32F103 系列非常经典、常用的系统时钟配置。
5. 单片机的时钟源
单片机的时钟源是系统运行的 “心跳”,决定了指令执行速度、外设工作频率,主要分内部时钟和外部时钟两类,核心差异聚焦在 “精度、成本、适用场景” :
1. 内部时钟(基于 LC 振荡器 )
- 原理:
单片机内部集成 LC(电感 + 电容 )振荡电路,通电后利用 LC 回路的充放电特性,产生周期性的振荡信号,经分频、倍频等处理后,作为系统时钟。但 LC 振荡受温度、电压波动、元件自身特性影响大,频率稳定性差(比如温度变化时,电容、电感参数改变,振荡频率会漂移 )。- 常见类型:
- HSI(高速内部时钟 ):频率一般为 8MHz(不同单片机有差异,部分 MCU 可能是 16MHz、4MHz 等 ),可直接作为系统时钟,或给高速外设(如 USART 串口、SPI 总线 )提供基础时钟。因精度有限,不适合对时序要求极高的场景(比如高精度通信、RTC 实时时钟 ),但胜在无需外接元件,简化硬件设计、降低成本,常用于快速原型开发、对时钟精度要求不高的小项目。
- LSI(低速内部时钟 ):频率通常为 40kHz 左右,主要给低功耗外设、独立看门狗(IWDG )、实时时钟(RTC,部分 MCU 会用 )提供时钟。看门狗依靠它计时,监测程序是否跑飞;低功耗模式下,用 LSI 驱动定时器,能减少系统功耗,让单片机在电池供电场景更耐用。
2. 外部时钟(基于石英晶体振荡器 )
核心痛点:内部时钟精度不足
很多电子设备(比如通信模块、实时时钟 )需要频率高度稳定的时钟信号。内部 LC 振荡器受环境影响大,频率容易 “漂移”(比如温度变化 10℃,频率可能偏差几千赫兹 ),无法满足高精度需求。这就需要更稳定的时钟源 ——石英晶体振荡器。石英晶体的 “稳频” 原理
石英晶体是一种压电材料,给它施加电压,会产生机械振动;反过来,机械振动又会产生电信号。且石英晶体有固有谐振频率(由晶体切割方式、尺寸决定 ),当外加信号频率接近这个固有频率时,会发生 “谐振”,振动幅度最大、能量损耗最小,输出信号频率高度稳定。利用这一特性,把石英晶体接入振荡电路,就能产生精准、稳定的时钟信号,频率偏差可控制在几十 ppm(百万分之一 )甚至更低,远优于内部 LC 振荡器。常见外部时钟类型
- HSE(高速外部时钟 ):
由外接的石英晶振(4 - 16MHz 常见,比如 STM32 常用 8MHz 晶振 ) + 振荡电路组成。晶振产生的基础频率,可通过单片机内部 PLL(锁相环 )倍频、分频,得到更高或更灵活的时钟。比如 STM32 中,8MHz HSE 经 PLL 倍频 9 倍,可输出 72MHz 系统时钟,让 CPU 以更高频率运行,提升指令执行速度,同时给高速外设(如 USB 模块、以太网 )提供精准时钟。因精度高、频率可调,是中大型项目、对时序要求严格场景的首选(比如工业控制、通信设备 ),缺点是需外接晶振、负载电容,增加硬件成本和 PCB 面积。- LSE(低速外部时钟 ):
外接 32.768kHz 石英晶振,专门给实时时钟(RTC ) 提供时钟。32.768kHz 是因为它分频后能精准匹配 “秒” 的计时(32768 = 2¹⁵ ,分频 15 次后得到 1Hz ,即 1 秒 ),让 RTC 实现高精度时间计数。常用于需要记录时间戳、定时唤醒的场景(如智能手表、电表、环境监测设备 ),是 RTC 功能的 “黄金搭档”。
二、 RT_thread启动
一、核心函数与基础作用
在 RT-Thread 操作系统中,
rtthread_startup
是系统启动的核心入口,承担着 “初始化硬件、搭建内核环境、创建关键线程、启动调度器” 的重任,是系统从 “硬件启动” 到 “多线程运行” 的关键桥梁。1.
rt_hw_board_init()
—— 硬件初始化 “基石”
核心职责:
这是整个系统启动的 “硬件准备阶段”,主要完成与硬件强相关的基础初始化,让单片机从 “裸机状态” 过渡到能支持 RT-Thread 运行的环境。常见工作包括:
- 系统时钟配置:
调用芯片厂商提供的时钟配置函数(如 STM32 的SystemInit
+SetSysClock
),把内部 / 外部时钟(HSI/HSE )、PLL 锁相环配置好,让 CPU 、外设运行在指定频率(比如 72MHz ),这是系统 “跑起来” 的基础(指令执行速度、外设时序都依赖时钟 )。- 串口初始化 + 终端绑定:
初始化串口硬件(配置波特率、数据位、停止位、奇偶校验 ),并将 RT-Thread 的 “系统输出终端” 绑定到该串口。后续系统运行信息(比如线程创建日志、调度器信息、用户打印的调试内容 ),会通过这个串口发送到上位机(如串口助手 ),方便开发者调试、监控系统状态。- 其他硬件初始化(可选 ):
部分场景下,还会初始化 LED 、按键、定时器等基础外设(如果项目需要一启动就控制硬件 ),或配置电源管理、 watchdog 等,让硬件处于 “可用状态”。代码定位:
通常位于board.c
文件(RT-Thread 工程中,board
目录下的硬件相关代码 ),不同芯片平台(如 STM32 、ESP32 )的实现差异大,需结合芯片手册、BSP(板级支持包 )编写。比如 STM32 平台,会调用标准外设库或 HAL 库的函数配置时钟、串口。2.
rt_thread_create()
—— 线程创建 “工具”
功能本质:
这是 RT-Thread 中动态创建线程(任务 ) 的 API ,用于在运行时为业务逻辑分配独立的 “运行空间”。调用时,需传入:
- 线程名称(字符串,用于调试识别 );
- 线程入口函数(线程要执行的代码逻辑,类似
void thread_entry(void *parameter)
);- 线程参数(传递给入口函数的数据,可自定义 );
- 线程栈大小(决定线程能嵌套调用多少函数、存储多少局部变量 );
- 线程优先级(决定线程被调度器选中的 “竞争权重”,数值越小优先级越高 );
- 线程初始状态(如
RT_THREAD_INIT
表示初始化完成但未启动 )。执行效果:
调用后,RT-Thread 会在内存中为线程分配栈空间、构建线程控制块(TCB ,记录线程状态、优先级、栈指针 ),并返回线程句柄(用于后续控制,如启动、挂起 )。但创建后线程不会立即运行,需调用rt_thread_startup()
或类似 API ,把线程加入 “就绪队列”,等待调度器分配 CPU 时间。3.
rtthread_startup(void)
—— 系统启动 “总流程”这是 RT-Thread 规定的统一启动入口,所有基于 RT-Thread 的程序,最终都会从硬件启动代码(如汇编
startup
文件 )跳转到这里,按 “四阶段” 完成系统初始化与启动:阶段 1:初始化与系统相关的硬件(
rt_hw_board_init()
主导 )
- 作用:
如前所述,完成时钟、串口、基础外设的初始化,让硬件具备 “运行条件”。这一步是系统启动的 “地基”,若硬件没正确初始化(比如时钟配置错误 ),后续内核、线程都无法正常工作。阶段 2:初始化系统内核对象(定时器、调度器、信号 )
关键函数:
rt_system_timer_init()
:初始化系统定时器(RT-Thread 的软件定时器 ),为定时任务、延时函数提供基础;rt_system_scheduler_init()
:初始化线程调度器,构建就绪队列、优先级表,让调度器能识别线程状态、按规则(如优先级抢占、时间片轮转 )分配 CPU ;rt_system_signal_init()
:初始化信号机制,用于线程间异步通信(类似 Linux 的信号 ),让一个线程能给另一个线程发送 “事件通知”(如异常处理、异步触发逻辑 )。隐藏逻辑:
这些初始化会在内存中构建内核数据结构(如定时器链表、调度器的优先级数组 ),为后续多线程运行、定时任务提供 “规则与容器”。阶段 3:创建 main 线程,初始化各类模块
核心逻辑:
通过rt_application_init()
函数,内部调用rt_thread_init(main_thread_entry)
,创建一个名为 “main” 的线程(或类似命名 ),并把main_thread_entry
设为线程入口。这个线程的核心工作是:
- 依次初始化 RT-Thread 的 “组件”(如
rt_components_init()
,会调用device_init
、components_init
等 ),完成设备驱动(如 LED 、传感器 )、文件系统、网络协议栈等高级功能的初始化;- 最终跳转到用户编写的
main()
函数,让开发者可以编写业务逻辑(比如创建自定义线程、初始化业务模块 )。为什么要 “封装” main 线程?
RT-Thread 是多线程系统,需要统一的 “用户入口管理”。通过创建 main 线程,把用户main()
函数放到线程中执行,能让调度器统一管理(比如设置 main 线程优先级、动态调整 ),也方便在系统启动阶段,先完成内核、组件初始化,再执行用户代码,避免冲突。阶段 4:初始化定时器线程、空闲线程,启动调度器
- 关键步骤:
rt_system_timer_thread_init()
:创建并初始化 “定时器线程”,专门处理软件定时器的超时事件(比如定时 1s 触发的任务 ),让定时器逻辑和业务线程解耦,提升系统响应效率;rt_thread_idle_init()
:创建 “空闲线程”,这是系统优先级最低的线程,当所有其他线程都处于阻塞状态时,空闲线程会被调度器选中运行。它的主要作用是 “回收资源”(比如清理线程残留的内存 )、“低功耗处理”(如让 CPU 进入休眠模式,降低系统功耗 );rt_system_scheduler_start()
:启动调度器,这是系统真正 “活起来” 的标志!调度器会扫描 “就绪队列”,选择优先级最高的线程(如 main 线程、定时器线程 ),把 CPU 执行权交给它。从此,系统进入 “多线程并发运行” 状态(实际是调度器快速切换线程,模拟并行 )。三、启动流程的 “时间线” 与关键特性
理解
rtthread_startup
的四阶段后,要注意这些细节:1. “线程创建后不立即运行” 的本质
调用
rt_thread_create()
创建线程,只是在内存中 “准备好运行环境”(分配栈、填 TCB ),但线程状态是 “初始化态”。必须通过rt_thread_startup()
(或rt_thread_resume()
)把线程加入 “就绪队列”,调度器才会在合适时机(比如当前线程阻塞、更高优先级线程就绪 )切换到它执行。这保证了系统启动时,能按 “先初始化内核、再跑业务线程” 的顺序执行,避免混乱。2. 调度器启动的 “分水岭”
在
rt_system_scheduler_start()
调用前,系统处于 “单线程初始化阶段”(代码按顺序执行rt_hw_board_init()
、内核对象初始化 );调用后,调度器接管 CPU ,系统进入 “多线程调度阶段”,线程会根据优先级、状态动态切换,真正实现 RT-Thread 的 “实时调度” 能力。3. 用户
main()
函数的 “线程化” 运行用户编写的
main()
函数,实际是在 “main 线程” 中执行的。这个线程的优先级、栈大小等,可在rt_application_init()
中配置(或通过rtconfig.h
宏定义调整 )。如果需要让main()
函数里的逻辑 “更灵活”(比如动态创建线程、延时 ),完全可以利用 RT-Thread 的线程 API ,因为此时调度器已经启动,多线程机制已经生效。
三、 内存分布
一、内存分布:程序与数据的 “存储地图”
在嵌入式系统(或任何程序运行时)中,内存分布决定了 “代码、数据、变量” 的存储位置,直接影响程序的运行效率、资源占用。RT-Thread 的内存分布与传统程序一致,可分为以下区段:
1. 代码段(Code)
- 存储内容:
存放程序的二进制机器指令,即编译器将 C、汇编代码编译后的结果(如函数实现、常量字符串 )。例如:void led_blink(void) { ... }
函数的指令,会被编译成机器码,存储在代码段。- 存储位置:
通常位于 FLASH(或 ROM ) 中,掉电不丢失。程序启动时,CPU 从代码段的起始地址开始取指令执行(如 STM32 从0x08000000
地址开始运行 )。- 特点:
只读(Read-Only),运行时不会被修改(除非程序自己通过指针强制修改,但不推荐 ),保证程序逻辑的稳定性。2. 只读数据段(RO-data)
- 存储内容:
存放程序中只读的常量数据,例如:这些数据在程序运行时不会被修改,因此和代码段一样,需要 “只读保护”。const int g_const_num = 100; // 全局只读常量 char *g_const_str = "RT-Thread"; // 字符串字面量,存储在 RO-data,指针本身可能在 RW-data
- 存储位置:
同样位于 FLASH 中,与代码段相邻或合并存储(不同编译器、链接脚本可能有差异 )。- 作用:
分离 “只读数据” 和 “可执行代码”,便于硬件的 FLASH 只读保护(防止程序运行时意外修改常量 ),同时节省 RAM 空间(不需要在 RAM 中重复存储 )。3. 读写数据段(RW-data)
- 存储内容:
存放初始化了的全局变量、静态变量(非const
修饰 ),例如:这些变量在程序运行时可能被修改,因此需要可读写的存储空间。int g_global_var = 10; // 全局变量,初始化非 0,存 RW-data static char g_static_buf[100] = "init"; // 静态变量,初始化非空,存 RW-data
- 存储位置:
位于 RAM 中,程序启动时,会从 FLASH 中拷贝初始化值到 RW-data 段(因为 FLASH 只读,无法直接修改 )。- 特点:
运行时可读写,是程序动态数据的主要存储区,但占用 RAM 空间。4. 0 数据段(ZI-data)
- 存储内容:
存放未初始化的全局变量、静态变量,以及初始化为 0 的全局 / 静态变量,例如:这些变量的初始值为 0(或未定义,编译器默认填 0 ),因此不需要在 FLASH 中存储初始化值,只需在程序启动时 “清零” 即可。int g_uninit_var; // 未初始化,默认初始化为 0,存 ZI-data static char g_zero_buf[100] = {0}; // 显式初始化为 0,存 ZI-data
- 存储位置:
位于 RAM 中,与 RW-data 段相邻。程序启动时,由系统自动将该段内存 全部清零(减少 FLASH 占用,提升启动速度 )。- 作用:
统一管理 “零初始化变量”,避免每个变量单独清零,提升程序启动效率(通过内存块清零实现 )。5. 内存分布的 “本质”:链接脚本与运行时加载
- 编译阶段:
编译器将代码、数据分类存储到不同的段(Code、RO-data、RW-data ),生成 ELF 格式的可执行文件,存储在 FLASH 中。- 启动阶段:
程序运行时,初始化代码(如startup.s
中的汇编代码 )会执行以下操作:
- 将 FLASH 中的 RW-data 段数据 拷贝到 RAM 中(因为 FLASH 只读,需要在 RAM 中修改 );
- 将 RAM 中的 ZI-data 段内存 全部清零(初始化未定义变量 );
- 设置栈指针(
SP
),跳转到代码段执行。
二、RT-Thread 的 OS 内核组成
RT-Thread 作为实时操作系统(RTOS),其内核实现了 **“实时多任务调度、资源管理、通信同步”** 的基础能力,核心模块包括:
1. 对象管理
- 作用:
RT-Thread 中,线程、信号量、互斥锁、定时器 等内核组件,都被抽象为 “对象”(Object)。对象管理模块负责:
- 统一分配、释放对象的内存(如线程控制块 TCB、信号量控制块 );
- 维护对象的生命周期(创建、初始化、删除 );
- 提供对象的查询、遍历接口(如查找某个线程的状态 )。
- 实现文件:
核心代码位于kernel/object.c
,通过rt_object_xxx
系列函数操作(如rt_object_init
、rt_object_delete
)。2. 线程管理及调度器
线程管理:
负责线程的创建、删除、挂起、恢复,例如:rt_thread_t thread = rt_thread_create("led", // 线程名称led_blink, // 线程入口函数NULL, // 线程参数512, // 线程栈大小3, // 线程优先级10 // 时间片 ); rt_thread_startup(thread); // 启动线程,加入就绪队列
线程控制块(TCB)存储线程的状态(就绪、运行、阻塞 )、栈指针、优先级等信息,由
struct rt_thread
结构体定义(在rtdef.h
或rthread.h
中 )。调度器:
实现 基于优先级的抢占式调度,核心逻辑:
- 维护 就绪队列(按优先级分组,同优先级按时间片轮转 );
- 当线程状态变化时(如阻塞、唤醒 ),更新就绪队列;
- 定时器中断触发时,检查是否需要切换线程(如时间片耗尽、高优先级线程就绪 )。
调度器的核心函数是rt_schedule()
,在定时器中断、线程状态变化时被调用。3. 线程间通信管理
- 通信方式:
RT-Thread 提供多种线程间同步、通信机制:
- 信号量(Semaphore):用于线程间同步(如资源计数、事件通知 );
- 互斥锁(Mutex):用于保护共享资源(支持优先级继承,解决优先级翻转 );
- 消息队列(Message Queue):用于线程间异步通信(传递任意长度的数据 );
- 邮箱(Mailbox):类似消息队列,但更轻量(传递指针,而非数据拷贝 )。
- 实现文件:
代码位于kernel/sem.c
(信号量 )、kernel/mutex.c
(互斥锁 )、kernel/mq.c
(消息队列 )等,统一通过rt_xxx_create
、rt_xxx_take
、rt_xxx_release
系列函数操作。4. 时钟管理
系统定时器:
提供 软件定时器 功能,支持定时回调,例如:rt_timer_t timer = rt_timer_create("timeout", // 定时器名称timer_callback, // 定时回调函数NULL, // 回调参数1000, // 定时周期(ms)RT_TIMER_FLAG_PERIODIC // 周期模式 ); rt_timer_start(timer); // 启动定时器
定时器依赖 硬件定时器中断(如 STM32 的
SysTick
),由rt_system_timer_init
初始化。延时函数:
提供rt_thread_delay(ms)
、rt_sem_take(sem, timeout)
等带超时的延时接口,内部通过 定时器中断 实现阻塞延时(线程进入阻塞状态,直到超时或被唤醒 )。5. 内存管理
- 内存池(Memory Pool):
预先分配一块连续内存,按固定大小分割为 “内存块”,用于频繁分配 / 释放的场景(如消息队列、线程栈 ),避免内存碎片。- 动态堆内存(Heap):
基于rt_malloc
、rt_free
实现,类似标准 C 的malloc
/free
,但需要手动初始化堆内存(如rt_system_heap_init
)。- 实现文件:
代码位于kernel/mem.c
(内存池 )、components/drv/common/heap.c
(动态堆 ),根据硬件平台选择不同的内存管理策略。
三、
rtdef.h
:OS 类型与宏定义的 “字典”
rtdef.h
是 RT-Thread 内核的核心头文件,定义了系统的 基础类型、常用宏、内核对象结构体,是理解 RT-Thread 架构的 “钥匙”。1. 基础类型定义
为了适配不同硬件平台(32 位、64 位 ),
rtdef.h
会重新定义 跨平台的类型,例如typedef signed char rt_int8_t; // 8 位有符号整数 typedef unsigned char rt_uint8_t; // 8 位无符号整数 typedef signed short rt_int16_t; // 16 位有符号整数 typedef unsigned short rt_uint16_t; // 16 位无符号整数 typedef signed int rt_int32_t; // 32 位有符号整数 typedef unsigned int rt_uint32_t; // 32 位无符号整数 // 64 位类型(根据平台适配) typedef signed long long rt_int64_t; typedef unsigned long long rt_uint64_t;
这些类型保证了代码在不同平台的可移植性(避免直接使用
int
、long
等依赖编译器的类型 )。2. 常用宏定义
内存对齐宏:
#define ALIGN(n) __attribute__((aligned(n))) // 按 n 字节对齐
用于定义需要严格内存对齐的变量(如硬件寄存器、缓存行 )。
属性修饰宏:
#define SECTION(x) __attribute__((section(x))) // 自定义段存储 #define RT_WEAK __attribute__((weak)) // 弱符号,允许重定义
SECTION(x)
:将变量或函数放到指定的链接段(如自定义内存区域 );RT_WEAK
:定义 “弱符号”,如果用户代码重新定义同名函数,优先使用用户实现。线程、对象宏:
#define RT_THREAD_PRIORITY_MAX 32 // 最大优先级(默认 32 级) #define RT_OBJECT_FLAG_STATIC 0x00 // 静态创建的对象(不动态分配内存)
定义内核的基础参数(如优先级范围、对象标志位 )。
3. 内核对象结构体
rtdef.h
定义了 线程、信号量、互斥锁 等内核对象的结构体,例如:struct rt_thread {struct rt_object parent; // 继承对象基类(用于统一管理)void (*entry)(void *parameter); // 线程入口函数void *parameter; // 线程参数rt_uint8_t *stack_addr; // 线程栈地址rt_uint32_t stack_size; // 线程栈大小rt_uint8_t current_priority; // 当前优先级rt_uint8_t init_priority; // 初始优先级// ... 其他字段(状态、时间片、定时器 ) };
通过 面向对象的继承设计(
parent
字段是struct rt_object
),所有内核对象都可以被统一管理(如遍历所有线程、信号量 )。
四、 OS操作系统的调度流程
一、RT-Thread 线程调度核心机制
RT-Thread 作为实时操作系统(RTOS),“完全抢占式、基于优先级” 的调度算法 是保障 “实时性” 的核心,决定了线程何时运行、何时被打断,以下是关键逻辑:
1. 完全抢占式调度
- 核心规则:
高优先级线程可以随时打断低优先级线程的执行。只要高优先级线程进入 “就绪状态”,调度器会立即暂停当前运行的低优先级线程,将 CPU 执行权切换给高优先级线程。
例如:
- 低优先级线程
thread_low
正在运行,此时高优先级线程thread_high
因 “信号量释放” 进入就绪态 → 调度器会立即抢占,暂停thread_low
,启动thread_high
运行。- 实现依赖:
基于 硬件中断(如 SysTick 定时器中断 )和 调度器主动触发(如线程状态变化时调用rt_schedule()
)。每次中断或状态变化,调度器都会检查 “是否有更高优先级线程就绪”,决定是否抢占。2. 优先级分级与配置
- 优先级数量:
RT-Thread 支持 3 种优先级级数配置(通过rtconfig.h
宏定义,编译时决定 ):
RT_THREAD_PRIORITY_MAX = 8
:8 级优先级(0 最高,7 最低 ),适用于简单系统,减少调度器计算量;RT_THREAD_PRIORITY_MAX = 32
:32 级优先级(0 最高,31 最低 ),是默认配置,平衡 “优先级细分” 和 “调度效率”;RT_THREAD_PRIORITY_MAX = 256
:256 级优先级(0 最高,255 最低 ),适合对优先级控制极细的复杂系统(如工业自动化 ),但调度器计算量稍大。- 空闲线程的特殊优先级:
无论优先级级数如何,最低优先级(如 7、31、255 )预留给 “空闲线程”(rt_thread_idle
)。当系统中所有其他线程都处于 “阻塞状态” 时,空闲线程才会运行,主要负责 “回收资源、低功耗处理”(如让 CPU 进入休眠模式 )。3. 同优先级线程的时间片轮转
- 适用场景:
当多个线程优先级相同时,调度器会为它们分配 时间片(Time Slice ),让线程轮流运行,避免单个线程长期占用 CPU。- 时间片机制:
每个线程被分配固定长度的 “时间片”(如 10 个滴答周期 ),当时间片耗尽,调度器会切换到同优先级的下一个就绪线程。
例如:
- 线程
thread_a
和thread_b
优先级相同(均为 3 ),时间片均为 10 →thread_a
运行 10 个滴答后,调度器切换到thread_b
运行,直到thread_b
时间片耗尽,再切回thread_a
(如果仍就绪 )。- 时间片长度配置:
创建线程时,通过rt_thread_create
的参数指定(如rt_thread_create(..., 10)
表示时间片为 10 个滴答 ),单位由系统时钟节拍决定(如 1 滴答 = 1ms )。二、线程状态流转:从创建到结束的全生命周期
RT-Thread 中,线程有 5 种状态(初始、就绪、运行、挂起、关闭 ),状态之间的切换由 “调度器规则” 和 “线程 API 调用” 共同控制,以下是详细流转逻辑:
1. 初始状态(Init)
- 进入条件:
调用rt_thread_create
或rt_thread_init
创建线程,但未调用rt_thread_startup
启动时,线程处于 “初始状态”。
例如:rt_thread_t thread = rt_thread_create("test", test_entry, NULL, 512, 3, 10); // 此时 thread 状态为初始态,不参与调度
- 特点:
线程未加入 “就绪队列”,调度器不会为其分配 CPU 执行权。需调用rt_thread_startup(thread)
,将线程状态切换为 “就绪态”,才会参与调度。2. 就绪状态(Ready)
- 进入条件:
- 调用
rt_thread_startup
启动线程,线程被加入 “就绪队列”;- 挂起的线程(
rt_thread_suspend
)被唤醒(rt_thread_resume
),重新加入就绪队列;- 阻塞的线程因 “等待的资源可用”(如信号量释放 ),自动切换为就绪态。
- 特点:
线程按 “优先级 + 时间片” 排队,等待调度器分配 CPU。调度器总是选择 “就绪队列中优先级最高的线程” 运行(同优先级则按时间片轮转 )。3. 运行状态(Running)
- 进入条件:
调度器从 “就绪队列” 中选中线程,将其状态切换为 “运行态”,CPU 开始执行该线程的入口函数。
例如:
- 系统启动后,空闲线程(最低优先级 )默认运行;
- 高优先级线程就绪时,抢占低优先级线程,进入运行态。
- 特点:
- 同一时间,只有 1 个线程处于运行态(单核 CPU 场景 );
- 线程运行时,可通过 API 主动放弃 CPU(如
rt_thread_delay
),或被高优先级线程抢占。4. 挂起状态(Suspended,也叫阻塞态 Blocked )
- 进入条件:
线程因 “等待资源” 或 “主动延时”,调用以下 API 时,会从 “运行态” 或 “就绪态” 切换为挂起态:
- 等待资源:调用
rt_sem_take
(等待信号量 )、rt_mq_recv
(等待消息队列 )、rt_mutex_take
(等待互斥锁 )等,若资源不可用,线程进入挂起态;- 主动延时:调用
rt_thread_delay(ms)
或rt_thread_sleep(ms)
,线程主动挂起指定时间,期间不参与调度。- 特点:
挂起的线程从 “就绪队列” 移除,调度器不会为其分配 CPU。需等待 “资源可用” 或 “延时结束”,才会自动(或被唤醒 )回到 “就绪态”。5. 关闭状态(Closed)
- 进入条件:
- 线程入口函数执行完毕(如
test_entry
函数return
),线程自动进入关闭态;- 调用
rt_thread_delete
或rt_thread_exit
,主动销毁线程。- 特点:
线程资源(如栈空间、线程控制块 TCB )会被回收(动态创建的线程 ),或标记为 “不可用”(静态创建的线程 )。关闭态的线程永久退出调度,无法再被启动或唤醒。三、状态流转示例:线程从创建到结束的完整流程
以下是一个典型的线程生命周期示例,包含状态切换的关键节点:
代码示例
// 线程入口函数 void test_entry(void *param) {while (1) {// 运行态 → 挂起态(主动延时 100ms)rt_thread_delay(100); // 延时结束 → 就绪态 → 调度器再次选中 → 运行态rt_kprintf("Thread running...\n");} }void system_init() {// 创建线程(初始态)rt_thread_t thread = rt_thread_create("test", test_entry, NULL, 512, 3, 10); // 初始态 → 就绪态rt_thread_startup(thread); // 线程运行后,因 rt_thread_delay 进入挂起态,100ms 后回到就绪态 }
四、调度器的 “决策逻辑”:如何选择下一个运行的线程?
调度器的核心工作是 “从就绪队列中选线程”,遵循以下优先级:
紧急度优先:
高优先级线程的 “就绪事件” 会立即触发调度(如中断唤醒、资源释放 ),确保紧急任务(如电机控制、通信接收 )及时响应。同优先级 “时间片轮转”:
同优先级线程按 “时间片” 轮流运行,时间片耗尽则切换到下一个就绪线程。例如:
- 线程 A(优先级 3,时间片 10 )运行 10 个滴答后,时间片耗尽 → 切换到线程 B(同优先级 3 )运行。
空闲线程兜底:
当所有线程都处于 “挂起态” 或 “关闭态”,调度器会选择 “空闲线程” 运行,执行低功耗处理、资源回收等工作。
五、 创建任务的方式
一、任务(线程)创建的两种方式:静态 vs 动态
RT-Thread 中,“任务” 等价于 “线程”(为简化描述,下文统一用 “线程” ),创建线程有 静态创建 和 动态创建 两种方式,核心差异在于 “内存分配方式” 和 “使用场景”。
1. 第一种:静态创建(
rt_thread_init
)函数原型
rt_err_t rt_thread_init(struct rt_thread *thread, // 线程控制块(需用户预先分配内存)const char *name, // 线程名称void (*entry)(void *parameter), // 线程入口函数void *parameter, // 线程参数void *stack_start, // 线程栈起始地址(需用户预先分配)rt_uint32_t stack_size, // 线程栈大小rt_uint8_t priority, // 线程优先级rt_uint32_t tick // 时间片(同优先级线程轮转时用) );
(1)核心特点:“内存由用户管理”
- 内存分配:
线程控制块(struct rt_thread
)和 线程栈(stack_start
)都需要 用户手动分配内存,例如:// 静态分配线程控制块 struct rt_thread static_thread; // 静态分配线程栈(512 字节,需按平台要求对齐) rt_uint8_t static_stack[512] RT_ALIGN(8); // 初始化线程(静态创建) rt_thread_init(&static_thread, "static", static_entry, NULL, static_stack, sizeof(static_stack), 3, 10);
- 内存来源:
通常从 全局变量 或 自定义内存池 分配,优点是 “内存可控”(不会因动态分配导致碎片 ),缺点是 “需用户手动管理内存生命周期”。(2)适用场景:对内存严格管控的系统
- 低功耗 / 资源受限设备:
如电池供电的物联网传感器,需严格控制内存分配,避免动态分配导致的不确定性。- 安全关键系统:
如汽车电子、工业控制,静态内存分配可预测性强,避免动态分配失败导致系统崩溃。- 启动阶段初始化:
系统启动时,在rtthread_startup
早期阶段,动态内存池可能未初始化,需用静态创建。(3)完整参数解析
参数名 作用与细节 thread
线程控制块指针,需用户预先定义 struct rt_thread
变量(如全局变量 )name
线程名称,最大长度由 RT_NAME_MAX
(rtconfig.h
中定义 )决定,超出截断entry
线程入口函数,线程启动后执行的代码逻辑(如 void static_entry(void *arg)
)parameter
传递给入口函数的参数(如配置信息、硬件句柄 ) stack_start
线程栈的起始地址,需用户分配并确保 内存对齐(如 ARM 平台需 8 字节对齐 ) stack_size
线程栈大小(字节 ),需根据线程逻辑复杂度设置(复杂函数需更大栈 ) priority
线程优先级(0 最高, RT_THREAD_PRIORITY_MAX-1
最低 )tick
同优先级线程的时间片(滴答数 ),时间片耗尽则切换到下一个同优先级线程 (4)使用示例
// 1. 静态分配线程控制块和栈 struct rt_thread static_thread; rt_uint8_t static_stack[512] RT_ALIGN(8); // 8 字节对齐(ARM 要求)// 2. 定义线程入口函数 void static_entry(void *param) {while (1) {rt_kprintf("Static thread running...\n");rt_thread_delay(1000); // 延时 1 秒} }// 3. 初始化并启动线程 void system_init() {rt_thread_init(&static_thread, "static", static_entry, NULL, static_stack, sizeof(static_stack), 3, 10);// 启动线程(加入就绪队列)rt_thread_startup(&static_thread); }
2. 第二种:动态创建(
rt_thread_create
,常用方式 )函数原型:
rt_thread_t rt_thread_create(const char *name, // 线程名称void (*entry)(void *parameter), // 线程入口函数void *parameter, // 线程参数rt_uint32_t stack_size, // 线程栈大小rt_uint8_t priority, // 线程优先级rt_uint32_t tick // 时间片(同优先级线程轮转时用) );
(1)核心特点:“内存由系统动态分配”
- 内存分配:
线程控制块(struct rt_thread
)和 线程栈 由 RT-Thread 的 动态内存池 分配(默认使用rt_malloc
),例如:// 动态创建线程,返回线程句柄 rt_thread_t dynamic_thread = rt_thread_create("dynamic", dynamic_entry, NULL, 512, 3, 10); if (dynamic_thread != RT_NULL) {rt_thread_startup(dynamic_thread); // 启动线程 }
- 内存来源:
依赖 RT-Thread 的动态内存管理模块(需初始化rt_system_heap_init
),优点是 “使用便捷”(无需手动分配内存 ),缺点是 “可能产生内存碎片”(频繁创建 / 删除线程时 )。(2)适用场景:快速开发与灵活场景
- 通用应用开发:
如物联网网关、消费电子,动态创建线程更便捷,开发效率高。- 线程频繁创建 / 销毁:
虽然可能产生碎片,但动态创建无需用户管理内存,适合业务逻辑复杂、线程生命周期短的场景。- 原型开发与调试:
快速验证功能时,动态创建可减少代码量,聚焦业务逻辑。(3)完整参数解析
参数名 作用与细节 name
线程名称,最大长度由 RT_NAME_MAX
(rtconfig.h
中定义 )决定,超出截断entry
线程入口函数,线程启动后执行的代码逻辑(如 void dynamic_entry(void *arg)
)parameter
传递给入口函数的参数(如配置信息、硬件句柄 ) stack_size
线程栈大小(字节 ),动态分配时需考虑内存池大小(避免分配失败 ) priority
线程优先级(0 最高, RT_THREAD_PRIORITY_MAX-1
最低 )tick
同优先级线程的时间片(滴答数 ),时间片耗尽则切换到下一个同优先级线程 返回值 成功返回线程句柄( rt_thread_t
),失败返回RT_NULL
(4)完整代码示例
// 线程入口函数 void dynamic_entry(void *param) {while (1) {rt_kprintf("Dynamic thread running...\n");rt_thread_delay(500); // 延时 500ms} }// 动态创建线程 void create_dynamic_thread() {// 动态创建线程(栈 512 字节,优先级 3,时间片 10 )rt_thread_t thread = rt_thread_create("dynamic", dynamic_entry, NULL, 512, 3, 10);if (thread != RT_NULL) {// 启动线程(加入就绪队列)rt_thread_startup(thread); } else {rt_kprintf("Thread create failed!\n");} }// 系统初始化时调用 void system_init() {// 初始化动态内存池(需确保堆内存已配置,如 STM32 需指定堆起始地址 )rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END); create_dynamic_thread(); }
二、两种创建方式的核心差异对比
对比项 静态创建( rt_thread_init
)动态创建( rt_thread_create
)内存分配 用户手动分配(全局变量 / 自定义内存池 ) 系统动态分配(依赖 rt_malloc
)内存可控性 强(无碎片风险,需用户管理 ) 弱(可能产生碎片,系统自动管理 ) 使用复杂度 高(需手动分配栈、控制块 ) 低(一行代码创建,自动分配内存 ) 适用场景 资源受限、安全关键系统 通用开发、快速原型、线程频繁创建 线程销毁 需调用 rt_thread_detach
+rt_thread_delete
直接调用 rt_thread_delete
(自动回收内存 )
六、 线程间通信接口
一、线程间通信的核心需求
在多线程系统中,线程间需要 “同步(协调执行顺序 )” 和 “通信(传递数据 / 事件 )”,解决以下问题:
- 同步:避免线程竞争共享资源(如同时写串口 ),确保 “生产者 - 消费者” 顺序执行;
- 通信:传递数据(如传感器数据 )、通知事件(如按键触发 )、处理异常(如系统故障 )。
RT-Thread 提供 6 种核心通信接口,覆盖不同场景需求:
二、1. 信号量(Semaphore)
(1)原理:“计数器 + 阻塞等待”
信号量是一种 轻量级同步工具,通过一个 “计数器” 控制对共享资源的访问:
- 计数器 > 0:线程可以 “获取” 信号量(计数器减 1 ),继续执行;
- 计数器 = 0:线程 “阻塞等待”,直到其他线程 “释放” 信号量(计数器加 1 )。
类比:停车场入口的 “剩余车位计数器”,车位 > 0 可进入(获取 ),= 0 需排队(阻塞 ),离开时释放车位(计数器加 1 )。
(2)函数接口(核心)
// 创建信号量(动态分配内存) rt_sem_t rt_sem_create(const char *name, rt_int32_t value, rt_uint8_t flag);// 获取信号量(阻塞等待,超时返回) rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout);// 释放信号量(计数器加 1,唤醒等待线程) rt_err_t rt_sem_release(rt_sem_t sem);// 删除信号量(动态创建时需调用) rt_err_t rt_sem_delete(rt_sem_t sem);
(3)典型应用:“生产者 - 消费者” 同步
// 信号量控制共享资源(假设共享串口) rt_sem_t uart_sem;// 生产者线程(发送数据) void producer_thread(void *arg) {while (1) {// 模拟产生数据char data = get_sensor_data(); // 获取信号量(等待串口可用)rt_sem_take(uart_sem, RT_WAITING_FOREVER); // 发送数据(共享资源)uart_send(data); // 释放信号量(串口可用)rt_sem_release(uart_sem); rt_thread_delay(1000);} }// 消费者线程(接收数据) void consumer_thread(void *arg) {while (1) {// 获取信号量(等待串口可用)rt_sem_take(uart_sem, RT_WAITING_FOREVER); // 接收数据(共享资源)char data = uart_recv(); // 释放信号量(串口可用)rt_sem_release(uart_sem); rt_kprintf("Received: %c\n", data);rt_thread_delay(500);} }// 初始化信号量 void sem_init() {// 创建信号量,初始值 1(表示 1 个共享资源)uart_sem = rt_sem_create("uart_sem", 1, RT_IPC_FLAG_FIFO); if (uart_sem != RT_NULL) {rt_thread_create("producer", producer_thread, NULL, 512, 3, 10);rt_thread_create("consumer", consumer_thread, NULL, 512, 4, 10);} }
(4)适用场景
- 控制 1 个或多个共享资源 的访问(如串口、传感器 );
- 实现 简单的同步(如线程 A 完成某操作后,线程 B 才能执行 );
- 优点:轻量级,开销小;缺点:无法解决 “优先级翻转”(需用互斥量 )。
三、2. 互斥量(Mutex)
(1)原理:“二值信号量 + 优先级继承”
互斥量是一种 特殊的二值信号量(计数器只能是 0 或 1 ),核心解决 “优先级翻转” 问题:
- 当低优先级线程持有互斥量,高优先级线程等待时,互斥量会 临时提升低优先级线程的优先级,使其与高优先级线程一致,避免高优先级线程长时间阻塞。
类比:低优先级线程拿了 “钥匙”(互斥量 ),高优先级线程要进门(等待钥匙 ),此时低优先级线程临时 “升级”,快速完成操作并释放钥匙。
(2)函数接口(核心)
// 创建互斥量(动态分配内存) rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag);// 获取互斥量(阻塞等待,支持递归获取) rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout);// 释放互斥量(递归获取需对应次数释放) rt_err_t rt_mutex_release(rt_mutex_t mutex);// 删除互斥量(动态创建时需调用) rt_err_t rt_mutex_delete(rt_mutex_t mutex);
(3)典型应用:“优先级翻转” 场景
rt_mutex_t shared_mutex;// 低优先级线程(持有互斥量) void low_thread(void *arg) {while (1) {// 获取互斥量rt_mutex_take(shared_mutex, RT_WAITING_FOREVER); // 模拟耗时操作(高优先级线程会等待)rt_thread_delay(1000); // 释放互斥量rt_mutex_release(shared_mutex); rt_thread_delay(5000);} }// 高优先级线程(等待互斥量) void high_thread(void *arg) {while (1) {rt_kprintf("High thread waiting mutex...\n");// 获取互斥量(可能阻塞,但会触发优先级继承)rt_mutex_take(shared_mutex, RT_WAITING_FOREVER); rt_kprintf("High thread got mutex!\n");// 释放互斥量rt_mutex_release(shared_mutex); rt_thread_delay(2000);} }// 初始化互斥量 void mutex_init() {// 创建互斥量shared_mutex = rt_mutex_create("shared_mutex", RT_IPC_FLAG_FIFO); if (shared_mutex != RT_NULL) {// 低优先级(3)rt_thread_create("low", low_thread, NULL, 512, 3, 10); // 高优先级(2)rt_thread_create("high", high_thread, NULL, 512, 2, 10); } }
(4)适用场景
- 解决 共享资源的互斥访问(如文件系统、硬件寄存器 );
- 必须处理 优先级翻转 的实时系统(如汽车电子、工业控制 );
- 支持 递归获取(同一线程可多次获取互斥量,需对应释放 )。
四、3. 事件集(Event)
(1)原理:“位掩码 + 多事件等待”
事件集是一种 多对多的同步工具,用 位掩码 表示多个事件(如事件 0、事件 1 ):
- 线程可以 等待多个事件的任意组合(如 “事件 0 或事件 1”、“事件 0 和事件 1” );
- 其他线程或中断可以 发送事件(设置对应的位 ),唤醒等待线程。
(2)函数接口(核心)
// 创建事件集(动态分配内存) rt_event_t rt_event_create(const char *name, rt_uint8_t flag);// 发送事件(设置位掩码) rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);// 接收事件(等待位掩码,支持逻辑与/或) rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved);// 删除事件集(动态创建时需调用) rt_err_t rt_event_delete(rt_event_t event);
(3)典型应用:“多事件触发”
rt_event_t key_event;// 按键中断服务函数(发送事件) void key_irq_handler() {// 发送事件 0(按键 0 按下)rt_event_send(key_event, 1 << 0); }// 线程 A:等待“事件 0 或事件 1” void thread_a(void *arg) {rt_uint32_t recved;while (1) {// 等待事件 0 或事件 1(RT_EVENT_FLAG_OR)rt_event_recv(key_event, (1 << 0) | (1 << 1), RT_EVENT_FLAG_OR, RT_WAITING_FOREVER, &recved);if (recved & (1 << 0)) {rt_kprintf("Event 0 received!\n");}if (recved & (1 << 1)) {rt_kprintf("Event 1 received!\n");}} }// 线程 B:发送事件 1(定时器触发) void thread_b(void *arg) {while (1) {rt_thread_delay(2000);// 发送事件 1(定时事件)rt_event_send(key_event, 1 << 1); } }// 初始化事件集 void event_init() {// 创建事件集key_event = rt_event_create("key_event", RT_IPC_FLAG_FIFO); if (key_event != RT_NULL) {rt_thread_create("thread_a", thread_a, NULL, 512, 3, 10);rt_thread_create("thread_b", thread_b, NULL, 512, 4, 10);// 初始化按键中断,绑定 key_irq_handlerkey_irq_init(key_irq_handler); } }
(4)适用场景
- 多事件触发(如按键、定时器、传感器事件 );
- 一对多 / 多对多同步(多个线程等待同一事件集,多个线程发送事件 );
- 优点:灵活,可等待任意事件组合;缺点:仅支持事件通知,无法传递数据(需结合邮箱 / 消息队列 )。
五、4. 邮箱(MailBox)
(1)原理:“4 字节消息 + 队列缓存”
邮箱是一种 轻量级通信工具,每次传递 1 个 4 字节的消息(如指针、整数 ),并支持缓存一定数量的消息:
- 发送线程:将消息放入邮箱队列;
- 接收线程:从队列取出消息,支持阻塞等待。
(2)函数接口(核心)
// 创建邮箱(动态分配内存) rt_mailbox_t rt_mailbox_create(const char *name, rt_size_t size, rt_uint8_t flag);// 发送邮箱消息(4 字节,可阻塞) rt_err_t rt_mailbox_send(rt_mailbox_t mb, rt_uint32_t value);// 接收邮箱消息(4 字节,可阻塞等待) rt_err_t rt_mailbox_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout);// 删除邮箱(动态创建时需调用) rt_err_t rt_mailbox_delete(rt_mailbox_t mb);
(3)典型应用:“线程间传递指针”
rt_mailbox_t data_mb;// 生产者线程(发送传感器数据指针) void producer(void *arg) {while (1) {// 动态分配数据(需注意内存释放)int *data = rt_malloc(sizeof(int)); *data = get_sensor_value();// 发送数据指针(4 字节)rt_mailbox_send(data_mb, (rt_uint32_t)data); rt_thread_delay(1000);} }// 消费者线程(接收并处理数据) void consumer(void *arg) {rt_uint32_t value;while (1) {// 接收数据指针(阻塞等待)rt_mailbox_recv(data_mb, &value, RT_WAITING_FOREVER); int *data = (int *)value;rt_kprintf("Sensor data: %d\n", *data);// 释放内存(生产者分配,消费者释放)rt_free(data); } }// 初始化邮箱 void mailbox_init() {// 创建邮箱,缓存 10 条消息data_mb = rt_mailbox_create("data_mb", 10, RT_IPC_FLAG_FIFO); if (data_mb != RT_NULL) {rt_thread_create("producer", producer, NULL, 512, 3, 10);rt_thread_create("consumer", consumer, NULL, 512, 4, 10);} }
(4)适用场景
- 传递小数据(4 字节以内,如指针、整数 );
- 线程间异步通信(无需等待立即响应 );
- 优点:轻量级,支持缓存;缺点:仅能传递 4 字节,大数据需用消息队列。
六、5. 消息队列(Message Queue)
(1)原理:“变长消息 + 队列缓存”
消息队列是一种 灵活的通信工具,支持传递 变长消息(如 1 字节到数百字节 ),并缓存一定数量的消息:
- 发送线程:将消息(含长度 )放入队列;
- 接收线程:从队列取出消息,支持阻塞等待。
(2)函数接口(核心)
// 创建消息队列(动态分配内存) rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag);// 发送消息队列(变长消息,可阻塞) rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t length);// 接收消息队列(变长消息,可阻塞等待) rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t length, rt_int32_t timeout);// 删除消息队列(动态创建时需调用) rt_err_t rt_mq_delete(rt_mq_t mq);
(3)典型应用:“线程间传递变长数据”
rt_mq_t log_mq;// 日志线程(发送变长日志) void log_thread(void *arg) {char log_buf[64];while (1) {// 拼接变长日志rt_sprintf(log_buf, "Time: %d, Temp: %d\n", rt_tick_get(), get_temp());// 发送日志(变长消息)rt_mq_send(log_mq, log_buf, rt_strlen(log_buf)); rt_thread_delay(2000);} }// 串口线程(接收并打印日志) void uart_thread(void *arg) {char recv_buf[64];while (1) {// 接收变长消息(阻塞等待)rt_mq_recv(log_mq, recv_buf, sizeof(recv_buf), RT_WAITING_FOREVER); uart_send(recv_buf); // 发送到串口} }// 初始化消息队列 void mq_init() {// 创建消息队列,每条消息最大 64 字节,最多缓存 10 条log_mq = rt_mq_create("log_mq", 64, 10, RT_IPC_FLAG_FIFO); if (log_mq != RT_NULL) {rt_thread_create("log", log_thread, NULL, 512, 3, 10);rt_thread_create("uart", uart_thread, NULL, 512, 4, 10);} }
(4)适用场景
- 传递变长数据(如日志、传感器数据 );
- 线程间异步通信(支持中断发送消息 );
- 优点:灵活,支持任意长度消息;缺点:开销比邮箱大(需拷贝数据 )。
https://github.com/0voice