MCU之FreeRTOS与RT-Thread函数对比
FreeRTOS vs RT-Thread 函数对比查阅手册
版本说明:
- FreeRTOS Native: FreeRTOS 原生 API
- CMSIS-RTOS v2: ARM 定义的通用 RTOS 接口标准 (常用于 STM32 等)
- RT-Thread: RT-Thread 标准版/Nano 版 API
目录 (Table of Contents)
- 快速迁移 Checklist
- 核心差异概览 (Critical Differences)
- 线程/任务管理 (Thread Management)
- 消息队列 (Message Queue)
- 信号量与互斥量 (Semaphore & Mutex)
- 事件组 (Event Group)
- 软件定时器 (Software Timer)
- 中断管理 (Interrupt Management)
- 内存管理 (Memory Management)
- 邮箱 (Mailbox)
- 调试与诊断参考
前言
- 相比裸机的大循环状态机,RTOS 使用调度器自动切换任务,实现准并行处理
- RTOS 下的队列、信号量等,在轮询等待是会阻塞程序进入挂起状态,不占用,而裸机的标志位、数组,是一直在占用 CPU 轮询。
1. 核心差异概览
在开始 API 对比前,必须注意以下核心机制的区别,否则极易导致 Bug。FreeRTOS 基于“优先级就绪链表 + 抢占式调度”,同优先级是否轮转由 configUSE_TIME_SLICING 控制;RT-Thread 则内建时间片轮转且优先级数值相反,两者的调度时序及饥饿行为会因此不同。
| 特性 |
FreeRTOS |
CMSIS-RTOS v2 |
RT-Thread |
注意 |
| 优先级定义 |
数值越大,优先级越高 |
数值越大,优先级越高 |
数值越小,优先级越高 (0 最高) |
这是最大的坑! 移植时务必转换逻辑。 |
| 栈大小单位 |
通常为字 (Word, 4 Bytes) |
通常为字节 (Byte) |
通常为字节 (Byte) |
xTaskCreate 传入 128 代表 512 字节;rt_thread_create 传入 512 代表 512 字节。 |
| 中断调用 |
必须使用 FromISR 后缀的专用 API |
统一 API (内部自动检测) |
统一 API (内部自动检测) |
FreeRTOS 在中断中调用普通 API 会导致崩溃。 |
| 时间片轮转 |
支持 (需配置 configUSE_TIME_SLICING) |
支持 |
支持 (同优先级任务按时间片轮转) |
|
2. 线程/任务管理 (Thread Management)
2.1 API 对比表
| 功能 |
FreeRTOS Native (task.h) |
CMSIS-RTOS v2 (cmsis_os2.h) |
RT-Thread (rtthread.h) |
| 创建(动态) |
xTaskCreate |
osThreadNew |
rt_thread_create |
| 创建(静态) |
xTaskCreateStatic |
osThreadNew (传入 buffer) |
rt_thread_init |
| 启动 |
vTaskStartScheduler (全局启动) |
osKernelStart |
rt_thread_startup (单个启动) |
| 删除 |
vTaskDelete |
osThreadTerminate |
rt_thread_delete / detach |
| 延时(相对) |
vTaskDelay |
osDelay |
rt_thread_delay / mdelay |
| 延时(绝对) |
vTaskDelayUntil |
osDelayUntil |
rt_thread_delay_until |
| 挂起/恢复 |
vTaskSuspend / Resume |
osThreadSuspend / Resume |
rt_thread_suspend / resume |
| 让权 |
taskYIELD |
osThreadYield |
rt_thread_yield |
2.2 详细说明与示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| graph LR subgraph 控制API SUSP["挂起态\n(手动暂停)"] end
subgraph 调度流程 READY["就绪列表\n(等待CPU)"] RUN["运行态\n(占用CPU)"] BLOCK["阻塞队列\n(等待事件/延时)"] SCHED["调度器/时间片\n(根据优先级选择任务)"] ISR["事件/中断\n(FromISR/rt_event_send)"] end
SUSP -- vTaskResume()/rt_thread_resume --> READY READY -- 获得CPU --> SCHED SCHED -- 选择最高优先级 --> RUN RUN -- 更高优先级到来/时间片到期 --> READY RUN -- 阻塞型API(Delay/Queue/Sem) --> BLOCK BLOCK -- 事件满足/超时 --> READY RUN -- vTaskSuspend()/rt_thread_suspend --> SUSP BLOCK -- vTaskSuspend() --> SUSP ISR -- 唤醒API(FromISR/rt_event_send) --> READY
|
FreeRTOS 创建任务
1 2 3 4 5 6 7 8 9 10 11 12
|
void vTaskCode(void * pvParameters) { for(;;) { vTaskDelay(pdMS_TO_TICKS(1000)); } }
xTaskCreate(vTaskCode, "NAME", 128, NULL, 1, NULL);
|
RT-Thread 创建任务
1 2 3 4 5 6 7 8 9 10 11 12 13
|
void thread_entry(void *parameter) { while(1) { rt_thread_mdelay(1000); } }
rt_thread_t tid = rt_thread_create("NAME", thread_entry, RT_NULL, 512, 25, 10); if (tid != RT_NULL) rt_thread_startup(tid);
|
2.3 栈单位换算与诊断
FreeRTOS 以 StackType_t (通常 4 字节) 为单位计量栈深;RT-Thread 直接使用字节数。因此在移植阶段推荐加入以下辅助宏与断言:
1 2 3 4 5 6 7
| #define FREERTOS_STACK_WORDS(bytes) ((bytes) / sizeof(StackType_t)) #define RTTHREAD_STACK_BYTES(words) ((words) * sizeof(StackType_t))
static inline void assert_stack_compat(size_t freertos_words, size_t rt_bytes) { configASSERT(RTTHREAD_STACK_BYTES(freertos_words) == rt_bytes); }
|
同时打开 uxTaskGetStackHighWaterMark 或 rt_thread_control(..., RT_THREAD_CTRL_INFO, ...),在调试阶段记录“最低余量”,可提前发现栈配置不匹配的问题。
3. 消息队列 (Message Queue)
3.1 API 对比表
| 功能 |
FreeRTOS Native (queue.h) |
CMSIS-RTOS v2 |
RT-Thread |
| 创建 |
xQueueCreate |
osMessageQueueNew |
rt_mq_create |
| 发送 |
xQueueSend / xQueueSendToFront |
osMessageQueuePut |
rt_mq_send / rt_mq_urgent |
| 接收 |
xQueueReceive |
osMessageQueueGet |
rt_mq_recv |
| ISR 发送 |
xQueueSendFromISR |
osMessageQueuePut |
rt_mq_send |
| ISR 接收 |
xQueueReceiveFromISR |
osMessageQueueGet |
rt_mq_recv |
3.2 关键差异与实现原理
- FreeRTOS: 队列是“循环 buffer + 读写索引”,节点大小恒定且发送/接收都走 memcpy;
xQueueSendFromISR 内部会根据 uxMessagesWaiting 判断是否需要唤醒高优先级任务。
- RT-Thread:
rt_mq 同样是循环 buffer,并利用 suspend_entry 链表管理阻塞线程;为了减小延迟,通常只传 4 字节或指针。
- 大数据传输策略: 两个系统都推荐“队列只传句柄,真实数据放独立内存池”。RT-Thread 还提供了 **邮箱 (Mailbox)**,结构上仅存储一个
rt_ubase_t,因此写入/读取无需 memcpy,延迟更可控。
4. 信号量与互斥量 (Semaphore & Mutex)
4.1 API 对比表
| 功能 |
FreeRTOS Native (semphr.h) |
CMSIS-RTOS v2 |
RT-Thread |
| 二值信号量 |
xSemaphoreCreateBinary |
osSemaphoreNew(1, 1, ...) |
rt_sem_create |
| 计数信号量 |
xSemaphoreCreateCounting |
osSemaphoreNew(max, init, ...) |
rt_sem_create |
| 互斥量 |
xSemaphoreCreateMutex |
osMutexNew |
rt_mutex_create |
| 递归互斥量 |
xSemaphoreCreateRecursiveMutex |
osMutexNew(attr) |
(默认支持递归) |
| 获取 (P 操作) |
xSemaphoreTake |
osSemaphoreAcquire / osMutexAcquire |
rt_sem_take / rt_mutex_take |
| 释放 (V 操作) |
xSemaphoreGive |
osSemaphoreRelease / osMutexRelease |
rt_sem_release / rt_mutex_release |
4.2 详细说明
- 优先级翻转: 两者都实现了优先级继承算法——当低优先级任务持有互斥量阻塞高优先级任务时,调度器临时提升低优先级任务的优先级,直到释放互斥量;信号量不具备该特性。
- 递归锁: FreeRTOS 需要显式创建 RecursiveMutex 并使用
xSemaphoreTakeRecursive;RT-Thread 的 Mutex 结构带 hold 计数与 owner 标记,天然支持同线程多次获取/释放。
- ISR 限制: FreeRTOS 互斥量禁止在中断中操作;RT-Thread 会在编译期
RT_DEBUG 下报警告,但依旧不建议这么做。
5. 事件组 (Event Group)
用于多对多同步,例如“等待 任务 A 和 任务 B 都完成”。
5.1 API 对比表
| 功能 |
FreeRTOS Native (event_groups.h) |
CMSIS-RTOS v2 |
RT-Thread |
| 创建 |
xEventGroupCreate |
osEventFlagsNew |
rt_event_create |
| 发送事件 |
xEventGroupSetBits |
osEventFlagsSet |
rt_event_send |
| 等待事件 |
xEventGroupWaitBits |
osEventFlagsWait |
rt_event_recv |
| 清除事件 |
xEventGroupClearBits |
osEventFlagsClear |
(接收时可选自动清除) |
5.2 逻辑标志与自动清除
- FreeRTOS:
xEventGroupWaitBits 参数 xWaitForAllBits 决定是 AND (所有位都置 1) 还是 OR (任意位置 1);xClearOnExit 可在任务唤醒时自动清零。
- RT-Thread:
rt_event_recv 参数 option 使用 RT_EVENT_FLAG_AND 或 RT_EVENT_FLAG_OR,并可组合 RT_EVENT_FLAG_CLEAR 自动清除;底层实现基于 32 位整型的按位操作,与 FreeRTOS 类似。
- 最佳实践: 事件组适合作为“状态同步”,若要传递数据仍应通过消息队列或邮箱完成功能分离。
6. 软件定时器 (Software Timer)
6.1 API 对比表
| 功能 |
FreeRTOS Native (timers.h) |
CMSIS-RTOS v2 |
RT-Thread |
| 创建 |
xTimerCreate |
osTimerNew |
rt_timer_create |
| 启动 |
xTimerStart |
osTimerStart |
rt_timer_start |
| 停止 |
xTimerStop |
osTimerStop |
rt_timer_stop |
| 模式 |
One-shot / Auto-reload |
osTimerOnce / osTimerPeriodic |
RT_TIMER_FLAG_ONE_SHOT / PERIODIC |
6.2 执行上下文与推荐模板
- FreeRTOS: 定时器回调函数在 定时器服务任务 (Daemon Task) 上下文中执行。绝对不能在回调中调用阻塞 API (如
vTaskDelay),否则会阻塞所有定时器。推荐模板:在回调里向队列发送命令或设置事件位,由普通任务消费。
- RT-Thread:
HARD_TIMER 模式: 回调在 中断上下文 执行 (严禁阻塞),适合做 GPIO 翻转、唤醒等硬实时动作。
SOFT_TIMER 模式: 回调在 timer 线程 执行 (类似 FreeRTOS),同样避免阻塞操作;可调用 rt_event_send 向业务线程播报。
- 调试建议: 对 FreeRTOS 可开启
configUSE_TRACE_FACILITY 结合 vTaskGetInfo 观察定时器任务负载;RT-Thread 则可调用 list_timer 查看 Timer 队列状态。
7. 中断管理 (Interrupt Management)
7.1 关键 API
| 功能 |
FreeRTOS Native |
CMSIS-RTOS v2 |
RT-Thread |
| 进入临界区 |
taskENTER_CRITICAL() |
osKernelLock() |
rt_enter_critical() |
| 退出临界区 |
taskEXIT_CRITICAL() |
osKernelUnlock() |
rt_exit_critical() |
| 关中断 |
portDISABLE_INTERRUPTS() |
- |
rt_hw_interrupt_disable() |
| 开中断 |
portENABLE_INTERRUPTS() |
- |
rt_hw_interrupt_enable() |
7.2 ISR 调用规则与优先级门限
- FreeRTOS:
- 中断中必须使用
FromISR 结尾的 API (例如 xQueueSendFromISR)。
- 这些 API 通常需要一个
BaseType_t *pxHigherPriorityTaskWoken 参数,用于判断是否需要触发上下文切换。
- 退出中断时需调用
portYIELD_FROM_ISR(xHigherPriorityTaskWoken)。
configMAX_SYSCALL_INTERRUPT_PRIORITY: 仅低于(数值更大)该门限的中断才能调用 FromISR API。要将 NVIC 的 0-15 级映射到 FreeRTOS,需要结合芯片实现的优先级位数,例如 STM32F1 仅实现 4 bit,可使用 NVIC_SetPriority(IRQn, <value << (8 - __NVIC_PRIO_BITS)>) 方式保持一致。
- RT-Thread:
- API 内部自动检测当前是线程环境还是中断环境。
- 无需特殊后缀,无需手动处理上下文切换请求。
- RT-Thread 仍限制“中断中不可阻塞”,如
rt_sem_take 在 ISR 环境会立即返回 -RT_EFULL 并记录日志。
8. 内存管理 (Memory Management)
| 功能 |
FreeRTOS Native |
CMSIS-RTOS v2 |
RT-Thread |
| 动态分配 |
pvPortMalloc |
malloc (需配置) |
rt_malloc |
| 动态释放 |
vPortFree |
free |
rt_free |
| 获取剩余内存 |
xPortGetFreeHeapSize |
- |
rt_memory_info (需开启 mem info) |
- FreeRTOS: 内存管理策略由
heap_x.c 文件决定 (heap_1 到 heap_5)。最常用的是 heap_4.c (支持碎片合并);若需要多区域或静态分区,可使用 heap_5.c 并在 vPortDefineHeapRegions 中注册多块 RAM。
- RT-Thread: 默认使用小内存管理算法 (small mem),以链表方式维护空闲块;在需要 determinism 的平台可以切换到 slab/finsh 相关组件。可通过
rt_memory_info 或 memtrace 插件实时查看使用情况。
- 迁移提示: FreeRTOS 的
pvPortMalloc 默认是线程安全的 (因为加锁), RT-Thread 的 rt_malloc 亦如此,但若在 ISR 中分配/释放都会失败,应提前安排对象池或静态缓存。
9. 邮箱 (Mailbox) - RT-Thread 特有
RT-Thread 提供了一种比消息队列更高效的通信机制:邮箱。
- 特点: 固定 4 字节容量 (32 位系统),开销极低。
- 用途: 适合传递指针、状态码。
- FreeRTOS 对应: FreeRTOS 没有专门的 Mailbox,通常创建一个
ItemSize = sizeof(void*) 的 Queue 来模拟。
| 功能 |
FreeRTOS (模拟) |
RT-Thread (rt_mb) |
说明 |
| 创建 |
xQueueCreate(len, sizeof(void *)) |
rt_mb_create(name, len, flag) |
FreeRTOS 仍是循环 buffer;RT-Thread 固定 4 字节。 |
| 发送指针 |
xQueueSend(queue, &ptr, timeout) |
rt_mb_send(mb, (rt_uint32_t)ptr) |
RT-Thread 直接写入 rt_uint32_t,无 memcpy。 |
| 接收指针 |
xQueueReceive(queue, &ptr, timeout) |
rt_mb_recv(mb, &ptr, timeout) |
两者都支持阻塞/超时。 |
| ISR 支持 |
xQueueSendFromISR |
rt_mb_send (自动判 ISR) |
依旧要遵守 configMAX_SYSCALL_INTERRUPT_PRIORITY。 |
| 典型用途 |
线程间传对象指针、IO 完成通知 |
GUI 事件、网络栈包指针、驱动状态机 |
— |
1 2 3
| rt_mailbox_t mb = rt_mb_create("mb", 32, RT_IPC_FLAG_FIFO); rt_mb_send(mb, (rt_uint32_t)&my_data);
|
在 FreeRTOS 中若确需类似功能,可将 Queue 的 uxItemSize 设为 sizeof(void *) 并搭配 xQueueOverwrite 等 API,但由于依旧存在 memcpy,极限性能不及 RT-Thread 的 Mailbox。
10. 调试参考
- FreeRTOS: 推荐开启
configCHECK_FOR_STACK_OVERFLOW (mode 2) 与 configGENERATE_RUN_TIME_STATS,配合 vTaskGetRunTimeStats 输出任务占比,快速确认调度行为是否符合预期。
- RT-Thread: 可在 FinSH 中使用
thread, list_timer, list_mem 等命令,或在代码里调用 rt_thread_dump、rt_timer_dump 了解运行态。