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

发布时间:2026/7/31 16:56:05

RTOS-F429-HAL-任务状态查询API实验(2026/7/30) 目录一任务状态查询 API 大全① vTaskPrioritySet() — 设置任务优先级② uxTaskPriorityGet() — 获取任务优先级③ uxTaskGetNumberOfTasks() — 获取任务总数④ uxTaskGetSystemState() — 获取所有任务状态快照代码示例代码解释⑤ vTaskGetInfo() — 获取单个任务详细信息代码示例⑥ xTaskGetCurrentTaskHandle() — 获取当前任务的句柄⑦ xTaskGetHandle() — 根据任务名反查句柄⑨ eTaskGetState() — 获取任务运行状态⑩ vTaskList() — 表格形式打印所有任务代码示例二实验步骤1改动内容FreeRTOSConfig.hfreertos_demo.cmain.c三申请内容函数1pvPortMalloc / vPortFree2正点原子 动态申请内存mymalloc3对比4在哪定义四补充⑧ uxTaskGetStackHighWaterMark() — 查询任务栈历史最小剩余TaskHandle_t 的本质vTaskList() State 列详解综合对比- vs [i].工程验证知识点vTaskDelete 不会立刻回收 TCBstart_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;// ✅ 也填了需 xGetFreeStackSpacepdTRUE } TaskStatus_t; 你只打印了 pcTaskName、uxCurrentPriority、xTaskNumber 这三个但其他字段你要用也随时能读。比如想打印任务状态加一行 ​ ​ printf(%d\r\n, status_array[i].eCurrentState); // 0Running 1Ready 2Blocked形参含义pxTaskStatusArray指向TaskStatus_t数组的指针函数往里填数据uxArraySize数组大小用任务总数pulTotalRunTime传NULL即可不开运行时统计用不到返值实际填入数组的任务数量用于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); /* 0Running因为就是自己在查自己 */ 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 1configUSE_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我们要么换成pvPortMallocFreeRTOS 自带要么用静态数组。好了随时可以开始移植文件改了什么FreeRTOSConfig.h①configUSE_TRACE_FACILITY0→1 ② 新增INCLUDE_xTaskGetHandle/INCLUDE_uxTaskGetStackHighWaterMark/INCLUDE_xTaskGetCurrentTaskHandle③ 注释掉不存在的trcRecorder.h④ tick 恢复 1000Hzfreertos_demo.ctask2 按顺序调 ⑩~② 共 9 个 API⑧ 栈水位注释备用用pvPortMalloc/vPortFree替代mymallocmain.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 开启可视化追踪配合 Tracealyzer214新增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-142①vTaskPrioritySet ②uxTaskPriorityGet设置任务优先级获取任务优先级151-152③ uxTaskGetNumberOfTasks获取系统当前任务总数165-180④uxTaskGetSystemState pvPortMalloc/vPortFree 获取所有任务的状态快照192-202⑤vTaskGetInfo pvPortMalloc/vPortFree 获取单个任务的详细信息211-213⑥xTaskGetCurrentTaskHandle获取调用者自己的任务句柄225-227⑦xTaskGetHandle(task1)按名字反查句柄243-244⑨eTaskGetState获取任务当前运行状态259-260⑩vTaskList(task_buff)以表格字符串形式输出所有任务信息266-279⑧uxTaskGetStackHighWaterMark#if 0包着 查询任务栈历史最小剩余main.c行号内容24printf 标题 →FreeRTOS Task Status Query API Test!三申请内容函数1pvPortMalloc/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_array和status_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 RAMCPU 独享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的堆默认在内部 SRAMDMA 可用CPU 独享数据mymalloc(SRAMCCM, ...)FreeRTOS 不支持 CCM需要自己封装我们为什么选pvPortMalloc省事不需要额外移植正点的malloc.c线程安全pvPortMalloc内部调了vTaskSuspendAll()多任务并发申请不会冲突正点的mymalloc在多任务下需要加临界区保护我们的堆够用configTOTAL_HEAP_SIZE 36KB就创建三四个任务 偶尔申请小数组完全够4在哪定义靠#include portable.h也是是靠Keil 工程里直接加了这个 .c 文件来编译的。声明在portable.h→FreeRTOS.h实现在heap_4.cFreeRTOS.h └→ #include portable.h └→ void *pvPortMalloc(size_t xSize); // 只有声明 └→ void vPortFree(void *pv); heap_4.cKeil 工程直接编译不被任何文件 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 查自己返值自任务创建以来栈剩余空间的最小值单位word1 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就绪排队等 CPUBeBlocked阻塞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。加了它就能跑。

相关新闻