当前位置: 首页 > news >正文

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 的全局 / 静态变量,例如:​​​​​​​
    int g_uninit_var; // 未初始化,默认初始化为 0,存 ZI-data
    static char g_zero_buf[100] = {0}; // 显式初始化为 0,存 ZI-data
    
    这些变量的初始值为 0(或未定义,编译器默认填 0 ),因此不需要在 FLASH 中存储初始化值,只需在程序启动时 “清零” 即可。
  • 存储位置
    位于 RAM 中,与 RW-data 段相邻。程序启动时,由系统自动将该段内存 全部清零(减少 FLASH 占用,提升启动速度 )。
  • 作用
    统一管理 “零初始化变量”,避免每个变量单独清零,提升程序启动效率(通过内存块清零实现 )。
5. 内存分布的 “本质”:链接脚本与运行时加载
  • 编译阶段
    编译器将代码、数据分类存储到不同的段(Code、RO-data、RW-data ),生成 ELF 格式的可执行文件,存储在 FLASH 中。
  • 启动阶段
    程序运行时,初始化代码(如 startup.s 中的汇编代码 )会执行以下操作:
    1. 将 FLASH 中的 RW-data 段数据 拷贝到 RAM 中(因为 FLASH 只读,需要在 RAM 中修改 );
    2. 将 RAM 中的 ZI-data 段内存 全部清零(初始化未定义变量 );
    3. 设置栈指针(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 后回到就绪态
}

四、调度器的 “决策逻辑”:如何选择下一个运行的线程?

调度器的核心工作是 “从就绪队列中选线程”,遵循以下优先级:

  1. 紧急度优先
    高优先级线程的 “就绪事件” 会立即触发调度(如中断唤醒、资源释放 ),确保紧急任务(如电机控制、通信接收 )及时响应。

  2. 同优先级 “时间片轮转”
    同优先级线程按 “时间片” 轮流运行,时间片耗尽则切换到下一个就绪线程。例如:

    • 线程 A(优先级 3,时间片 10 )运行 10 个滴答后,时间片耗尽 → 切换到线程 B(同优先级 3 )运行。
  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_MAXrtconfig.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_MAXrtconfig.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

http://www.dtcms.com/a/295001.html

相关文章:

  • java有哪些字符需要转译
  • 2026 拼多多秋招内推码(提前批)
  • 前端学习之JavaScript事件监听解析
  • Bonk发币教学全流程
  • 欧盟网络安全标准草案EN 18031详解
  • JDialong弹窗
  • 计算机网络学习----Https协议
  • 亚马逊云科技 上海AI研究院 突然解散 | AI早报
  • 13. event.target 和 event.currentTarget 区别
  • 【C语言进阶】动态内存管理(2)
  • 力扣(LeetCode) ——轮转数组(C语言)
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(2)
  • 【Web APIs】JavaScript 节点操作 ⑦ ( 创建节点案例 | 网页评论功能 )
  • 旅游管理虚拟仿真实训室:重构实践教学新生态
  • 掌握Autofac:IOC容器实战指南
  • GaussDB view视图的用法
  • 分布式光伏发电项目简易故障录波装置介绍
  • [硬件电路-78]:模拟器件 - 从宏观到微观:高频信号下电路与光路的特性演变与挑战
  • Hexo - 免费搭建个人博客05 - 更新个人博客
  • GUI简介 - OpenExo
  • 回顾 Palantir:八年之旅的反思
  • ​​SBOM 软件供应链安全(转)
  • haproxy的七层代理
  • Day01_C++编程
  • Ollama(3)模型迁移和API使用
  • Modbus协议详解与c#应用
  • 重写 与 重载
  • pig cloud框架中引入websocket
  • nginx websocket 代理 断网后 再联网 不能连接
  • Windows下编译UTF8-CPP