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

相关推荐
Freak嵌入式4 小时前
MicroPython LVGL基础知识和概念:交互与事件处理
ide·嵌入式·gui·lvgl·micropython·电子·upypi
Freak嵌入式1 天前
LVGL基础知识和概念:视觉样式与资源系统
ide·驱动开发·嵌入式·lvgl·micropython·upypi
FreakStudio2 天前
小作坊 GitHub 协作闭环:fork-sync-dev-pr-merge 实战指南
python·单片机·嵌入式·面向对象·电子diy
阿源-2 天前
UEFI Application 如何调用 Protocol
嵌入式·uefi
C^h2 天前
RT thread使用u8g2点亮oled显示屏
linux·单片机·嵌入式硬件·嵌入式
2023自学中2 天前
正点原子 Linux 驱动开发:多点电容触摸屏实验,gt9147 触摸芯片
linux·驱动开发·嵌入式
charlie1145141912 天前
现代Qt开发——0.1——如何在IDE中配置Qt环境?
开发语言·c++·ide·qt·嵌入式
wsoz2 天前
音视频分布传输协议(AVDTP)
音视频·嵌入式·蓝牙br/edr·avdtp
华清远见成都中心2 天前
嵌入式就业岗位有哪些?
嵌入式·华清远见成都中心·成都嵌入式培训机构