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

相关推荐
嵌入式小企鹅17 小时前
嵌入式面试宝典
学习·面试·嵌入式·嵌入式工程师·高薪offer
星瞳科技OpenMV17 小时前
国家级高新技术企业星瞳科技,定义嵌入式机器视觉行业新标杆
人工智能·嵌入式·图像识别·机器视觉·openmv·星瞳科技·星瞳科技openmv
Hello_Embed17 小时前
【无标题】
网络·笔记·网络协议·tcp/ip·嵌入式
XD74297163618 小时前
001. MSP430G2553 入门总述:从零开始学习这颗单片机
单片机·嵌入式硬件·学习·嵌入式·msp430g2553
CinzWS19 小时前
A53 FPGA原型验证:从RTL到可运行系统的挑战
arm开发·嵌入式·芯片验证·原型验证·a53
Freak嵌入式19 小时前
亲测可用!可本地部署的 MicroPython 开源仿真器
ide·驱动开发·嵌入式·仿真·micropython·upypi
嵌入式小企鹅1 天前
CPU供需趋紧、DeepSeek V4全链适配、小米开源万亿模型
人工智能·学习·开源·嵌入式·小米·算力·昇腾
FreakStudio1 天前
亲测可用!可本地部署的 MicroPython 开源仿真器
python·单片机·嵌入式·面向对象·并行计算·电子diy·电子计算机
飞凌嵌入式2 天前
保姆级教程 | i.MX 93开发板适配Zephyr RTOS全解析
科技·嵌入式
freshman_y2 天前
Linux开发中DTS和/proc/device-tree讲解
linux·嵌入式