FreeRTOS学习(6)-任务之静态创建函数解析2(重点)

前文提要
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、总结

通过上面几个函数的分析,我们得到了任务初始化的流程。
在这里插入图片描述

个人学习文档,有问题欢迎大家评论交流,如果感到有用的话点个赞吧。ヽ(。◕‿◕。)ノ゚

  • 39
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值