FreeRTOS 的公共类型把应用代码与处理器移植层隔开。先查当前内核版本和 portable/.../portmacro.h,再判断宽度、符号和栈单位。本文以 FreeRTOS-Kernel V11.1.0、GCC ARM_CM3、32 位 Cortex-M3 为具体例子;其他移植不能只凭类型名推断。
内核类型与单位
跳转到“内核类型与单位”| 类型 | 用途 | 本文移植中的含义 |
|---|---|---|
BaseType_t | API 返回值、布尔式判断等 | 有符号 long,该 ARM ABI 下为 32 位 |
UBaseType_t | 任务优先级、队列长度等 | 无符号 long,该 ARM ABI 下为 32 位 |
TickType_t | Tick 计数和等待时间 | 按配置使用无符号 16 位或 32 位整数 |
StackType_t | 任务栈的一个元素 | uint32_t,一个元素为 4 字节 |
TaskHandle_t | 由内核返回的任务句柄 | 指向不透明任务结构的指针,不应解引用私有 TCB |
TaskHandle_t * | 接收任务句柄的输出参数 | 句柄变量的地址,例如 &worker_handle |
BaseType_t 并不是无符号类型。UBaseType_t 才表达无符号的对应类型。桌面宿主移植的 long 和栈元素可能与 STM32 不同,因此调试输出也不能直接假定所有类型都适合 %lu。
#include "FreeRTOS.h"#include "task.h"
/* 这些断言特意限定本文的 GCC ARM_CM3 构建。 */_Static_assert(sizeof(StackType_t) == 4, "CM3 stack element is 4 bytes");_Static_assert(sizeof(BaseType_t) == 4, "CM3 BaseType_t is 32 bits");_Static_assert((BaseType_t)-1 < 0, "BaseType_t must be signed");_Static_assert((UBaseType_t)-1 > 0, "UBaseType_t must be unsigned");
size_t stack_bytes_for_words(configSTACK_DEPTH_TYPE words){ return (size_t)words * sizeof(StackType_t);}此转换只适用于结果可由 size_t 表示的栈深度。原生 xTaskCreate() 的栈深度是 StackType_t 元素数;512 在此移植中为 2048 字节。CMSIS-RTOS2 的 osThreadAttr_t.stack_size 则直接使用字节,不应再次误乘或漏乘 4。
任务控制块保存状态、优先级、栈指针等内核信息;任务栈保存调用现场和局部变量。句柄用于调用公共 API,既不是整块任务内存,也不是可以随意读写的栈地址。
Tick 的配置与换算
跳转到“Tick 的配置与换算”旧配置使用 configUSE_16_BIT_TICKS 的值:值为 1 选择 16 位,值为 0 选择 32 位。仅检查该宏有没有定义,会把两种情况混淆。
V11.1.0 还支持 configTICK_TYPE_WIDTH_IN_BITS;它与旧配置二选一。内核公共定义包含 16、32、64 位选项,但 V11.1.0 的 GCC ARM_CM3 移植只接受 16 或 32 位。不能因为公共头文件出现 TICK_TYPE_WIDTH_64_BITS 就认为这个端口能直接使用 64 位 Tick。
若 Tick 频率为 ,一个 Tick 的名义长度为 。100 Hz 对应 10 ms,1000 Hz 对应 1 ms,这些是可选配置,不是不同操作系统的固定规律。
pdMS_TO_TICKS(ms) 把毫秒换算成 Tick。V11.1.0 默认实现用 64 位中间乘法后做整数除法,因此会向下截断,最终结果仍须能由 TickType_t 表示。例如 100 Hz 下 1 ms 得到 0 Tick;不能把它当成至少等待 1 ms 的保证。应用可按需要检查零值、拆分超长等待,或提供经过范围验证的换算策略。
16 位 Tick 在 1000 Hz 下约每 65.536 秒回绕;32 位约每 49.71 天回绕。内核阻塞 API 会处理其支持范围内的 Tick 溢出,应用自行比较绝对时间时仍需采用无符号差值和明确的最大间隔。低功耗 tickless 模式可能暂时抑制周期中断并补偿经过的时间,逻辑 Tick 不等于始终存在一一对应的物理 SysTick 中断。
命名约定是阅读提示
跳转到“命名约定是阅读提示”
图保留了原学习资料的完整表格。表中 task.c、timer.c 应对应实际文件 tasks.c、timers.c;short、long 的字节数还取决于 ABI,不能把历史表格当成所有平台的类型定义。
| 常见前缀 | 阅读含义 |
|---|---|
c、s、l | 历史约定中的 char、short、long |
x | 移植定义类型,如 BaseType_t,也常用于句柄 |
u | 无符号修饰,与基础类型前缀组合 |
p | 指针修饰,与基础类型前缀组合 |
uc、pc | 常见的 unsigned char、char 指针变量 |
函数 v... | 通常返回 void |
函数 x... | 通常返回移植定义类型或句柄,须看声明 |
函数 pv... | 通常返回 void * |
宏 port...、task... | 移植层、任务层宏 |
宏 pd...、config...、err... | 公共定义、配置、错误码 |
名字不能代替 C 类型检查。pdTRUE、pdPASS 通常为 1,pdFALSE、pdFAIL 通常为 0,但具体 API 的返回约定须单独阅读:队列发送成功只说明数据进入队列,不代表消费者已经完成业务处理。
常见 API 按任务、队列、信号量/互斥量、软件定时器、事件组、流/消息缓冲区和任务通知组织。通知属于任务 API,信号量和互斥量建立在队列实现之上;不能据此认为它们的使用约束相同。ISR 可用性、阻塞行为和所有权均以对应 API 文档为准。