
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏》
❄专栏传送门 :《产品测评专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
目录
[1.2 任务创建函数 xTaskCreate](#1.2 任务创建函数 xTaskCreate)
[1.5 初始化任务链表](#1.5 初始化任务链表)
前言
在 FreeRTOS 实时操作系统中,任务管理 是最核心的基础功能,所有业务逻辑都依托任务运行。很多初学者只会调用 API,却不清楚 xTaskCreate、vTaskDelay、vTaskDelete 底层到底做了什么,遇到栈溢出、任务卡死、调度异常等问题完全无从下手。
本文基于 FreeRTOS 源码 ,从零拆解任务控制块 TCB、任务创建全流程、任务删除、任务延时四大核心知识点,逐行解析源码、图文结合,彻底吃透 FreeRTOS 任务管理底层原理,适合 STM32 等 Cortex‑M 平台开发者学习。
一、任务创建
在 FreeRTOS 中,任务本质上是一个永不返回的无限循环函数。在多任务系统设计中,我们会根据功能模块的边界,将完整的系统拆分为多个独立、互不阻塞的函数,每个函数就对应一个任务,每个任务负责项目的一块独立功能。
这些任务会在 FreeRTOS 调度器的管理下分时轮转运行。
一个标准的 FreeRTOS 任务函数,必须满足以下 4 个核心特性:
永不返回,无限循环
任务函数一旦启动就会持续运行,不能有return语句,也不能执行到函数末尾退出,否则会触发系统断言错误。标准写法是在函数内部嵌套for(;;)或while(1)无限循环,在循环中实现任务的业务逻辑。
同一函数可创建多个任务
同一个任务函数,可以被用来创建多个独立的任务实例,多个任务可以共享同一个函数入口。FreeRTOS 会为每个任务实例分配独立的上下文,保证任务间互不干扰。
每个任务拥有独立栈空间
每个任务都有专属的栈(Stack)内存:任务 A 的局部变量会保存在任务 A 的栈中,任务 B 的局部变量会保存在任务 B 的栈中,函数运行时的所有栈开销(局部变量、函数调用、中断现场保护等),都只会使用当前任务自己的栈空间,从根本上隔离了任务间的内存数据。
全局 / 静态变量共享,需注意访问冲突
函数中定义的全局变量、静态变量会存放在内存的固定区域,被所有任务共享。这类共享资源存在「并发访问冲突」的风险,因此在任务开发中,应优先使用任务局部变量;若必须使用共享变量,需通过互斥锁、信号量等同步机制保护临界区,避免数据竞争。
创建任务是操作系统最基础的操作。我们从数据结构 → API → 内存分配 → 初始化 → 挂链表完整走一遍流程。
1.1、任务控制块TCB
在 FreeRTOS 中,每个任务都有一个专属 TCB(Task Control Block),内核依靠它管理任务的优先级、栈、状态、名称等所有信息。
源码如下:
cpp
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 指向任务栈顶 */
ListItem_t xStateListItem; /* 任务状态链表节点 */
ListItem_t xEventListItem; /* 事件链表节点 */
UBaseType_t uxPriority; /* 任务优先级 */
StackType_t *pxStack; /* 指向栈起始地址 */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/* 任务名 */
} tskTCB;
TCB 就是任务的身份证 + 户口本,内核靠它识别、调度、管理任务。
1.2 任务创建函数 xTaskCreate
任务创建 API 是我们最常用的函数,先看参数含义:
cpp
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 任务函数指针
const char * const pcName, // 任务名字
const uint16_t usStackDepth, // 栈大小(单位:字,4字节)
void * const pvParameters, // 入口参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask // 任务句柄
);
这个API函数使用的是动态分配内存的方式创建任务
动态创建任务的流程如下:

FreeRTOS 兼容两种栈方向:
portSTACK_GROWTH > 0:向上增长(低地址 → 高地址)
portSTACK_GROWTH < 0:向下增长(高地址 → 低地址)
Cortex‑M 架构(STM32)默认向下增长!

为了防止栈覆盖 TCB 结构体,FreeRTOS 做了严格的分配顺序:
栈向上增长:先分配 TCB,再分配栈。
栈向下增长:先分配栈,再分配 TCB。
1.3、初始化任务prvInitialiseNewTask
首先计算出任务栈顶地址(堆栈增长方向不同计算方法不同),再进行地址对齐。
若堆栈向下增长(STM32采用这种增长方式):
cpp
#if( portSTACK_GROWTH < 0 )
{
/*计算栈顶地址*/
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
/*将地址作8字节对齐--&(~0x0007)*/
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. */
/* Check the alignment of the calculated top of stack is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
}
pxNewTCB->pxStack为任务块堆栈的起始地址,pxTopOfStack 指向当前任务块堆栈的栈顶。如下图所示:

1.4、添加新任务到就绪链表
进入临界区(屏蔽中断)
任务计数 +1
第一次创建任务 → 初始化所有链表
将任务插入就绪链表
如果新任务优先级更高 → 立即任务切换
将任务添加至就绪列表,用下列函数:
cpp
prvAddTaskToReadyList( pxNewTCB );
if( xSchedulerRunning != pdFALSE )
{
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
}
注意:prvAddTaskToReadyList( pxNewTCB )实际调用的函数是vListInsertEnd,即采用尾插法,在链表的尾部插入元素。
cpp
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
pxList:列表项要插入的列表
pxNewListItem :要插入的列表项
1.5 初始化任务链表
在FreeRTOS中,一个任务有多种状态,每种状态对应一个链表,将任务置于不同的状态,实质上就是将任务添加至对应的状态链表。
链表初始化函数如下:
cpp
vListInitialise( List_t * const pxList )
cpp
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
pxIndex表示列表项的索引号,初始状态时,链表中只有xListEnd一个元素,因此pxIndex指向xListEnd。
二、任务删除
任务删除是 FreeRTOS 中常用的任务管理操作,用于销毁不再需要的任务、释放系统资源。我们先从 API 用法入手,再深入源码解析底层实现。
删除任务的函数原型如下:运行
void vTaskDelete( TaskHandle_t xTaskToDelete );
参数说明
| 参数 | 描述 |
|---|---|
| xTaskToDelete | 任务句柄,使用 xTaskCreate 创建任务时可以得到一个句柄。也可传入 NULL,这表示删除自己。 |
任务删除的三种场景
在实际开发中,任务删除分为三种典型场景:
自杀:任务自己删除自己,调用 vTaskDelete(NULL)。
被杀:别的任务执行 vTaskDelete(pvTaskCode),pvTaskCode 是自己的句柄
杀人:执行 vTaskDelete(pvTaskCode),pvTaskCode 是别的任务的句柄
⚠️ 注意:不建议频繁创建 / 删除任务,会造成内存碎片,推荐使用任务挂起 / 恢复替代。
删除任务的流程如下:
总结
本文围绕 FreeRTOS 任务管理核心,详细拆解了任务创建与任务删除的底层原理、API 用法及源码逻辑,从任务控制块 TCB 的作用、任务创建的内存分配与初始化流程,到任务删除的场景与安全机制.
