前文提要
FreeRTOS学习(1)—为什么使用RTOS
FreeRTOS学习(2)-链表和节点之结构体分析
FreeRTOS学习(3)-链表和节点程序分析
FreeRTOS学习(4)-什么是任务?
FreeRTOS学习(5)-任务之静态创建函数解析
本节将为大家解析任务创建函数中的核心部分,任务的初始化。
1、任务初始化函数prvInitialiseNewTask
(1)源代码
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, /* 任务入口 */
const char * const pcName, /* 任务名称,字符串形式 */
const uint32_t ulStackDepth, /* 任务栈大小,单位为字 */
void * const pvParameters, /* 任务形参 */
TaskHandle_t * const pxCreatedTask, /* 任务句柄 */
TCB_t *pxNewTCB ) /* 任务控制块指针 */
{
StackType_t *pxTopOfStack;
UBaseType_t x;
/* 获取栈顶地址 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
//pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* 向下做8字节对齐 */
pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );
/* 将任务的名字存储在TCB中 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == 0x00 )
{
break;
}
}
/* 任务名字的长度不能超过configMAX_TASK_NAME_LEN */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 初始化TCB中的xStateListItem节点 */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
/* 设置xStateListItem节点的拥有者 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 初始化任务栈 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
/* 让任务句柄指向任务控制块 */
if( ( void * ) pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
}
(2)源代码分析
①获取任务栈的栈顶地址,并确保该地址是8字节对齐的。
·这里对栈顶地址的计算公式做一个说明。
pxNewTCB->pxStack这是的任务的栈空间起始地址。Arm的栈的生长方式是向下生长。也就是栈底是低地址,栈顶是高地址。而且因为计数是从0开始,因此要在深度ulStackDepth基础上减一。
·
为什么要做八字节对齐?
性能优化:许多处理器在访问对齐的内存时效率更高。对于 Cortex-M3 处理器来说,对齐的内存访问可以减少总线周期,从而提高访问速度和整体系统性能。
硬件要求:有些处理器对未对齐的内存访问有严格要求,如果访问未对齐的内存,可能会导致异常或错误。因此,为了确保系统的稳定性和正确性,通常需要进行内存对齐。
具体做法:
0x0007 的二进制表示为 0000 0000 0000 0000 0000 0000 0000 0111。
取反后变为 1111 1111 1111 1111 1111 1111 1111 1000,这就是一个掩码,用于清除最低3位。
②将任务名字存储在TCB中
通过一个简单的for循环进行存储,较为简单,这里不再展开叙述。
③初始化任务控制块的任务节点,设置拥有者
·初始化:vListInitialiseItem函数初始化xStateListItem节点,确保它处于已知的初始状态。
·设置拥有者:listSET_LIST_ITEM_OWNER宏设置xStateListItem节点的拥有者为pxNewTCB,以便在任务调度过程中正确引用任务控制块。
④初始化任务栈pxPortInitialiseStack(难点)–单独解析
/
/
/
2、初始化任务栈pxPortInitialiseStack
(1)源代码
(2)解析
pxPortInitialiseStack 函数的作用是初始化任务栈中的寄存器状态,使得当任务切换到该任务时,CPU 能够正确地从栈中恢复寄存器值,从而恢复任务的执行状态。最后,函数返回更新后的 pxTopOfStack 指针,指向任务栈的栈顶。
当任务切换发生时,CPU 会使用这个指针来加载任务的寄存器状态,从而恢复任务的执行。这个栈的位置是在每个任务的任务栈。
(3)图示
/
/
/
3、就绪列表
任务初始化之后,需要把任务添加到继续列表中,表示这个任务已经准备好,可以进行调度。
(1)任务就绪列表的定义
就绪列表实际上就是一个 List_t 类型的数组,数组的大小由决定 最 大 任 务 优 先 级 的 宏 configMAX_PRIORITIES 决 定 configMAX_PRIORITIES 默认定义为 5,最大支持 256 个优先级。数组的下标对应了任务的优先级,同一优先级的任务统一插入到就绪列表的同一条链表中。
具体来说,pxReadyTasksLists 中的每一个元素都是一个链表。每个链表(即每个 List_t 元素)包含所有具有相同优先级的任务。因此,pxReadyTasksLists 实际上是一个优先级队列,每个优先级有一个单独的链表来存储该优先级的所有就绪任务。
(2)就绪列表初始化
(3)将任务添加到就绪列表
这句话的作用是将任务控制块(TCB)对应的任务插入到优先级为 1 的就绪任务列表的末尾。
笔者看到这的时候,想到一个问题,xStateListItem这个节点用于将任务插入到就绪列表中。那应该一个TCB也有其他的节点用于将任务插入其他列表中把。由于本次学习的代码是从0到1慢慢垒起来的,目前并没有看到其他节点。因此我查看了源码印证了我的想法。例如TCB中包含以下节点:
我们可以看出,同一个任务可以同时在不同的状态下存在于不同的链表中,但每个节点只能在一个链表中存在。因此我们定义了多个节点用于不同的列表。
4、总结
通过上面几个函数的分析,我们得到了任务初始化的流程。
个人学习文档,有问题欢迎大家评论交流,如果感到有用的话点个赞吧。ヽ(。◕‿◕。)ノ゚