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_tRT_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_creatert_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
posted @ 2026-08-18 18:53  哲别羽  阅读(2)  评论(0)    收藏  举报