FreeRtos源码分析之任务创建和管理(一)
一、任务控制块TCB
在开始学习FreeRtos任务相关源码之前,我们需要先了解一个重要的结构体-任务控制块。FreeRtos的每一个任务都有一个独立的任务控制块TCB,TCB中记录了任务的优先级、名称、栈地址等内容,是FreeRtos任务管理最重要的一个结构体。这里不讨论mpu和newlib相关的内容。TCB结构体如下:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{volatile StackType_t * pxTopOfStack; /*< 指向最后一个入栈的项目 THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */#if ( portUSING_MPU_WRAPPERS == 1 )xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */#endifListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ListItem_t xEventListItem; /*< Used to reference a task from an event list. */UBaseType_t uxPriority; /*< 任务的优先级. 0表示最低优先级. */StackType_t * pxStack; /*< 指向栈空间的起始地址. */char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< 存储任务的名字. */#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )StackType_t * pxEndOfStack; /*< 指向栈的最高地址,当栈的增长方向向上时使用,Cortex-M3的栈增长方向向下,所以不使用这个字段. >*/#endif#if ( portCRITICAL_NESTING_IN_TCB == 1 )UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */#endif#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority; /*< 最后分配给任务的优先级 - 用于优先级继承算法. */UBaseType_t uxMutexesHeld;#endif#if ( configUSE_APPLICATION_TASK_TAG == 1 )TaskHookFunction_t pxTaskTag;#endif#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];#endif#if ( configGENERATE_RUN_TIME_STATS == 1 )uint32_t ulRunTimeCounter; /* 记录任务的运行时间 */#endif#if ( configUSE_NEWLIB_REENTRANT == 1 )struct _reent xNewLib_reent;#endif#if ( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];#endif/* See the comments in FreeRTOS.h with the definition of* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */#endif#if ( INCLUDE_xTaskAbortDelay == 1 )uint8_t ucDelayAborted;#endif#if ( configUSE_POSIX_ERRNO == 1 )int iTaskErrno;#endif
} tskTCB;
几个必须理解的成员:
- pxStack 指向任务栈的起始地址
- pxEndOfStack 任务栈的结束地址,只有在栈地址向上增长时使用
- uxPriority 任务的优先级
- xStateListItem 任务状态链表,由任务的状态列表项所引用,表示该任务的状态(就绪,阻塞,挂起),任务的状态列表后面会详细介绍。
- xEventListItem 任务事件链表
- ulRunTimeCounter 任务的运行时间
ListItem_t是一个双向链表,结构体定义如下:
struct xLIST_ITEM
{TickType_t xItemValue; /*< The value being listed. In most cases this is used to sort the list in descending order. */struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< Pointer to the next ListItem_t in the list. */struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */void * pvOwner; /*< Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */struct xLIST * configLIST_VOLATILE pxContainer; /*< Pointer to the list in which this list item is placed (if any). */
};
pvOwner是一个指针,指向任务控制块的结构体,方便内核根据ListItem_t结构体访问其所在的任务控制块;
二、FreeRtos任务创建流程
FreeRtos任务创建函数:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //任务的入口函数const char * const pcName, //任务名称,默认最长10个字节const configSTACK_DEPTH_TYPE usStackDepth,//任务的堆内存空间void * const pvParameters,//传递给任务的参数UBaseType_t uxPriority,//任务的优先级TaskHandle_t * const pxCreatedTask )//任务的句柄
FreeRtos任务创建流程如下:
- [1] 根据栈增长方向,给任务分配内存
- [2] 初始化栈顶指针pxTopOfStack并检查栈地址是否字节对齐
- [3] 存储任务的任务名和优先级
- [4] 初始化状态表和事件表(xStateListItem和xEventListItem)
- [5] 初始化任务栈(pxPortInitialiseStack)
- [6] 添加任务到就绪任务链表
2.1、任务的内存分配
FreeRtos任务使用的的内存也是通过PvPortMalloc函数分配而来的。这部分内存除了通过xTaskCreate函数指定的usStackDepth之外,还需要额外分配一个TCB结构体大小的内存,用来对任务的内存进行管理。
另外任务内存分配策略和CPU内存的增长方向有关系:
如果CPU的内存增长向下(portSTACK_GROWTH<0),任务的内存布局分布图如下:
如果CPU的内存增长方向向上(portSTACK_GROWTH>0),任务的内存布局图如下:
任务内存分配源码:
#if ( portSTACK_GROWTH > 0 )//如果栈向上增长{/* Allocate space for the TCB. Where the memory comes from depends on* the implementation of the port malloc function and whether or not static* allocation is being used. */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );//先给TCB分配内存if( pxNewTCB != NULL ){/* 再给任务分配内存,大小为 usStackDepth * sizeof( uint32_t ).* 内存的基地址存储在pxNewTCB->pxStack中. */pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */if( pxNewTCB->pxStack == NULL ){/* Could not allocate the stack. Delete the allocated TCB. */vPortFree( pxNewTCB );pxNewTCB = NULL;}}}#else /* portSTACK_GROWTH */{StackType_t * pxStack;/* 先给任务分配内存. */pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */if( pxStack != NULL ){/* 再给任务控制块分配内存. */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */if( pxNewTCB != NULL ){/* Store the stack location in the TCB. */pxNewTCB->pxStack = pxStack;}else{/* The stack cannot be used as the TCB was not created. Free* it again. */vPortFree( pxStack );}}else{pxNewTCB = NULL;}}#endif /* portSTACK_GROWTH */
2.2、初始化栈顶指针并检查任务栈地址是否字节对齐
栈地址的字节对齐主要是为了提高CPU访问数据的效率,以及避免不同cpu之间因数据存储机制的不同导致的数据存取问题。
pxTopOfStack记录了栈顶指针
#if ( portSTACK_GROWTH < 0 ){pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );//栈顶地址必须按照portBYTE_ALIGNMENT字节对齐pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. Checked by assert(). *//* Check the alignment of the calculated top of stack is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );#if ( configRECORD_STACK_HIGH_ADDRESS == 1 ){/* Also record the stack's high address, which may assist* debugging. */pxNewTCB->pxEndOfStack = pxTopOfStack;}#endif /* configRECORD_STACK_HIGH_ADDRESS */}#else /* portSTACK_GROWTH */{pxTopOfStack = pxNewTCB->pxStack;/* Check the alignment of the stack buffer is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );/* The other extreme of the stack space is required if stack checking is* performed. */pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );}#endif /* portSTACK_GROWTH */
2.3、存储任务名和优先级
/* 存储任务的名称到任务控制块TCB. */if( pcName != NULL ){for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than* configMAX_TASK_NAME_LEN characters just in case the memory after the* string is not accessible (extremely unlikely). */if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}}/* Ensure the name string is terminated in the case that the string length* was greater or equal to configMAX_TASK_NAME_LEN. */pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';}else{//如果没有给任务传入一个合法的名字,那么任务名为0pxNewTCB->pcTaskName[ 0 ] = 0x00;}//判断任务的优先级是否越界,最大优先级不能超过configMAX_PRIORITIES定义的值减一,默认是5if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;}
2.4、初始化任务的事件链表
//将pxNewTCB->xStateListItem.pvOwner指向任务控制块,以后可以根据ListItem_t结构体追溯到任务控制块TCBlistSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* 事件列表始终按照优先级顺序排列,优先级越高值越大,Freertos的优先级值越小优先级越高*/listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. *//*将pxNewTCB->xEventListItem.pvOwner指向任务控制块,以后可以根据ListItem_t结构体追溯到任务控制块TCB*/listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
listSET_LIST_ITEM_OWNER和listSET_LIST_ITEM_VALUE的定义如下:
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue ) ( ( pxListItem )->xItemValue = ( xValue ) )
- pxNewTCB->xStateListItem和pxNewTCB->xEventListItem中的pvOwner成员都指向pxNewTCB,后续可以利用这两个链表查到对应的任务控制块TCB;
- pxNewTCB->xStateListItem主要用于任务的状态管理
2.5、初始化任务栈
初始化任务栈部分代码应该算是整个任务创建过程中最难以理解的地方了,想要看懂这部分代码需要先了解对应CPU内核的寄存器架构,这里我们以cortex-M3为例。
Cortex-M3拥有R0-R15共16个通用寄存器和一些特殊功能寄存器。如图:
c函数的前四个实参会依次放入r0~r3寄存器中,第五个参数开始后面参数都会压入堆栈。
初始化任务栈之后,TCB各个指针的指向如下图:
至此,FreeRtos的内存分配结束,可以看出,FreeRtos实际分配的内存比传入的值要大,而实际可用的空间比传入的值要小(栈顶存放R0-R15寄存器)。
- 任务占用的总内存大小=sizeof(TCB)+usStackDepth*4;
- 任务实际可用内存大小=(usStackDepth-16)*4;
pxPortInitialiseStack函数源码如下:
/** See header file for description.*/
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,TaskFunction_t pxCode,void * pvParameters )
{/* Simulate the stack frame as it would be created by a context switch* interrupt. *//* Offset added to account for the way the MCU uses the stack on entry/exit* of interrupts, and to ensure alignment. */pxTopOfStack--;//当前栈地址存放堆栈指针*pxTopOfStack = portINITIAL_XPSR; /* xPSR */pxTopOfStack--;//当前地址存放程序计数器PC指针*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */pxTopOfStack--;//当前地址存放连接寄存器LR*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR *//* 跳过R12,R13,R2,R1寄存器 */pxTopOfStack -= 5; /* R12, R3, R2 and R1. */*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 *//* A save method is being used that requires each task to maintain its* own exec return value. */pxTopOfStack--;*pxTopOfStack = portINITIAL_EXC_RETURN;pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */return pxTopOfStack;
}
2.6、将任务添加到就绪任务列表
FreeRtos使用一个全局数组pxReadyTasksLists存储就绪态任务,其最大长度为用户设定的优先级长度:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
任务初始化完成后,系统会将TCB结构体中的xStateListItem成员存储到就绪态任务所在的全局数组中,代码如下:
#define prvAddTaskToReadyList( pxTCB ) \traceMOVED_TASK_TO_READY_STATE( pxTCB ); \taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
此外还有三个比较重要的全局变量:
- pxCurrentTCB当前任务的任务控制块TCB指针,指向当前正在运行的任务;
- uxCurrentNumberOfTasks记录当前任务总数,如果任务被删除,这个值会减小;
- uxTaskNumber创建的任务总数,任务被删除这个值不变;
这里我们重点看下FreeRtos怎么记录优先级的,记录优先级的代码如下:
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
可以看出FreeRtos的优先级都会存储到一个32位的无符号整数uxReadyPriorities中,并且是按位记录的,比如任务的优先级为0,那么uxReadyPriorities的bit0为1,如果任务的优先级为3,那么uxReadyPriorities的第bit3为1。
到这里,一定会有人不理解,FreeRtos中如果有很多相同优先级的任务,那么pxReadyTasksLists的数组长度设置为最大的优先级值为什么不会会溢出。答案很简单,因为这个优先级数组是一个链表数组,所有相同优先级的任务会以链表的形式存储在链表头为pxReadyTasksLists[pri]的链表中。
优先级链表头 | 存储任务 |
---|---|
pxReadyTasksLists[0] | task1->task2->task3 |
pxReadyTasksLists[1] | task4->task5->task6 |
pxReadyTasksLists[2] | task7->task8->task9 |
vListInsertEnd函数的作用是将新创建的任务添加到链表尾部,对于同等优先级而言,它们在运行时也是有先后顺序的,按照FreeRtos的链表逻辑,具有相同优先级的任务,先创建的任务会优先运行。
FreeRtos带注释源码Gitee地址:https://gitee.com/zBlackShadow/FreeRtos10.4.3.git
使用IAR工程:CORTEX_M4F_STM32F407ZG-SK
FreeRtos源码分析之任务创建和管理(一)相关推荐
- Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean
前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...
- FreeRTOS源码分析与应用开发02:任务管理
目录 1. 任务概述 1.1 任务表示 1.2 任务状态 1.2.1 运行态 1.2.2 就绪态 1.2.3 阻塞态 1.2.4 挂起态 1.3 任务优先级 1.3.1 FreeRTOS优先级配置 1 ...
- Openstack Nova 源码分析 — 使用 VCDriver 创建 VMware Instance
目录 目录 前言 流程图 nova-compute vCenter 前言 在上一篇 Openstack Nova 源码分析 - Create instances (nova-conductor阶段) ...
- netty 5 alph1源码分析(服务端创建过程)
研究了netty的服务端创建过程.至于netty的优势,可以参照网络其他文章.<Netty系列之Netty 服务端创建>是 李林锋撰写的netty源码分析的一篇好文,绝对是技术干货.但抛开 ...
- FreeRTOS源码分析与应用开发01:中断配置与临界段
目录 1. 异常与中断的基本概念 1.1 异常分类 1.2 中断概述 1.2.1 中断处理宜短暂 1.2.2 临界段影响中断实时性 1.3 中断硬件基础 1.3.1 外设 1.3.2 中断控制器 1. ...
- Spark源码分析之九:内存管理模型
Spark是现在很流行的一个基于内存的分布式计算框架,既然是基于内存,那么自然而然的,内存的管理就是Spark存储管理的重中之重了.那么,Spark究竟采用什么样的内存管理模型呢?本文就为大家揭开Sp ...
- zuul源码分析之Request生命周期管理
为什么80%的码农都做不了架构师?>>> zuul核心框架 zuul是可以认为是一种API-Gateway.zuul的核心是一系列的filters, 其作用可以类比Servle ...
- FreeRTOS源码分析与应用开发07:事件标志组
目录 1. 概述 2. 事件标志组类型 3. 创建事件标志组 4. 删除事件标志组 5. 设置事件标志位 5.1 任务级设置 5.2 中断级设置 6. 清除事件标志位 6.1 任务级清除 6.2 中断 ...
- FreeRTOS源码分析与应用开发04:消息队列
目录 1. 队列结构 2. 创建队列 2.1 动态创建队列 2.1.1 xQueueCreate函数 2.1.2 xQueueGenericCreate函数 2.1.3 xQueueGenericRe ...
最新文章
- remote: HTTP Basic: Access denied
- 岛屿类-网格类问题-DFS | 力扣200. 岛屿数量
- 单细胞一站式分析网站CeDR Atlas使用指南
- how is table select_all_icon being loaded
- 宏块帧内预测的具体过程
- QToolButton设置图片填充满_韩国高人气图片素材大合集!每张图,都是桌面壁纸,逼格满满...
- const用在成员函数后
- 利用Bat命令批量修改文件名
- android项目设计实验报告模板,Android实验报告模板_实验一.doc
- 前端面试必考的「 Webpack详解 」都在这了
- extern int a 和int a的区别
- Android知识点深究
- 从键盘输入直接三角形的两条直接边的长度,求斜边的长度和三角形的面积,计算结果保留两位小数
- JavaScript中的onload详解
- 蓝牙通话耳机质量哪个好?通话质量好的蓝牙耳机
- 32 --> 详解 OpenWRT系统框架基础软件模块之netifd
- lumen php命令,lumen控制器调用artisan
- 绿色IT,从环保到经济效益
- 谷歌和百度的精确搜索技巧
- java实现给PDF文件添加图片水印,java实现给PDF文件添加文字水印