FreeRTOS 任务管理源码解析---任务创建与删除全流程----FreeRTOS专栏

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏

专栏传送门《产品测评专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、任务创建

1.1、任务控制块TCB

[1.2 任务创建函数 xTaskCreate](#1.2 任务创建函数 xTaskCreate)

1.3、初始化任务prvInitialiseNewTask

1.4、添加新任务到就绪链表

[1.5 初始化任务链表](#1.5 初始化任务链表)

二、任务删除

总结


前言

在 FreeRTOS 实时操作系统中,任务管理 是最核心的基础功能,所有业务逻辑都依托任务运行。很多初学者只会调用 API,却不清楚 xTaskCreatevTaskDelayvTaskDelete 底层到底做了什么,遇到栈溢出、任务卡死、调度异常等问题完全无从下手。

本文基于 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 的作用、任务创建的内存分配与初始化流程,到任务删除的场景与安全机制.

相关推荐
dddwjzx2 小时前
嵌入式Linux C应用编程入门——标准IO库
嵌入式
pai同学2 小时前
ESP-IDF+vscode开发ESP32第十二讲——event
嵌入式
凉、介3 小时前
KVM + QEMU 虚拟化
笔记·学习·嵌入式·arm·qemu·虚拟化·kvm
dddwjzx18 小时前
嵌入式Linux C应用编程入门——文件IO进阶
嵌入式
2023自学中1 天前
imx6ull 开发板, mame 模拟器,运行游戏 测试
linux·游戏·嵌入式·开发板
dddwjzx1 天前
嵌入式Linux C应用编程入门——文件IO
嵌入式
fzm52981 天前
车载ECU单元测试技术与应用研究
c语言·自动化测试·单元测试·嵌入式·白盒测试
用户120487221613 天前
Linux驱动编译与加载
linux·嵌入式
用户805533698033 天前
Input 子系统架构:Core、Handler、Driver 三层是怎么协作的
linux·嵌入式
用户805533698033 天前
RK-Forge外设系列开篇 - 把板子从「能启动」变成「能用」:Ethernet/SPI/MMC 三个纯接线外设
linux·github·嵌入式