FreeRTOS内存管理和任务管理
FreeRTOS是指小型实时操作系统内核。作为一个轻量级的操作系统,其功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。
FreeRTOS内存管理和任务管理
一 .FreeRTOS 整体架构
1、FreeRTOS简介
FreeRTOS是指小型实时操作系统内核。作为一个轻量级的操作系统,其功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。
2、FreeRTOS架构框图

FreeRTOS支持市面上的多种芯片架构,如Cortex-M 、Cortex-R 、Cortex-A、RISC-V等,可以通过官方提供的Demo快速完成移植。除基础内核外,FreeRTOS包含了丰富的组件和第三方库,还有专门应用于物联网的AWS版本等。
二. FreeRTOS 编程规范
1、编码标准
FreeRTOS的核心代码遵从MISRA-C(汽车产业软件可靠性协会)编码标准。如果出现有不满足标准的会在代码中显示注释出来。可以在代码中全局搜索"MISRA exception"找到不满足MISRA-C标准的编码。例:
if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
2、命名规则
2.1 宏
2.1.1 规则
前缀:小写,作为宏的起始部分(所在文件的简写)
其余部分为大写,两个单词之间用下划线隔开(第一个大写和前缀之间没有下划线间隔)
2.1.2 实例
portmacro.h
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long
FreeRTOSConfig.h
#define configUSE_PREEMPTION 1
#define configCPU_CLOCK_HZ ( 100000000 )
task.c task.h
#define tskDEFAULT_INDEX_TO_NOTIFY ( 0 )
#define tskRUNNING_CHAR ( 'X' )
queue.c queue.h
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 )
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 )
#define queueOVERWRITE ( ( BaseType_t ) 2 )
2.2、变量
命名规则:位宽前缀Name1Name2…Namex
2.2.1 stdint 命名的变量
8bit(char)无符号:uint8_t 前缀 ‘uc’
8bit(char)有符号:int8_t 前缀 ‘c’
uint8_t ucQueueType;
int8_t cRxLock;
char cStatus;
16bit(short)无符号:uint16_t 前缀 ‘us’
16bit(short)有符号:int16_t 前缀 ‘s’
FreeRTOS中没有直接用 uint16_t 和 int16_t 命名的变量
32bit(long)无符号:uint32_t 前缀 ‘ul’
32bit(long)有符号:int32_t 前缀 ‘l’
uint32_t ulStackDepth
int32_t lArrayIndex;
2.2.2 FreeRTOS 定义的数据类型
FreeRTOS 的自定义的数据类型如下:
/* integer data type */
typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
typedef uint32_t TickType_t; /* 32bit hardware*/
/* defined by freertos */
typedef struct tskTaskControlBlock * TaskHandle_t;
typedef struct QueueDefinition * QueueHandle_t;
typedef xTIMER Timer_t;
typedef struct EventGroupDef_t * EventGroupHandle_t
FreeRTOS自定义的数据类型只有两种前缀,如下:
整型无符号前缀 ‘ux’
整型有符号和FreeRTOS自定义的数据类型前缀 ‘x’
/* integer data type */
UBaseType_t uxTimerNumber;
BaseType_t xListWasEmpty;
StackType_t xStack[ STACK_SIZE ];
TickType_t xNextExpireTime;
/* defined by freertos */
TaskHandle_t xTimerTaskHandle = NULL;
QueueHandle_t xTimerQueue = NULL;
Timer_t xNewTimer;
EventGroupHandle_t xEventGroup = NULL;
2.2.3 指针类型
在2.2.1和2.2.2的数据类型的命名前面增加 ‘p’
uint8_t * pucStackByte;
int8_t * pcOriginalReadPosition;
uint32_t * pulNotificationValue;
StackType_t * pxTopOfStack;
BaseType_t * pxHigherPriorityTaskWoken
UBaseType_t *puxVariableToIncrement;
TickType_t * const pxTicksToWait;
TaskHandle_t *pxCreatedTask;
Queue_t * pxNewQueue ;
Timer_t * const pxTimer;
EventGroup_t * pxEventBits;
2.3、函数
2.3.1 static 函数
只在当前文件有效的, 被 static 修饰: 增加前缀 “prv” ,private的意思 。不区分有无返回值、返回值是否为指针等,统一固定前缀 “prv”。
规则: prvName1Name2…Namex
实例如下:
/* task.c */
static void prvDeleteTCB( TCB_t * pxTCB )
/* queue.c */
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void * pvItemToQueue, const BaseType_t xPosition )
2.3.2 全局函数
有返回值前缀为 ‘x’ 或’ux’,无返回值前缀为 ‘v’
返回值为指针,增加前缀 ‘p’
前缀后面紧跟着所在文件名的缩写
实例如下:
/* task.c */
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
/* queue.c */
BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue )
/* timer.c */
void vTimerSetTimerID( TimerHandle_t xTimer, void * pvNewID )
/* porttable.h */
void * pvPortMalloc( size_t xSize )
3、数据类型
见 2.2节,变量的命令。主要就是三种数据类型
stdint.h 命名的整型
FreeRTOS 命名的整型
FreeRTOS自定义的 链表、句柄等服务于各个模块的结构体
4、注释和风格
注释:只用 /* */ 进行注释
头文件中会给出函数的说明和例程,方便查阅和使用
源文件中关键处也会有对应的注释,辅助理解
缩进:4个空格
关键字 for、if、while等后面没有空格
实例如下:
/**************** timer.h **************/
/**
* BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer );
*
* Queries a timer to see if it is active or dormant.
*
BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer ) PRIVILEGED_FUNCTION;
/**************** timer.c**************/
BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer )
{
BaseType_t xReturn;
Timer_t * pxTimer = xTimer;
configASSERT( xTimer );
/* Is the timer in the list of active timers? */
taskENTER_CRITICAL();
{
if( ( pxTimer->ucStatus & tmrSTATUS_IS_ACTIVE ) == 0 )
{
xReturn = pdFALSE;
}
else
{
xReturn = pdTRUE;
}
}
taskEXIT_CRITICAL();
return xReturn;
} /*lint !e818 Can't be pointer to const due to the typedef. */
三. FreeRTOS 内存管理
1、内存管理的作用
嵌入式软件开发中,有一定代码规模的软件产品,会自己进行内存管理。针对操作系统而言,内存管理也是其核心功能。基本思路是将一段已知的内存块给到对应的软件产品,作为它的内存池。内存管理将软件产品中需要操作内存的,都限制在这个内存池中。这样能在复杂的系统中,便于内存的规划和问题定位。
2、FreeRTOS的内存策略
FreeRTOS支持静态内存和动态内存,其选择取决于应用场景。
2.1 静态内存
静态内存一般用在系统对可靠性要求非常高的场景。静态内存是编译器自动分配在栈区;分配时间可以认为固定为0,且大小固定。但是内存使用效率低。
在FreeRTOS的内核中,静态内存接口在功能模块后面增加后缀“static”单独实现(configSUPPORT_STATIC_ALLOCATION设置为 1),需要显示的将内存传到对应的功能模块中,如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer ) PRIVILEGED_FUNCTION;
puxStackBuffer 即为创建任务时,给到当前任务的静态内存。
2.2 动态内存
动态内存一般用在系统对可靠性要求不高的场景。动态内存在运行过程中被分配,一般分配的耗时不固定。但是内存的使用效率很高。
针对动态内存,FreeRTOS规定了统一的内存接口,在内核或者应用开发中都调用定好的内存接口。基于接口,用户可以使用官方的动态内存策略,也可以自己实现。这样将内存管理和FreeRTOS的内核实现通过接口完全隔离,使得系统更加的灵活。 在不同的应用场景中我们根据需要去选择不同的接口实现即可。configSUPPORT_DYNAMIC_ALLOCATION设置为 1
接口如下:
/*
- Map to the memory management routines required for the port.
*/
void * pvPortMalloc( size_t xSize ) ;
void vPortFree( void * pv ) ;
void vPortInitialiseBlocks( void ) ;
size_t xPortGetFreeHeapSize( void ) ;
2.3 heap文件
如果FreeRTOS是动态的创建对象,FreeRTOS认为直接使用 C标准库的 malloc 和 free 存在如下问题:
它们在嵌入式系统中并不是总是可用的
它们可能会消耗大量的代码空间
它们不是线程安全的
它们是不确定性的
FreeRTOS提供了5个 heap文件,简要说明如下:
heap_1.c: 只实现了 pvPortMalloc ,不允许内存的释放,申请时间确定。适用于不需要释放内存的场景,相当于是“静态内存”
heap_2.c: 允许内存的释放,申请内存时用了最佳匹配算法
heap_3.c: 只是对 malloc 和 free进行简单封装,以使的在FreeRTOS中使用时安全的
heap_4.c: 在heap_2的基础上,在释放内存的时候增加了相邻内存块的合并算法,减少碎片的产生
heap_5.c: 在heap_4的基础上,能够跨多个非相邻的内存区域。
除了 heap_1.c ,申请内存都是不确定的,但是比标准C库要好很多
3、heap_4.c 详解
这是 FreeRTOS 的一个内存管理模块,具体实现了动态内存分配(pvPortMalloc)和释放(vPortFree),并带有合并相邻空闲块的功能,以减少内存碎片。
3.1 宏定义**
#define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( xHeapStructSize << 1 ) )
- 定义最小内存块大小,至少为
xHeapStructSize的两倍。
#define heapBITS_PER_BYTE ( ( size_t ) 8 )
- 假设一个字节为 8 位。
#define heapSIZE_MAX ( ~( ( size_t ) 0 ) )
- 定义
size_t能表示的最大值,用于检查内存大小溢出。
#define heapBLOCK_ALLOCATED_BITMASK ( ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 ) )
- 使用最高位标记内存块是否已分配。
3.2 数据结构 BlockLink_t
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK * pxNextFreeBlock; /**< 指向下一个空闲块 */
size_t xBlockSize; /**< 当前块大小(包含 BlockLink_t) */
} BlockLink_t;
- 这是内存块的链表结构,用于管理内存的分配和空闲状态。
- 每个块包含两部分:
- 指向下一个空闲块的指针
pxNextFreeBlock。 - 当前块的大小
xBlockSize,包括链表头部的大小。
- 指向下一个空闲块的指针
3.3 全局变量
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
static BlockLink_t xStart; // 链表起始节点(虚拟头节点,不分配内存)
static BlockLink_t * pxEnd; // 指向链表的最后一个空闲块
static size_t xFreeBytesRemaining = 0; // 当前剩余堆内存大小
ucHeap是实际的堆空间。xStart是虚拟头节点,用于指向第一个空闲块。pxEnd是链表末尾的标志节点。xFreeBytesRemaining跟踪当前剩余可用的内存。
3.4 主要函数分析
1. prvHeapInit
c复制代码static void prvHeapInit( void )
{
BlockLink_t * pxFirstFreeBlock;
portPOINTER_SIZE_TYPE uxStartAddress, uxEndAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
/* 确保堆起始地址对齐 */
uxStartAddress = ( portPOINTER_SIZE_TYPE ) ucHeap;
if( ( uxStartAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
uxStartAddress += ( portBYTE_ALIGNMENT - 1 );
uxStartAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= ( size_t ) ( uxStartAddress - ( portPOINTER_SIZE_TYPE ) ucHeap );
}
/* 设置链表起点和终点 */
xStart.pxNextFreeBlock = ( void * ) heapPROTECT_BLOCK_POINTER( uxStartAddress );
xStart.xBlockSize = 0;
uxEndAddress = uxStartAddress + ( portPOINTER_SIZE_TYPE ) xTotalHeapSize;
uxEndAddress -= ( portPOINTER_SIZE_TYPE ) xHeapStructSize;
uxEndAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( BlockLink_t * ) uxEndAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( NULL );
/* 初始化第一个空闲块 */
pxFirstFreeBlock = ( BlockLink_t * ) uxStartAddress;
pxFirstFreeBlock->xBlockSize = ( size_t ) ( uxEndAddress - ( portPOINTER_SIZE_TYPE ) pxFirstFreeBlock );
pxFirstFreeBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxEnd );
/* 更新全局变量 */
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
}
作用
- 初始化堆的链表结构。
- 确保堆空间从一个对齐的地址开始。
- 第一个空闲块覆盖整个堆空间(除了
pxEnd)。
2. pvPortMalloc
void * pvPortMalloc( size_t xWantedSize )
{
// 如果需要分配的大小为非零,则调整大小以满足内存对齐要求
if( xWantedSize > 0 )
{
// 为内存块的头部大小(管理数据)增加额外的空间
xWantedSize += xHeapStructSize;
// 确保分配的大小满足端口定义的字节对齐要求
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
size_t xAdditionalRequiredSize = portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK );
xWantedSize += xAdditionalRequiredSize;
}
}
// 暂停所有任务,以防止任务切换干扰内存分配操作
vTaskSuspendAll();
{
// 如果堆尚未初始化,则初始化它
if( pxEnd == NULL )
{
prvHeapInit();
}
// 检查是否有足够的内存可以分配
if( xWantedSize <= xFreeBytesRemaining )
{
BlockLink_t * pxPreviousBlock = &xStart; // 链表的头部(虚拟块)
BlockLink_t * pxBlock = heapPROTECT_BLOCK_POINTER( xStart.pxNextFreeBlock ); // 获取第一个空闲块
// 遍历链表,找到一个足够大的空闲块
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock != pxEnd ) )
{
pxPreviousBlock = pxBlock;
pxBlock = heapPROTECT_BLOCK_POINTER( pxBlock->pxNextFreeBlock );
}
// 如果找到足够大的块,则分配内存
if( pxBlock != pxEnd )
{
// 计算返回给用户的内存地址(跳过块头)
void * pvReturn = ( void * ) ( ( uint8_t * ) pxBlock + xHeapStructSize );
// 从空闲链表中移除当前块
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
// 如果当前块的剩余空间大于最小块大小,则拆分块
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
// 创建一个新的块,表示当前块的剩余部分
BlockLink_t * pxNewBlockLink = ( void * ) ( ( uint8_t * ) pxBlock + xWantedSize );
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; // 更新剩余块大小
pxBlock->xBlockSize = xWantedSize; // 更新分配块的大小
// 将新块插入空闲链表
pxNewBlockLink->pxNextFreeBlock = pxPreviousBlock->pxNextFreeBlock;
pxPreviousBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxNewBlockLink );
}
// 更新剩余内存大小
xFreeBytesRemaining -= pxBlock->xBlockSize;
// 标记当前块已被分配(通常会清除块头标志位)
heapALLOCATE_BLOCK( pxBlock );
// 返回分配的内存地址
return pvReturn;
}
}
}
// 恢复任务切换
( void ) xTaskResumeAll();
// 如果内存分配失败,返回 NULL
return NULL;
}
作用
- 分配指定大小的内存块。
- 找到合适的空闲块,分割并返回。
- 更新全局变量以反映分配后的内存状态。
3. vPortFree
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
if( pv != NULL )
{
puc -= xHeapStructSize;
BlockLink_t * pxLink = ( void * ) puc;
vTaskSuspendAll();
{
heapFREE_BLOCK( pxLink );
prvInsertBlockIntoFreeList( pxLink );
}
( void ) xTaskResumeAll();
}
}
作用
- 释放指定的内存块。
- 调用
prvInsertBlockIntoFreeList将其重新插入链表,并尝试合并相邻空闲块。
4. prvInsertBlockIntoFreeList
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert )
{
BlockLink_t * pxIterator;
// 找到适合插入的位置,遍历链表直到找到比要插入块大的位置
for( pxIterator = &xStart; heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) < pxBlockToInsert;
pxIterator = heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) )
{
// 空循环体,因为主要操作在条件和更新语句中完成
}
// pxIterator 指向当前块的前一个块,用于检查是否可以与前面的块合并
uint8_t * puc = ( uint8_t * ) pxIterator;
// 检查要插入的块是否与当前块连续,如果是,则合并它们
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
// 合并当前块和要插入的块
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator; // 更新要插入的块指针,指向合并后的块
}
// 更新指针,指向插入块的内存地址
puc = ( uint8_t * ) pxBlockToInsert;
// 检查插入的块是否与下一个空闲块连续,如果是,则合并它们
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) )
{
// 合并插入块和下一个块
pxBlockToInsert->xBlockSize += heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock )->xBlockSize;
// 将插入块的下一个指针指向下一个块的下一个指针
pxBlockToInsert->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock )->pxNextFreeBlock;
}
else
{
// 如果不能合并,将插入块的下一个指针指向当前块的下一个指针
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
// 将当前块的下一个指针指向插入的块
pxIterator->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxBlockToInsert );
}
作用
- 将释放的内存块插入空闲链表。
- 尝试合并前后的相邻块以减少碎片化。
4、总结
pvPortMalloc时分割的内存块,不会发生合并,直接走插入流程 (理论就是这样:分割下来的是一块大内存的后面,只能和后面的 合并,如果能合并就不会分开)
只有在 pvPortFree的时候才会发生内存合并。xPortGetFreeHeapSize 和 vPortGetHeapStats 对debug内存问题有一定的帮助。 比如可以调用他们确认系统中有无内存泄露(有泄露的话空闲内存量会一直减小)
四. FreeRTOS 任务管理
FreeRTOS 通过 基于优先级的抢占式调度器 实现任务管理,负责管理任务的创建、切换、调度、挂起和删除。以下是 FreeRTOS 任务管理的核心原理和实现机制。
1、FreeRTOS 任务管理的基本原理
1.1 任务的基本结构
每个任务在 FreeRTOS 中被抽象为一个 TCB(Task Control Block),其中存储了任务运行所需的信息:
- 任务状态:运行态、就绪态、阻塞态、挂起态。
- 任务优先级:影响调度器的选择。
- 任务栈指针:保存任务的运行上下文。
- 任务句柄:供用户和调度器引用任务。
- 任务周期信息:记录任务延时或阻塞所需的时间。
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack; // 任务栈指针
ListItem_t xStateListItem; // 用于链接任务的链表项
UBaseType_t uxPriority; // 任务优先级
char pcTaskName[configMAX_TASK_NAME_LEN]; // 任务名称
// ... 其他信息
} TCB_t;
1.2 任务状态
任务可以处于以下几种状态,调度器会根据任务的状态决定哪个任务运行:
- 运行态(Running):
- 当前被 CPU 执行的任务。
- 系统中只能有一个任务处于运行态。
- 就绪态(Ready):
- 所有条件满足,可以立即执行的任务。
- 阻塞态(Blocked):
- 等待某事件发生(如延时、信号量、消息队列)。
- 挂起态(Suspended):
- 被显式挂起的任务,不参与调度。
- 删除态(Deleted):
- 已删除但尚未释放内存的任务。
1.3 调度器的工作机制
FreeRTOS 的调度器是 基于优先级的抢占式调度器,负责决定哪个任务应该运行:
- 优先级调度:
- FreeRTOS 中任务优先级由用户定义,调度器总是选择最高优先级的任务运行。
- 时间片轮转:
- 如果多个任务具有相同优先级,则调度器按照时间片轮转执行这些任务。
- 抢占式调度:
- 高优先级任务进入就绪态时,立即抢占当前运行的低优先级任务。
- 中断优先:
- 中断处理优先于任务,系统会在中断完成后重新调度任务。
2、FreeRTOS 调度器的核心实现
2.1 调度器的启动
- 函数
vTaskStartScheduler():- 启动调度器后,任务切换由调度器接管。
- 它初始化系统时间基准和任务列表,然后调用第一个任务。
void vTaskStartScheduler( void )
{
// 初始化系统和调度器
prvInitialiseTaskLists();
xSchedulerRunning = pdTRUE;
// 启动第一个任务
prvStartFirstTask();
}
- 函数
prvStartFirstTask():- 加载第一个任务的上下文,并开始执行。
2.2 任务切换(上下文切换)
任务切换是 FreeRTOS 任务管理的核心机制,它通过保存和恢复任务上下文实现任务间的切换。
- 任务上下文:
- 每个任务都有独立的栈,用于存储:
- CPU 寄存器值。
- 程序计数器(PC)。
- 状态寄存器。
- 当任务切换时,当前任务的上下文会保存在栈中,新任务的上下文会从其栈中恢复。
- 每个任务都有独立的栈,用于存储:
- 上下文切换的触发条件:
- 任务调用
taskYIELD()主动让出 CPU。 - 中断触发任务切换。
- 延时或阻塞超时后任务重新进入就绪态。
- 任务调用
- 上下文切换过程:
- 保存当前任务上下文:
- 将 CPU 寄存器值压栈。
- 更新当前任务的栈指针:
- 保存到任务的
pxTopOfStack。
- 保存到任务的
- 加载新任务上下文:
- 恢复
pxTopOfStack指向的栈内容到 CPU 寄存器。
- 恢复
- 保存当前任务上下文:
2.3 就绪列表
FreeRTOS 维护多个任务列表,用于存储不同优先级的任务状态:
- 就绪列表(Ready List):
- 每个优先级一个列表,存储所有处于就绪态的任务。
- 延时列表(Delay List):
- 存储所有处于阻塞态且有延时的任务。
- 挂起列表(Suspend List):
- 存储所有被挂起的任务。
就绪列表结构:
static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
- 每个任务通过链表项(
ListItem_t)链接到就绪列表。
2.4 时间片轮转
时间片轮转是调度器在多个相同优先级任务之间分配 CPU 时间的一种机制。
- 实现机制:
- 使用链表存储相同优先级的任务。
- 每次切换时,从列表中取下一个任务执行。
- 当前任务重新加入链表尾部。
- 触发条件:
- 系统时钟节拍(SysTick)中断到达。
2.5 阻塞态的管理
- 任务延时(
vTaskDelay):- 当前任务调用
vTaskDelay后,被移动到延时列表中。
- 当前任务调用
- 延时列表的更新:
- 在每次系统时钟中断中,减小延时列表中每个任务的剩余时间。
- 如果某任务的延时到达,将其移入就绪列表。
3、FreeRTOS 任务管理的关键函数
3.1 创建任务
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void * pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pxCreatedTask
);
- 创建一个新任务并将其添加到就绪列表。
3.2 延时任务
void vTaskDelay(const TickType_t xTicksToDelay);
- 将当前任务移动到延时列表,并延迟指定的时钟周期。
3.3 挂起和恢复任务
- 挂起任务:
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
- 恢复任务:
void vTaskResume(TaskHandle_t xTaskToResume);
3.4 删除任务
void vTaskDelete(TaskHandle_t xTaskToDelete);
- 将任务从调度器中移除,并释放相关资源。
4、FreeRTOS 任务管理的特点
- 实时性:
- 高优先级任务能够及时响应事件。
- 支持中断嵌套和快速切换。
- 灵活性:
- 用户可以动态创建、删除、挂起和恢复任务。
- 低开销:
- 核心数据结构和算法设计紧凑,适合嵌入式系统。
- 扩展性:
- 支持多种同步机制(信号量、队列、事件组)。
5、总结
FreeRTOS 的任务管理通过任务控制块(TCB)、任务列表和抢占式调度机制实现。调度器基于优先级选择任务运行,同时利用时间片轮转机制在相同优先级任务之间分配 CPU 时间。这种设计为嵌入式系统提供了高效、灵活的任务管理能力。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)