RT-Thread线程创建与调度笔记
RT-Thread 静态线程与动态线程创建
一、源码
rt_task_sample.c — 线程创建与调度钩子
#include <rtthread.h>
/* ======================== 静态线程定义 ======================== */
struct rt_thread thread1; // 线程控制块(静态分配)
char thread1_stack[1024] __attribute__((aligned(4))); // 1KB 栈空间,4 字节对齐
/* ======================== 线程入口函数 ======================== */
/* thread1:有限循环,跑完 10 次后线程自动销毁 */
void thread_entry1(void *parameter)
{
/*
* i 不需要 static:
* 函数只被 RTOS 调用一次,for 循环从头跑到尾不会中途返回。
* rt_thread_mdelay() 只是让出 CPU,函数没退出,i 的值不变。
*/
for (int i = 0; i < 10; i++)
{
rt_kprintf("thread1 count=[%d]\r\n", i);
rt_thread_mdelay(500); // 延时 500ms,线程进入挂起态
}
// 循环结束 → 函数返回 → 线程进入关闭态,内核自动回收
}
/* thread2:无限循环,永远运行 */
void thread_entry2(void *parameter)
{
for (;;)
{
rt_kprintf("thread2\r\n");
rt_thread_mdelay(1000); // 延时 1000ms
}
}
/* ======================== 调度钩子 ======================== */
/* 每次线程切换时由内核调用,打印切换信息 */
void scheduler_hook(struct rt_thread *from, struct rt_thread *to)
{
/* 线程名在父对象 rt_object 里,需要通过 parent.name 访问 */
rt_kprintf("thread %s------> thread %s\r\n",
from->parent.name, to->parent.name);
}
/* ======================== 创建线程 ======================== */
int create_thread_sample(void)
{
rt_err_t ret = RT_EOK;
rt_scheduler_sethook(&scheduler_hook); // 注册调度钩子
/* ---------- 静态线程:rt_thread_init ---------- */
/* 参数:控制块, 名字, 入口函数, 参数, 栈地址, 栈大小, 优先级, 时间片tick */
ret = rt_thread_init(&thread1,
"thread1",
thread_entry1,
NULL,
thread1_stack,
sizeof(thread1_stack),
20, // 优先级 20
10); // 时间片 10 tick
if (RT_EOK != ret)
rt_kprintf("thread1 create failed\r\n");
else
rt_kprintf("thread1 create successful\r\n");
/* ---------- 动态线程:rt_thread_create ---------- */
/* 参数:名字, 入口函数, 参数, 栈大小, 优先级, 时间片tick */
/* 返回线程句柄,失败返回 RT_NULL */
rt_thread_t thread2_handle = RT_NULL;
thread2_handle = rt_thread_create("thread2",
thread_entry2,
NULL,
1024, // 栈大小 1024 字节
20, // 优先级 20
10); // 时间片 10 tick
if (RT_NULL == thread2_handle)
rt_kprintf("thread2 create failed\r\n");
else
rt_kprintf("thread2 create successful\r\n");
/* 启动线程 */
rt_thread_startup(&thread1); // 静态线程:传控制块地址
rt_thread_startup(thread2_handle); // 动态线程:传句柄(指针)
return 0;
}
main.c — 主函数
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
int main(void)
{
extern int create_thread_sample(void); // 声明外部函数
create_thread_sample(); // 创建并启动两个线程
int count = 1;
while (count++)
{
rt_thread_mdelay(1000); // 主线程每 1000ms 让出一次 CPU
}
return RT_EOK;
}
二、核心知识点
1. 静态线程 vs 动态线程
| 静态线程 | 动态线程 | |
|---|---|---|
| 创建 | rt_thread_init() |
rt_thread_create() |
| 销毁 | rt_thread_detach() |
rt_thread_delete() |
| 控制块 | 用户自己定义 struct rt_thread |
内核 rt_malloc 分配 |
| 栈空间 | 用户自己定义 char stack[] |
内核 rt_malloc 分配 |
| 适用场景 | 资源紧张、不想动态分配 | 方便灵活,用完即释放 |
2. __attribute__((aligned(4)))
- GCC 语法,让变量起始地址是 4 的倍数
- ARM Cortex-M 的栈指针 SP 必须 4 字节对齐,不对齐会触发硬件异常
- 数字直接写字节数:
aligned(4)= 4 字节对齐,aligned(8)= 8 字节对齐
3. 线程五种状态
| 状态 | 宏定义 | 含义 |
|---|---|---|
| 初始态 | RT_THREAD_INIT |
刚创建,未启动,不参与调度 |
| 就绪态 | RT_THREAD_READY |
等待 CPU,按优先级排队 |
| 运行态 | RT_THREAD_RUNNING |
正在占用 CPU |
| 挂起态 | RT_THREAD_SUSPEND |
等资源/延时,不参与调度,到期自动恢复 |
| 关闭态 | RT_THREAD_CLOSE |
函数返回或被销毁,生命周期结束 |
4. RT-Thread 挂起 vs FreeRTOS 阻塞/挂起
| RT-Thread | FreeRTOS | 对应关系 |
|---|---|---|
| 挂起态 | 阻塞态 (Blocked) | 本质相同:等资源/超时,自动恢复 |
| 挂起态 | 挂起态 (Suspended) | 不同:FreeRTOS 的挂起需手动 Resume |
| 关闭态 | 无直接对应 | 线程生命周期结束 |
5. RT-Thread 优先级 vs FreeRTOS 优先级
| RT-Thread | FreeRTOS | |
|---|---|---|
| 数值方向 | 数字越小优先级越高(0 最高) | 数字越大优先级越高(0 最低) |
| 取值范围 | 0 ~ RT_THREAD_PRIORITY_MAX-1,由 RT_THREAD_PRIORITY_MAX(rtconfig.h)决定,常用 32 |
0 ~ configMAX_PRIORITIES-1,由 configMAX_PRIORITIES(FreeRTOSConfig.h)决定,常用 7/10/32 |
| 空闲线程优先级 | tidle 优先级 = RT_THREAD_PRIORITY_MAX-1(最大值,最低) |
idle 优先级 = 0(最低) |
| main 线程优先级 | 默认 RT_MAIN_THREAD_PRIORITY(通常为最大优先级,即最低优先级附近) |
main 在 configMAX_PRIORITIES-1?——不是,FreeRTOS 没有"main 线程"概念,main 里创建任务 |
| 时间片 | 同优先级线程轮转,时间片在 rt_thread_create/init 里作为参数显式指定(单位 tick) |
同优先级任务轮转,时间片固定为 1 tick(configTICK_RATE_HZ 决定粒度),由 xTaskIncrementTick 处理 |
| 是否支持抢占 | 支持(基于优先级的位图调度器,rt_sched),高优先级立即抢占低优先级 |
支持(configUSE_PREEMPTION 默认开),高优先级抢占低优先级 |
| 优先级数量上限 | 受 RT_THREAD_PRIORITY_MAX 限制,传统上限 256,常见 32 |
受 configMAX_PRIORITIES 限制,用通用方法时无上限(实际受 RAM 限制) |
| 同名容易踩的坑 | 代码里写 rt_thread_create(..., prio=20, ...) 在 RT-Thread 是个偏低的优先级 |
同样的 prio=20 在 FreeRTOS 如果 configMAX_PRIORITIES>20 就是个偏高的优先级 |
核心记忆点:方向相反。
- RT-Thread:
0是 VIP,数字越大越往后排。 - FreeRTOS:
0是打杂的(idle),数字越大越优先。
迁移代码时,优先级映射:FreeRTOS_prio = (configMAX_PRIORITIES - 1) - RT_Thread_prio,方向反过来换算。
6. 两个创建函数逐参数详解
(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 | thread |
struct rt_thread * |
&thread1 |
线程控制块 TCB 的地址,用户提供(全局静态变量)。这是静态创建与动态创建最大的区别:控制块由用户自己定义,内核不分配。 |
| 2 | name |
const char * |
"thread1" |
线程名,字符串。保存在 thread->parent.name 里,用于 shell(list_thread)、调度钩子、调试时识别线程。可重名,仅作显示用。 |
| 3 | entry |
void (*)(void *) |
thread_entry1 |
线程入口函数指针。内核启动线程时调用它,函数返回即线程结束(进关闭态)。 |
| 4 | parameter |
void * |
NULL |
传给入口函数的参数,即 entry(parameter) 里的 parameter。不需要时传 NULL。常用它传一个结构体指针/索引给线程。 |
| 5 | stack_start |
void * |
thread1_stack |
栈起始地址,用户提供。必须指向一块有效内存,且通常要 4 字节对齐(本例用 __attribute__((aligned(4))))。 |
| 6 | stack_size |
rt_uint32_t |
sizeof(thread1_stack) = 1024 |
栈大小,单位字节。要能容纳函数调用链的局部变量、中断嵌套上下文、RTOS 调度栈。不够会栈溢出(可能踩内存或触发硬件 fault)。 |
| 7 | priority |
rt_uint8_t |
20 |
优先级,数字越小优先级越高。范围 0 ~ RT_THREAD_PRIORITY_MAX-1。0 是最高,空闲线程在最大值。 |
| 8 | tick |
rt_uint32_t |
10 |
时间片,单位 tick。同优先级线程之间轮转时,每个线程最多连续跑 tick 个 tick 就被切走。仅对同优先级线程有意义,不同优先级靠抢占不靠时间片。 |
返回值:rt_err_t,RT_EOK 表示成功,其他为错误码(如 -RT_ERROR)。
(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 | name |
const char * |
"thread2" |
线程名,同上。 |
| 2 | entry |
void (*)(void *) |
thread_entry2 |
线程入口函数指针,同上。 |
| 3 | parameter |
void * |
NULL |
传给入口函数的参数,同上。 |
| 4 | stack_size |
rt_uint32_t |
1024 |
栈大小,单位字节。注意:这里只给大小,不给地址——栈内存由内核 rt_malloc 动态分配,用户不需要提供栈数组。 |
| 5 | priority |
rt_uint8_t |
20 |
优先级,规则同 rt_thread_init。 |
| 6 | tick |
rt_uint32_t |
10 |
时间片(tick),规则同 rt_thread_init。 |
返回值:rt_thread_t(即 struct rt_thread *),成功返回线程句柄,失败返回 RT_NULL。
关键差异:
rt_thread_create比rt_thread_init少了两个参数——没有thread(控制块)和stack_start(栈地址)。因为这两个都由内核rt_malloc动态分配,用户只需要告诉内核"要多大栈",不需要自己准备 TCB 和栈数组。代价是依赖动态内存堆(RT_USING_HEAP必须开启),且创建/销毁有堆碎片风险。
(3) 两个函数对比
rt_thread_init(静态) |
rt_thread_create(动态) |
|
|---|---|---|
| 参数个数 | 8 个 | 6 个 |
| 控制块 TCB | 用户传入地址(参数 1) | 内核 malloc 分配 |
| 栈空间 | 用户传入地址 + 大小(参数 5、6) | 内核 malloc 分配,只传大小(参数 4) |
| 返回值 | rt_err_t(状态码) |
rt_thread_t(句柄,失败 RT_NULL) |
| 销毁函数 | rt_thread_detach() |
rt_thread_delete() |
| 启动方式 | rt_thread_startup(&thread1) 传控制块地址 |
rt_thread_startup(thread2_handle) 传句柄 |
| 依赖 | 不需要堆 | 需要 RT_USING_HEAP |
| 适用 | 资源紧张、栈大小固定、不想用堆 | 灵活、用完即释放、栈大小运行时决定 |
参数顺序记忆:
init:线程对象, 名字, 入口, 参数, 栈地址, 栈大小, 优先级, 时间片create:名字, 入口, 参数, 栈大小, 优先级, 时间片
create 就是 init 去掉开头的"线程对象"和"栈地址",再把"栈大小"提到前面。
(4) FreeRTOS xTaskCreate() — 动态创建(对照)
函数原型:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void * pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pxCreatedTask );
需 configSUPPORT_DYNAMIC_ALLOCATION = 1(默认就是 1)。RAM(TCB + 栈)从 FreeRTOS 堆自动分配。
| 顺序 | 参数 | 类型 | 说明 |
|---|---|---|---|
| 1 | pvTaskCode |
TaskFunction_t (void (*)(void *)) |
任务入口函数指针。任务应无限循环,不能返回/退出;要结束就调 vTaskDelete(NULL) 自删。 |
| 2 | pcName |
const char * |
描述性名字,主要给调试用。最大长度 configMAX_TASK_NAME_LEN(含 \0)。 |
| 3 | uxStackDepth |
configSTACK_DEPTH_TYPE (uint16_t/uint32_t) |
栈深度,单位是字(word),不是字节!实际字节数 = uxStackDepth × 堆栈宽度。Cortex-M 上 = uxStackDepth × 4。详见下方"堆栈宽度"。 |
| 4 | pvParameters |
void * |
传给入口函数的参数。若传某变量地址,该变量在任务运行期间必须一直存在(不能传栈局部变量地址)。 |
| 5 | uxPriority |
UBaseType_t |
任务优先级,数字越大优先级越高(与 RT-Thread 相反)。范围 < configMAX_PRIORITIES,超了默认钳到 configMAX_PRIORITIES-1。MPU 下可 ` |
| 6 | pxCreatedTask |
TaskHandle_t * |
输出句柄,用于后续 vTaskDelete/vTaskSuspend 等。不需要可传 NULL。 |
返回:成功 pdPASS,失败 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(堆不够)。
(5) 堆栈宽度(stack width)是啥
堆栈宽度 = MCU 一次压栈/出栈的最小单位的位数,由 CPU 架构 + FreeRTOS 移植层决定,跟你怎么写代码无关:
- 它对应移植层
portmacro.h里的StackType_t:uint32_t→ 32 位宽,1 word = 4 字节(ARM Cortex-M 全是这种:M0/M3/M4/M7/M33/H5…)uint16_t→ 16 位宽,1 word = 2 字节(部分老内核)uint8_t→ 8 位宽,1 word = 1 字节
- 所以 实际分配栈字节 =
uxStackDepth × 堆栈宽度。 - 文档例子:32 位宽 +
uxStackDepth=400→ 400×4 = 1600 字节;16 位宽 +uxStackDepth=100→ 100×2 = 200 字节。 - 为什么用"字"不用"字节":不同架构栈宽不同,移植层统一按字计数;且栈对齐、PSP 装载本身就是按字做的,按字分配天然对齐。
Cortex-M 速算:
uxStackDepth× 4 = 栈字节数。比如uxStackDepth=256→ 1024 字节栈。
(6) RT-Thread vs FreeRTOS 栈大小参数对照(跨系统踩坑点)
RT-Thread rt_thread_create |
FreeRTOS xTaskCreate |
|
|---|---|---|
| 参数名 | stack_size |
uxStackDepth |
| 单位 | 字节 | 字(Cortex-M 上 ×4 才是字节) |
写 1024 |
直接 = 1024 字节栈 | = 1024×4 = 4096 字节栈(4 倍!) |
| Cortex-M 想要 1024 字节栈 | stack_size=1024 |
uxStackDepth=256 |
| 栈来源 | 内核 rt_malloc |
内核堆分配 |
| 优先级方向 | 数字越小越高 | 数字越大越高 |
踩坑场景:把 RT-Thread 代码迁移到 FreeRTOS,原 rt_thread_create(..., 1024, 20, ...) 直接抄成 xTaskCreate(..., 1024, 20, ...)——栈直接大 4 倍(可能堆不够创建失败),优先级还方向反了(20 在 RT-Thread 偏低,在 FreeRTOS 若上限>20 则偏高)。迁移时记得:栈大小 ÷4,优先级方向反转换算。
7. 调度钩子 rt_scheduler_sethook()
- 每次线程切换时自动调用
- 回调函数原型:
void hook(struct rt_thread *from, struct rt_thread *to) - 用于调试、统计、性能分析
- 注意:线程名通过
thread->parent.name访问,不是thread->name
8. 局部变量为什么不需要 static
void thread_entry1(void *parameter)
{
for (int i = 0; i < 10; i++) { ... }
}
- 线程入口函数只被 RTOS 调用一次
rt_thread_mdelay()让出 CPU 时函数没有返回,只是暂停i存在栈上,函数没结束就一直活着,调度切换不影响它的值- 不是每次调度都重新调用函数,而是一次调用内多次调度切换
三、运行日志(调度钩子输出)
thread tidle0------> thread main
thread main------> thread tidle0
thread tidle0------> thread thread2
thread2
thread thread2------> thread tidle0
thread tidle0------> thread thread1
thread1 count=[4]
thread thread1------> thread tidle0
thread tidle0------> thread thread1
thread1 count=[5]
...
thread1 count=[9]
thread thread1------> thread tidle0
thread tidle0------> thread main
thread tidle0------> thread thread2
thread2 ← thread1 跑完后不再出现,只剩 thread2
观察:
tidle0是空闲线程,没有就绪线程时自动运行thread1跑完 10 次后进入关闭态,日志中消失thread2无限循环,持续输出- 主线程
main每秒让出一次 CPU

浙公网安备 33010602011771号