RTOS-F429-HAL-任务状态查询API实验(2026/7/30)

📅 2026/7/31 17:10:14 👁️ 阅读次数 📝 编程学习
RTOS-F429-HAL-任务状态查询API实验(2026/7/30)

目录

一:任务状态查询 API 大全

① vTaskPrioritySet() — 设置任务优先级

② uxTaskPriorityGet() — 获取任务优先级

③ uxTaskGetNumberOfTasks() — 获取任务总数

④ uxTaskGetSystemState() — 获取所有任务状态快照

代码示例:

代码解释:

⑤ vTaskGetInfo() — 获取单个任务详细信息

代码示例:

⑥ xTaskGetCurrentTaskHandle() — 获取当前任务的句柄

⑦ xTaskGetHandle() — 根据任务名反查句柄

⑨ eTaskGetState() — 获取任务运行状态

⑩ vTaskList() — 表格形式打印所有任务

代码示例:

二:实验步骤:

1:改动内容

FreeRTOSConfig.h

freertos_demo.c

main.c

三:申请内容函数

1:pvPortMalloc / vPortFree

2:正点原子 动态申请内存mymalloc

3:对比

4:在哪定义

四:补充

⑧ uxTaskGetStackHighWaterMark() — 查询任务栈历史最小剩余

TaskHandle_t 的本质

vTaskList() State 列详解

综合对比:-> vs [i].

工程验证知识点

vTaskDelete 不会立刻回收 TCB

start_task 临界区问题


一:任务状态查询 API 大全

vTaskPrioritySet()— 设置任务优先级

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
形参含义
xTask目标任务句柄,传NULL表示改自己的优先级
uxNewPriority新优先级(0 ~ configMAX_PRIORITIES-1)

返值:无。如果新优先级比当前任务高,会立刻触发任务切换。

需要宏INCLUDE_vTaskPrioritySet = 1✅ 我们已开

uxTaskPriorityGet()— 获取任务优先级

UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );
形参含义
xTask目标任务句柄,传NULL= 查自己

返值:任务的优先级数值。

需要宏INCLUDE_uxTaskPriorityGet = 1✅ 已开

uxTaskGetNumberOfTasks()— 获取任务总数

UBaseType_t uxTaskGetNumberOfTasks( void );

返值:系统当前管理的任务总数(含就绪、阻塞、挂起、未回收的已删除任务)。

需要宏:无,始终可用 ✅

uxTaskGetSystemState()— 获取所有任务状态快照

UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime ); ​ 在申请的内存下填充了任务的结构体所有内容,返回值:实际写入的任务数 ,用于for 循环遍历用 typedef struct xTASK_STATUS { TaskHandle_t xHandle; // ✅ 也填了 const char *pcTaskName; // ✅ 任务名 UBaseType_t xTaskNumber; // ✅ 任务编号 eTaskState eCurrentState; // ✅ 也填了(R/B/S/D) UBaseType_t uxCurrentPriority; // ✅ 当前优先级 UBaseType_t uxBasePriority; // ✅ 也填了 uint32_t ulRunTimeCounter; // ✅ 也填了(需开宏) StackType_t *pxStackBase; // ✅ 也填了 uint16_t usStackHighWaterMark;// ✅ 也填了(需 xGetFreeStackSpace=pdTRUE) } TaskStatus_t; 你只打印了 pcTaskName、uxCurrentPriority、xTaskNumber 这三个,但其他字段你要用也随时能读。比如想打印任务状态,加一行: ​ ​ printf("%d\r\n", status_array[i].eCurrentState); // 0=Running 1=Ready 2=Blocked
形参含义
pxTaskStatusArray指向TaskStatus_t数组的指针,函数往里填数据
uxArraySize数组大小(用任务总数)
pulTotalRunTimeNULL即可(不开运行时统计用不到)

返值:实际填入数组的任务数量,用于for 循环遍历用。

需要宏configUSE_TRACE_FACILITY = 1❌ 我们还没开

代码示例:
/* ════════════════════════════════════════════════════════ * ④ uxTaskGetSystemState — 获取所有任务的状态快照 * 原型:UBaseType_t uxTaskGetSystemState( * TaskStatus_t *pxTaskStatusArray, // [输出] 状态数组 * UBaseType_t uxArraySize, // 数组容量 * uint32_t *pulTotalRunTime); // NULL=不开运行统计 * 返值:实际写入数组的任务数量 * 需要:configUSE_TRACE_FACILITY = 1 * TaskStatus_t 结构体含:pcTaskName / uxCurrentPriority / xTaskNumber / eCurrentState 等 * ════════════════════════════════════════════════════════ */ // 第1步:挖坑(申请一块空地) status_array = pvPortMalloc(sizeof(TaskStatus_t) * task_num); /* 按任务数申请数组 */ if (status_array != NULL) /* 分配成功才继续 */ { // 第2步:填坑(往空地里写任务信息) // 此时 status_array[0]、[1]、[2]... 才有了真正的任务名、优先级、编号, //返回task_num2 实际写入的任务数 ,用于for 循环遍历用 task_num2 = uxTaskGetSystemState(status_array, task_num, NULL); printf("任务名\t\t任务优先级\t\t任务编号\r\n"); for (i = 0; i < task_num2; i++) /* 遍历打印每个任务 */ { printf("%s\t\t%ld\t\t%ld\r\n", status_array[i].pcTaskName, /* 任务名字符串 */ status_array[i].uxCurrentPriority, /* 当前优先级 */ status_array[i].xTaskNumber); /* 任务编号(全局唯一) */ } printf("\r\n"); vPortFree(status_array); /* ★ 用完了释放,防止内存泄漏 */ status_array = NULL; /* 悬空指针归零 */ }
代码解释:
UBaseType_t task_num = 0; /* ③ uxTaskGetNumberOfTasks 返回值 */ UBaseType_t task_num2 = 0; /* ④ uxTaskGetSystemState 实际写入的任务数*/ TaskStatus_t *status_array = NULL; /* ④ 存所有任务状态的数组(动态申请) */ 有task_num 和 task_num2 是因为, start_up 创建以后把自己删除了,再读一遍获得实际写入的任务数,不浪费内存。 ​ // 第1步:挖坑(申请一块空地) status_array = pvPortMalloc(sizeof(TaskStatus_t) * task_num); /* 按任务数申请数组 */ ​ // 第2步:填坑(往空地里写任务信息) task_num2 = uxTaskGetSystemState(status_array, task_num, NULL); ​ 有多个任务时 : 因为返回的status_array是结构体 一块连续的空地,切成 N 个格子 ​ pvPortMalloc(sizeof(TaskStatus_t) * 4) // 40 字节 × 4 = 160 字节 ​ 空地(160 字节连续内存): ┌──────────────┬──────────────┬──────────────┬──────────────┐ │ status_array │ status_array │ status_array │ status_array │ │ [0] │ [1] │ [2] │ [3] │ │ (40 字节) │ (40 字节) │ (40 字节) │ (40 字节) │ │ task2 的信息 │ start_task │ IDLE 的信息 │ task1 的信息 │ └──────────────┴──────────────┴──────────────┴──────────────┘ [0]、[1]、[2] 就是指针偏移——编译器知道你指针类型是 TaskStatus_t *,写 [i] 就自动往后跳 i × 40 字节。 ​ ​ 单个任务时: // 分配 1 个 → 用 -> 直接访问,简洁 status_info = pvPortMalloc(sizeof(TaskStatus_t)); vTaskGetInfo(Task2_Handler, status_info, pdTRUE, eInvalid); printf("%s\r\n", status_info->pcTaskName); // 一步到位 ​ // 分配 N 个 → 用 [i]. 遍历访问 status_array = pvPortMalloc(sizeof(TaskStatus_t) * task_num); uxTaskGetSystemState(status_array, task_num, NULL); for (i = 0; i < task_num2; i++) printf("%s\r\n", status_array[i].pcTaskName); // 挨个来 -> 和 [i]. 本质一样,只是写法不同——知道只有一个就用 ->,有一堆就用下标。编译器翻译出来是同一回事。

vTaskGetInfo()— 获取单个任务详细信息

void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState ); eTaskState 枚举 ​ eRunning = 0 // 正在跑 eReady = 1 // 就绪,排队等 CPU eBlocked = 2 // 阻塞中(vTaskDelay/等信号量/等队列) eSuspended = 3 // 被挂起 eDeleted = 4 // 已删除,TCB 还没回收 eInvalid = 5 // 无效(用作占位)
形参含义
xTask目标任务句柄
pxTaskStatus输出参数,指向TaskStatus_t结构体
xGetFreeStackSpacepdTRUE= 顺便查栈剩余;pdFALSE= 不查
eStateeInvalid= 自动获取当前状态;也可手动指定一个值

返值:无,数据填入pxTaskStatus

需要宏configUSE_TRACE_FACILITY = 1❌ 还没开

代码示例:
status_info = pvPortMalloc(sizeof(TaskStatus_t)); //这里只申请一个任务结构体所需的内存即可 ​ 上面要打印的所有任务的状态快照,所以要乘上任务总数 pvPortMalloc(sizeof(TaskStatus_t) * task_num); ​ status_info = pvPortMalloc(sizeof(TaskStatus_t)); /* 只查一个任务,申请一个即可 */ if (status_info != NULL) { vTaskGetInfo(Task2_Handler, status_info, pdTRUE, eInvalid); printf("任务名:%s\r\n", status_info->pcTaskName); /* "task2" */ printf("任务优先级:%ld\r\n", status_info->uxCurrentPriority); /* 4(刚刚改了) */ printf("任务编号:%ld\r\n", status_info->xTaskNumber); /* FreeRTOS 自动分配 */ printf("任务状态:%d\r\n", status_info->eCurrentState); /* 0=Running(因为就是自己在查自己) */ printf("栈剩余:%u word (%u bytes)\r\n", status_info->usStackHighWaterMark, // ← pdTRUE 填的就是它 status_info->usStackHighWaterMark * 4); vPortFree(status_info); /* ★ 用完了释放,防止内存泄漏 */ status_info = NULL; /* 悬空指针归零 */ } ​

xTaskGetCurrentTaskHandle()— 获取当前任务的句柄

TaskHandle_t xTaskGetCurrentTaskHandle( void );

返值:调用者自己的任务句柄。

需要宏INCLUDE_xTaskGetCurrentTaskHandle = 1,但我们的定义藏在#if configUSE_TRACE_FACILITY == 1块里,开了 TRACE 才会生效 ⚠️

xTaskGetHandle()— 根据任务名反查句柄

TaskHandle_t xTaskGetHandle( const char *pcNameToQuery );
形参含义
pcNameToQuery任务名字符串(创建时起的名字)

返值:匹配的任务句柄;没找到返回NULL注意:这个函数比较慢,不要频繁调用。

需要宏INCLUDE_xTaskGetHandle = 1❌ 还没开

eTaskGetState()— 获取任务运行状态

eTaskState eTaskGetState( TaskHandle_t xTask );
形参含义
xTask目标任务句柄

返值(枚举):

eRunning = 0 // 正在运行 eReady = 1 // 就绪(排队等 CPU) eBlocked = 2 // 阻塞(在等延时/信号量/队列) eSuspended = 3 // 挂起 eDeleted = 4 // 已删除,TCB 还没回收 eInvalid = 5 // 无效状态

需要宏INCLUDE_eTaskGetState = 1✅ 已开

vTaskList()— 表格形式打印所有任务

void vTaskList( char *pcWriteBuffer );
形参含义
pcWriteBuffer输出缓冲区指针,每个任务约需 40 字节

返值:无。写入类似这样的表格字符串:

Name State Priority Stack Num task1 R 2 98 5 task2 R 2 102 6 IDLE R 0 118 4 vTaskList 输出的 State 列用的是单字符,不是数字: 字符 枚举值 含义 R eReady 就绪(排队等 CPU) B eBlocked 阻塞(vTaskDelay/等信号量) S eSuspended 挂起 D eDeleted 已删除但 TCB 未回收 源码在 tasks.c:143-146: #define tskBLOCKED_CHAR 'B' #define tskREADY_CHAR 'R' #define tskDELETED_CHAR 'D' #define tskSUSPENDED_CHAR 'S' 你的实验输出大概长这样: Name State Priority Stack Num task1 B 2 98 5 ← B=阻塞中(vTaskDelay 等着) task2 R 4 102 6 ← R=就绪(正在跑/刚跑完打印) IDLE R 0 118 4 ← 空闲任务,没人跑就它跑 没有 X = Running——因为 vTaskList 是自己调自己,调用时它处于"跑完了一步、还没到下一步"的状态,就被归到 Ready 里了

需要宏configUSE_TRACE_FACILITY = 1+configUSE_STATS_FORMATTING_FUNCTIONS = 1⚠️ 第一个还没开

代码示例:

static char task_buff[512]; // 静态数组,编译时就定好了,存在数据段 对比一下我们用的两种内存: task_buff[512] status_array 方式 静态,直接声明数组 动态,pvPortMalloc 空间 512 字节,编译时就占好了 运行时从堆里挖 时间 永远存在,程序结束才释放 vPortFree 后归还 用哪个 大小固定、不用还 大小运行时才知道、用完要还 vTaskList 只是往你给它的缓冲区里写字符串,它不管你这块内存是静态还是堆。你给一个能用的地址就行。 static char task_buff[512]; // vTaskList 输出缓冲区 vTaskList(task_buff); /* 把表格字符串写入 task_buff */ printf("%s\r\n", task_buff); /* 一次性打印整张表 */

二:实验步骤:

汇总一下,我们FreeRTOSConfig.h目前缺以下配置:

要改的宏当前值需要值影响的函数
configUSE_TRACE_FACILITY01④⑤⑥⑩
INCLUDE_xTaskGetHandle没定义1
INCLUDE_uxTaskGetStackHighWaterMark没定义1

另外正点用了mymalloc,我们要么换成pvPortMalloc(FreeRTOS 自带),要么用静态数组。

好了,随时可以开始移植

文件改了什么
FreeRTOSConfig.hconfigUSE_TRACE_FACILITY0→1 ② 新增INCLUDE_xTaskGetHandle/INCLUDE_uxTaskGetStackHighWaterMark/INCLUDE_xTaskGetCurrentTaskHandle③ 注释掉不存在的trcRecorder.h④ tick 恢复 1000Hz
freertos_demo.ctask2 按顺序调 ⑩~② 共 9 个 API(⑧ 栈水位注释备用),用pvPortMalloc/vPortFree替代mymalloc
main.c标题改为 Task Status Query API Test

跟正点代码的主要差异

正点我们
mymalloc(SRAMIN, ...)pvPortMalloc(...)(FreeRTOS 自带堆)
myfree没写(正点漏了)vPortFree(...)用完了主动释放
task_buff全局char[xxx]一样,static char task_buff[512]
printf有 LCD 辅助纯串口 printf

可以 Keil 编译下载了。

1:改动内容

FreeRTOSConfig.h

行号改了什么
52configTICK_RATE_HZ20 → 1000(恢复正常 1ms 心跳)
177configUSE_TRACE_FACILITY0 → 1 (开启可视化追踪(配合 Tracealyzer)
214新增INCLUDE_xTaskGetHandle 1( 根据任务名反查句柄)
215新增INCLUDE_uxTaskGetStackHighWaterMark 1(查栈历史最小剩余)
216新增INCLUDE_xTaskGetCurrentTaskHandle 1(获取当前任务句柄)
264-267#include "trcRecorder.h"注释掉(我们没有这个文件)

总之trcRecorder.h跟 FreeRTOS 版本高低没关系——它是另一个付费软件 Tracealyzer 的库。正点原子跟着 FreeRTOS 官方模板一并放进来了,但我们没买也没装这个工具,所以注释掉就行。

freertos_demo.c

行号内容
47static char task_buff[512]— vTaskList 输出缓冲区
140-142vTaskPrioritySet+ ②uxTaskPriorityGet(设置任务优先级+获取任务优先级)
151-152③ `uxTaskGetNumberOfTasks(获取系统当前任务总数)
165-180uxTaskGetSystemState+ pvPortMalloc/vPortFree (获取所有任务的状态快照)
192-202vTaskGetInfo+ pvPortMalloc/vPortFree (获取单个任务的详细信息)
211-213xTaskGetCurrentTaskHandle(获取调用者自己的任务句柄)
225-227xTaskGetHandle("task1")(按名字反查句柄)
243-244eTaskGetState(获取任务当前运行状态)
259-260vTaskList(task_buff)(以表格字符串形式输出所有任务信息)
266-279uxTaskGetStackHighWaterMark#if 0包着) (查询任务栈历史最小剩余)

main.c

行号内容
24printf 标题 →"FreeRTOS Task Status Query API Test!"

三:申请内容函数

1:pvPortMalloc/vPortFree

函数原型

void *pvPortMalloc( size_t xWantedSize ); // 从 FreeRTOS 堆里申请内存 void vPortFree( void *pv ); // 归还内存

参数和返值

pvPortMallocvPortFree
参数xWantedSize= 要多少字节pv= 之前 malloc 拿到的指针
返值成功 → 指针;失败 →NULL
注意一定要检查返值是不是 NULL!传 NULL 会直接 return,安全

我们工程代码示例:

TaskStatus_t *status_array = NULL; /* ④ 存所有任务状态的数组(动态申请) */ UBaseType_t task_num = 0; /* ③ uxTaskGetNumberOfTasks 返回值,task_num=任务总数 */ status_array = pvPortMalloc(sizeof(TaskStatus_t) * task_num); /* 按任务数申请数组 */ //用完释放 vPortFree(status_array); /* ★ 用完了释放,防止内存泄漏 */ status_array = NULL; /* 悬空指针归零 */ pvPortMalloc 返回 void *,C 语言里 void * 可以直接赋值给任何指针。 TaskStatus_t *status_array = NULL; // pvPortMalloc 只需要一个参数:要多少字节 // ↓ status_array = pvPortMalloc( sizeof(TaskStatus_t) * task_num ); // └─────┬─────┘ └───┬───┘ // 一个结构体多大 要存几个 // ≈ 40 字节 = 4 个 // └──────────┬──────────┘ // 总共要 160 字节 拆开看: TaskStatus_t 结构体 ≈ 40 字节(句柄 + 名字指针 + 优先级 + 编号 + 状态...) × task_num = 4 个任务 = 要申请的内存 = 160 字节,刚好放下 4 个 TaskStatus_t pvPortMalloc(160) 在堆里挖 160 字节,返回首地址给 status_array。然后你可以像用数组一样用: status_array[0].pcTaskName // 第 1 个任务的名字 status_array[1].uxCurrentPriority // 第 2 个任务的优先级 status_array[2].xTaskNumber // 第 3 个任务的编号 // ... sizeof(TaskStatus_t) * task_num 就是算"我要的数组总共占多少字节"。跟你之前用 HAL 库写 sizeof(GPIO_InitTypeDef) 一个道理。 你项目里所有动态内存都是从 同一块地 里挖的: FreeRTOSConfig.h: #define configTOTAL_HEAP_SIZE ((size_t)(36 * 1024)) // 划了 36KB 地皮 heap_4.c: static uint8_t ucHeap[36 * 1024]; // 这就是那块地 谁在从这块地里挖东西? ucHeap[36KB] ──────────────────────────────────────── │ xTaskCreate(task1) → pvPortMalloc(栈+TCB) → ~600B │ │ xTaskCreate(task2) → pvPortMalloc(栈+TCB) → ~600B │ │ xTaskCreate(start) → pvPortMalloc(栈+TCB) → ~600B │ │ 你的代码 → pvPortMalloc(160B) → status_array 数组 │ │ ...还剩很多... │ └──────────────────────────────────────────────────── xTaskCreate 内部也是调 pvPortMalloc 给任务分配栈和 TCB,跟你自己调 pvPortMalloc 完全同一口井里打水。井的大小就是 configTOTAL_HEAP_SIZE = 36KB。 内存不够用了就把这个数改大(F429 有 256KB SRAM,随便加)。

2:正点原子 动态申请内存mymalloc

你看第 27 行和第 130/141 行:

#include "./MALLOC/malloc.h" // 行 27:引用正点自己的 malloc // 行 130:按任务数申请数组 status_array = mymalloc(SRAMIN, sizeof(TaskStatus_t) * task_num); // 行 141:申请单个结构体 status_array2 = mymalloc(SRAMIN, sizeof(TaskStatus_t));

跟我们唯一区别就是内存函数不同,其他逻辑完全一样。另外正点还没释放内存——status_arraystatus_array2申请完用完就丢了,没调myfree。这个实验里只申请一次问题不大,实际项目里就是内存泄漏。

3:对比

正点mymallocFreeRTOSpvPortMalloc
所属正点原子自己写的内存管理FreeRTOS 官方内存管理
源码MALLOC/malloc.cFreeRTOS/portable/MemMang/heap_4.c
内存来源分池:SRAMIN / SRAMEX / SRAMCCM单池ucHeap[configTOTAL_HEAP_SIZE]
申请mymalloc(SRAMIN, size)pvPortMalloc(size)
释放myfree(SRAMIN, ptr)vPortFree(ptr)

STM32F429 有三块物理上独立的 SRAM:

┌─────────────────┐ │ SRAMIN (128KB) │ ← 内部 SRAM1,最快,默认用这个 ├─────────────────┤ │ SRAMEX (8MB) │ ← 外部 SDRAM,大但慢 ├─────────────────┤ │ SRAMCCM (64KB) │ ← CCM RAM,CPU 独享,DMA 访问不了 └─────────────────┘

正点的mymalloc(SRAMIN, size)让你指定从哪个池子里挖。FreeRTOS 的pvPortMalloc只有一个池子(你想用外部 SDRAM 就得换heap_5.c)。

正点必须在main()先初始化内存池才能用

my_mem_init(SRAMIN); // 初始化内部 SRAM 池 my_mem_init(SRAMEX); // 初始化外部 SDRAM 池 my_mem_init(SRAMCCM); // 初始化 CCM 池

FreeRTOS 的堆在heap_4.c里静态定义好了,不用初始化,开箱即用。


用哪个池的决策

场景正点选哪个池FreeRTOS 怎么做
普通变量/数组mymalloc(SRAMIN, ...)pvPortMalloc(...),从内部 SRAM 的堆里拿
大块数据(图片、字库)mymalloc(SRAMEX, ...)走外部 SDRAM改用heap_5.c配多区,或者自己用pvPortMalloc不够再外扩
DMA 缓冲区mymalloc(SRAMIN, ...)不能放 CCM一样,pvPortMalloc的堆默认在内部 SRAM,DMA 可用
CPU 独享数据mymalloc(SRAMCCM, ...)FreeRTOS 不支持 CCM(需要自己封装)

我们为什么选pvPortMalloc

  1. 省事:不需要额外移植正点的malloc.c

  2. 线程安全pvPortMalloc内部调了vTaskSuspendAll(),多任务并发申请不会冲突;正点的mymalloc在多任务下需要加临界区保护

  3. 我们的堆够用configTOTAL_HEAP_SIZE = 36KB,就创建三四个任务 + 偶尔申请小数组,完全够

4:在哪定义

#include "portable.h",也是是靠Keil 工程里直接加了这个 .c 文件来编译的。

声明在portable.hFreeRTOS.h,实现在heap_4.c

FreeRTOS.h └→ #include "portable.h" └→ void *pvPortMalloc(size_t xSize); // 只有声明! └→ void vPortFree(void *pv); heap_4.c(Keil 工程直接编译,不被任何文件 include) └→ void *pvPortMalloc(size_t xWantedSize) { ... } // 真正的实现 └→ void vPortFree(void *pv) { ... }

Keil 编译时把heap_4.c当成独立的 .c 文件编译成 .o,链接的时候跟你的freertos_demo.c拼在一起,pvPortMalloc的调用就对上了。

所以如果你去 Keil 工程里把heap_4.c移除,编译会报 "undefined symbol pvPortMalloc"。加了它就能跑。


四:补充

uxTaskGetStackHighWaterMark()— 查询任务栈历史最小剩余

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
形参含义
xTask目标任务句柄,NULL= 查自己

返值:自任务创建以来栈剩余空间的最小值(单位:word,1 word = 4 字节)。值越接近 0 越危险,说明任务栈快溢出了。

需要宏INCLUDE_uxTaskGetStackHighWaterMark = 1✅ 已开

代码示例

task_stack_min = uxTaskGetStackHighWaterMark(Task2_Handler); printf("task2 历史剩余最小堆栈为:%ld word (%ld bytes)\r\n", task_stack_min, task_stack_min * 4);

开发阶段建议常开,用来确认TASK_STACK_SIZE设得合不合理,太紧了就调大。确认没问题后可以关掉省开销。


TaskHandle_t的本质

typedef void * TaskHandle_t; // task.h:103

就是void *的别名——通用指针。创建任务时拿到的是指向任务 TCB 的地址,FreeRTOS 故意不暴露 TCB 结构体细节,只给一个不透明句柄。


vTaskList()State 列详解

字符枚举值含义
ReReady就绪(排队等 CPU)
BeBlocked阻塞(vTaskDelay/等信号量)
SeSuspended挂起
DeDeleted已删除但 TCB 未回收

源码在tasks.c:143-146

#define tskBLOCKED_CHAR 'B' #define tskREADY_CHAR 'R' #define tskDELETED_CHAR 'D' #define tskSUSPENDED_CHAR 'S'

没有X= Running——因为vTaskList是自己调自己,调用时任务处于"跑完了上一步、还没到下一步"的状态,被归到 Ready 里。


综合对比:->vs[i].

用法场景
ptr->field只有 1 个结构体pvPortMalloc(sizeof(TaskStatus_t))
arr[i].field有 N 个结构体pvPortMalloc(sizeof(TaskStatus_t) * N)

本质一样,->[i].编译器翻译出来是同一回事,只是写法不同。


工程验证知识点

vTaskDelete不会立刻回收 TCB

删除自己后uxTaskGetNumberOfTasks()可能短暂返回旧数字(含已删除任务),几 ms 后 idle 任务回收 TCB 才变少。正常现象。

start_task临界区问题

vTaskDelete(NULL)后不能写taskEXIT_CRITICAL()——因为vTaskDelete不会返回。正确做法:要么去掉临界区(任务创建不需要保护),要么taskEXIT_CRITICAL()写在vTaskDelete之前。

所以如果你去 Keil 工程里把heap_4.c移除,编译会报 "undefined symbol pvPortMalloc"。加了它就能跑。