Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread一般讨论
【RSOC26】Day2:RT-Thread 启动流程、线程调度与 SCons 构建实践
发布于 2026-08-18 23:25:46 浏览:25
订阅该版
[tocm] # 【RSOC26】Day2:RT-Thread 启动流程、线程调度与 SCons 构建实践 第二天的课程开始进入 RT-Thread 内核与线程管理部分,主要学习了 RT-Thread 系统启动流程、自动初始化机制、线程控制块、线程状态与调度、静态与动态线程,以及 SCons 构建系统。课程结束后,在 STM32F407 上完成了多线程实验,对线程创建、优先级调度、挂起恢复及 MSH 调试进行了实际验证。 --- ## 一、RT-Thread 系统启动流程 在裸机程序中,通常会把 `main()` 作为主要程序入口。但在 RT-Thread 中,进入用户 `main()` 之前,系统已经完成了板级初始化、内核初始化、线程创建以及调度器启动等工作。 本次 RT-Thread 4.1.1 工程的启动过程可以概括为: ```text Reset_Handler ↓ C Runtime ↓ entry() ↓ rtthread_startup() ↓ rt_hw_board_init() ↓ rt_components_board_init() ↓ rt_system_timer_init() ↓ rt_system_scheduler_init() ↓ rt_application_init() ↓ rt_thread_idle_init() ↓ rt_system_scheduler_start() ↓ main_thread_entry() ↓ rt_components_init() ↓ main() ``` 其中,`rtthread_startup()` 是 RT-Thread 系统启动的核心入口。系统在这里完成板级初始化、系统定时器初始化、调度器初始化以及应用线程初始化等工作。 RT-Thread 还会创建 Main Thread,用户编写的: ```c int main(void) { /* application */ } ``` 最终是在 Main Thread 中运行的。 因此,RT-Thread 中的 `main()` 并不是 MCU 上电后直接执行的第一个 C 函数,而是在操作系统内核环境和调度器已经建立之后执行的应用入口。 --- ## 二、RT-Thread 自动初始化机制 RT-Thread 提供了自动初始化机制,各驱动、组件和应用模块可以通过初始化宏将自己的初始化函数注册到系统启动流程中。 例如: ```c INIT_APP_EXPORT(thread_study_init); ``` 其基本实现过程可以概括为: ```text 初始化函数 ↓ INIT_xxx_EXPORT ↓ 函数指针放入指定 Section ↓ 链接脚本统一收集 ↓ RT-Thread 启动 ↓ 遍历初始化函数表 ↓ 依次执行初始化函数 ``` 常见初始化宏包括: ```c INIT_BOARD_EXPORT() INIT_DEVICE_EXPORT() INIT_COMPONENT_EXPORT() INIT_ENV_EXPORT() INIT_APP_EXPORT() ``` 不同宏对应不同的系统初始化阶段。 其中: - `INIT_BOARD_EXPORT()` 主要用于板级初始化; - `INIT_APP_EXPORT()` 主要用于应用层初始化。 本次实验中的: ```c INIT_APP_EXPORT(thread_study_init); ``` 使 `thread_study_init()` 在应用初始化阶段自动执行,因此无需在 `main()` 中显式调用线程初始化函数。 这种机制可以减少模块之间的直接依赖,使不同驱动和组件能够分别维护自己的初始化过程。 --- ## 三、线程控制块与内核对象 RT-Thread 使用线程控制块保存一个线程运行和调度所需的全部核心信息。 线程控制块由: ```c struct rt_thread ``` 定义。 其中比较重要的成员包括: | 字段 | 作用 | | --- | --- | | `sp` | 当前线程栈指针,用于上下文切换 | | `entry` | 线程入口函数 | | `parameter` | 线程入口参数 | | `stack_addr` | 线程栈起始地址 | | `stack_size` | 线程栈大小 | | `stat` | 当前线程状态 | | `current_priority` | 当前线程优先级 | | `init_priority` | 初始优先级 | | `init_tick` | 初始时间片 | | `remaining_tick` | 当前剩余时间片 | RT-Thread 内核采用统一对象模型: ```text rt_object ↑ rt_thread ``` 线程对象在基础 `rt_object` 上增加线程相关属性。 基础对象中包含: ```text name type flag list ``` 等公共信息,使线程、定时器以及后续 IPC 对象都可以使用统一的内核对象管理机制。 --- ## 四、线程状态与生命周期 RT-Thread 中线程主要包含五种基本状态: ```text INIT READY RUNNING SUSPEND CLOSE ``` 分别对应: | 状态 | 含义 | | --- | --- | | `INIT` | 已创建或初始化,但尚未启动 | | `READY` | 已具备运行条件,等待调度 | | `RUNNING` | 当前正在 CPU 上运行 | | `SUSPEND` | 因延时、等待资源或人工挂起暂时不参与调度 | | `CLOSE` | 线程已经结束或被删除 | 典型状态转换关系为: ```text rt_thread_startup() INIT --------------------------------> READY | | Scheduler v RUNNING / \ / \ delay/wait preempt | | v v SUSPEND READY | resume / timeout | v READY ``` 线程通过: ```c rt_thread_create() ``` 或: ```c rt_thread_init() ``` 完成创建后,首先处于 `INIT` 状态。 还需要: ```c rt_thread_startup(thread); ``` 才会进入 `READY` 队列并开始参与系统调度。 --- ## 五、线程栈与上下文切换 RTOS 中每个线程都拥有独立的线程栈。 线程栈不仅用于保存局部变量和函数调用信息,也用于保存线程切换时的处理器上下文。 例如从 Thread A 切换到 Thread B: ```text Thread A Running ↓ 保存 A 的上下文 ↓ Thread A Stack Thread B Stack ↓ 恢复 B 的上下文 ↓ Thread B Running ``` 之后再次调度 Thread A 时,可以从其线程栈恢复之前保存的上下文,因此 Thread A 能够继续从之前暂停的位置运行。 线程栈大小需要根据任务复杂度合理配置。栈过小可能产生 Stack Overflow,栈过大则会造成 MCU RAM 浪费。 --- ## 六、静态线程与动态线程 RT-Thread 提供了两种主要线程创建方式。 ### 1. 动态线程 动态线程使用: ```c rt_thread_create() ``` 例如: ```c rt_thread_t tid; tid = rt_thread_create("thread1", thread_entry, RT_NULL, 1024, 16, 5); ``` 线程控制块和线程栈由 RT-Thread 在运行时从 Heap 中动态申请。 --- ### 2. 静态线程 静态线程使用: ```c rt_thread_init() ``` 需要应用程序提前准备线程控制块和线程栈: ```c static struct rt_thread thread2; static rt_uint8_t thread2_stack[1024]; ``` 然后: ```c rt_thread_init(&thread2, "thread2", thread_entry, RT_NULL, thread2_stack, sizeof(thread2_stack), 16, 5); ``` 两种方式的主要区别如下: | 项目 | 动态线程 | 静态线程 | | --- | --- | --- | | API | `rt_thread_create()` | `rt_thread_init()` | | TCB | 动态分配 | 应用提供 | | Stack | 动态分配 | 应用提供 | | Heap 依赖 | 有 | 无 | | 内存确定性 | 相对较低 | 较高 | | 使用方式 | 灵活 | 资源确定 | 对于要求资源使用可预测的嵌入式系统,静态线程可以在编译和链接阶段确定内存占用,同时减少运行时动态分配失败和内存碎片的风险。 --- ## 七、抢占式调度与时间片轮转 RT-Thread 采用基于优先级的抢占式调度。 其优先级规则为: ```text 优先级数值越小 = 线程优先级越高 ``` 例如: ```text Thread A : Priority 10 Thread B : Priority 16 ``` Thread A 的优先级高于 Thread B。 如果 B 当前正在运行,而 A 进入 READY 状态: ```text Thread B RUNNING ↓ Thread A READY ↓ Thread A 优先级更高 ↓ 发生抢占 ↓ Thread A RUNNING ``` 除了优先级之外,RT-Thread 还支持同优先级线程之间的时间片轮转。 例如: ```text Thread A Priority = 16 Time Slice = 5 Thread B Priority = 16 Time Slice = 5 ``` 当两个线程同时处于 READY 状态时,可以按照时间片进行 Round-Robin 调度。 因此: ```text Priority ↓ 决定不同优先级线程之间的调度顺序 Time Slice ↓ 决定同优先级线程之间的轮转 ``` --- ## 八、多线程实验设计 本次实验基于 STM32F407,实现三个 RT-Thread 线程。 | 线程 | 创建方式 | 优先级 | 时间片 | 栈 | 功能 | | --- | --- | ---: | ---: | ---: | --- | | `th_led1` | 动态创建 | 16 | 5 tick | 1024 B | 红色 LED 翻转、计数和 tick 输出 | | `th_led2` | 静态初始化 | 16 | 5 tick | 1024 B | 蓝色 LED 翻转和计数 | | `th_monitor` | 动态创建 | 10 | 5 tick | 1024 B | KEY0 检测、线程状态和剩余栈监测 | 硬件资源: ```text LED_R → PF12 LED_B → PF11 KEY0 → PC0 ``` 均按照低电平有效配置。 整体关系如下: ```text KEY0 │ ▼ th_monitor Priority = 10 │ ├── 状态监控 ├── Stack Free └── Suspend / Resume │ ▼ th_led1 Priority = 16 │ ▼ LED_R th_led2 Priority = 16 │ ▼ LED_B ``` --- ## 九、按键挂起与恢复线程 监控线程除了输出线程状态,还负责检测 KEY0。 KEY0 使用内部上拉输入: ```text 松开 → High 按下 → Low ``` 为了减少机械按键抖动影响,监控线程按照 20 ms 周期进行采样,连续多次确认后再判断一次有效按键。 第一次按下后: ```text KEY0 ↓ 设置挂起请求 ↓ th_led1 在自身线程上下文执行 ↓ rt_thread_suspend(rt_thread_self()) ↓ th_led1 暂停 ``` 再次按下: ```text KEY0 ↓ rt_thread_resume(th_led1) ↓ th_led1 READY ↓ 重新参与调度 ``` 同时需要注意: ```text RT_THREAD_SUSPEND ``` 并不只代表人工挂起。 例如: ```c rt_thread_mdelay(500); ``` 也会使线程在等待期间处于挂起状态。 因此实验中额外维护: ```text manual_suspend ``` 用于区分 KEY0 人工暂停状态和正常延时状态。 --- ## 十、Monitor 线程与栈空间监控 `th_monitor` 的优先级为: ```text 10 ``` 高于两个 LED 线程的: ```text 16 ``` 因此当 Monitor 进入 READY 状态时,可以优先于 LED 工作线程执行。 监控线程采用: ```text 20 ms ``` 基础周期进行按键检测,并累计至约: ```text 2000 ms ``` 输出一次线程状态。 这种方式可以同时满足: ```text 快速按键响应 + 约 2 秒线程状态报告 ``` 避免直接使用单次: ```c rt_thread_mdelay(2000); ``` 造成较大的按键响应延迟。 状态输出包括: ```text Thread Name Priority State Stack Free ``` 剩余栈空间通过扫描 RT-Thread 在线程栈初始化阶段填充的特征字节进行估算。 --- ## 十一、MSH 自定义调试命令 为了便于查看三个实验线程的状态,增加了: ```c MSH_CMD_EXPORT(...) ``` 导出的: ```text thread_dump ``` 命令。 在 RT-Thread MSH 中可以执行: ```text msh /> thread_dump ``` 同时还可以使用系统自带: ```text msh /> list_thread ``` 查看所有系统线程。 `thread_dump` 输出内容主要包括: ```text 线程名称 线程状态 线程优先级 剩余栈 manual_suspend ``` 通过这些信息可以直接观察线程在正常运行、延时以及 KEY0 人工暂停时的状态变化。 --- ## 十二、SCons 构建系统 Day2 还学习了 RT-Thread 的 SCons 构建系统。 RT-Thread 工程中常见: ```text SConstruct SConscript ``` 其中: ```text SConstruct ``` 负责工程顶层构建。 而: ```text SConscript ``` 负责具体模块中的: ```text Source Files Header Search Path Dependency Compile Options ``` 例如: ```python src = ['thread_study.c'] group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) ``` 构建关系可以概括为: ```text Source Code ↓ SConscript ↓ Dependency ↓ DefineGroup ↓ SCons ↓ Compiler / Linker ↓ Firmware ``` 本实验进行了实际 SCons 构建: ```text scons -Q -j1 ``` 成功生成: ```text rt-thread.elf ``` 资源统计: ```text text = 60676 B data = 1880 B bss = 4464 B ``` 构建返回码: ```text 0 ``` RT-Thread Studio 的最终 Clean Build 同样为: ```text 0 errors 0 warnings ``` --- ## 十三、实机运行结果 实验程序通过 ST-Link 下载至 STM32F407,并使用 ST-Link Virtual COM Port 进行串口调试。 串口配置: ```text COM7 115200 8-N-1 ``` 实机验证结果如下: | 检查项 | 结果 | | --- | --- | | RT-Thread Studio Build | PASS | | Error | 0 | | Warning | 0 | | SCons Build | PASS | | ST-Link Program | PASS | | Flash Verify | PASS | | `th_led1` | PASS | | `th_led2` | PASS | | `th_monitor` | PASS | | `thread_dump` | PASS | | `list_thread` | PASS | | KEY0 Suspend | PASS | | KEY0 Resume | PASS | 运行过程中: - 红色 LED 按约 500 ms 周期翻转; - 蓝色 LED 按约 700 ms 周期翻转; - Monitor 约每 2 s 输出线程状态; - KEY0 第一次按下后 `th_led1` 暂停; - `th_led2` 和 `th_monitor` 保持正常运行; - KEY0 第二次按下后 `th_led1` 恢复; - `thread_dump` 能够读取三个实验线程的状态和剩余栈空间。 --- ## 十四、总结 Day2 的学习内容主要围绕 RT-Thread 系统启动、线程对象、线程状态以及调度机制展开,同时进一步学习了 SCons 构建系统。 整个 RT-Thread 线程运行过程可以概括为: ```text MCU Reset ↓ RT-Thread Startup ↓ Kernel Initialization ↓ Thread Create / Init ↓ INIT ↓ rt_thread_startup() ↓ READY ↓ Scheduler ↓ RUNNING ↓ Delay / Wait / Suspend ↓ SUSPEND ↓ Resume / Timeout ↓ READY ``` 通过 STM32F407 多线程实验,动态线程、静态线程、高优先级监控线程、线程挂起恢复、运行状态监控以及 MSH 调试均完成了实际验证。 这些机制也是后续学习信号量、互斥量、事件、邮箱和消息队列等 RT-Thread IPC 功能的基础。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
niconiconi
这家伙很懒,什么也没写!
文章
2
回答
0
被采纳
0
关注TA
发私信
相关文章
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
【最新活动】RT-Thread论坛活动正在进行(2026年5月活动已更新)
4
国产MCU移植系列教程汇总,欢迎查看!
5
机器人操作系统 (ROS2) 和 RT-Thread 通信
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
五分钟玩转RT-Thread新社区
8
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
9
STM32的“GPU”——DMA2D实例详解
10
RT-Thread隐藏的宝藏之completion
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
Bootloader
AT
Hardfault
CAN总线
ART-Pi
FinSH
DMA
USB
RT-Thread
SCons
文件系统
RT-Thread Nano
线程
MQTT
STM32
FAL
rt-smart
BSP
cubemx
RTC
WIZnet_W5500
PWM
I2C_IIC
UART
packages_软件包
GD32
ota在线升级
ESP8266
freemodbus
flash
keil_MDK
编译报错
ADC
潘多拉开发板_Pandora
flashDB
Debug
ulog
msh
rt_mq_消息队列_msg_queue
socket
MicroPython
SDIO总线
中断
SFUD
QEMU
本月问答贡献
crystal266
3
个答案
1
次被采纳
CYFS
3
个答案
1
次被采纳
oxlm
1
个答案
1
次被采纳
王棒棒
1
个答案
1
次被采纳
wdfk_prog
1
个答案
1
次被采纳
本月文章贡献
Ewan11111
3
篇文章
1
次点赞
徐VV
2
篇文章
2
次点赞
金阳阳
2
篇文章
1
次点赞
longtsing
1
篇文章
4
次点赞
RTT_Studio
1
篇文章
4
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部