FreeRTOS源码分析-1创建任务和任务删除

目录

1 任务的句柄(结构体)

2 创建任务

2.1 创建任务初始化源码分析

2.2 任务添加到就绪列表源码分析

2.3任务堆栈的初始化源码分析

问:R0为什么要入栈保存?因为作为函数的第一个传入参数,必须也要保存。

问:为什么要入栈异常返回值?

3 任务删除


1 任务的句柄(结构体)

/********结构体指针*******/
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;

2 创建任务

创建业务流程:

  • 分配任务控制块内存空间,分配任务堆栈空间
  • 初始化任务控制块,初始化任务堆栈
  • 添加任务到就绪列表中

补充一个知识:M4权威指南4.4.3栈存储

2.1 创建任务初始化源码分析

具体工作,把栈空间的高地址分配给栈顶,任务分配的优先级,任务控制块
链接到任务状态表中,任务控制块连接到事件表中,任务堆栈初始化,之后返回任务栈顶。

/*********创建任务********/
//任务创建函数,其中osThreadCreate是CMSIS封装接口
Key_TaskHandle = osThreadCreate(osThread(Key_Task), NULL);

/**
 * @description: osThreadCreate内部是xTaskCreate,创建一个任务线程,任务栈分配内存、控制块分配内存,把任务添加到就绪列表,初始化
 * @param {TaskFunction_t}pxTaskCode 函数指针(函数名)
 * @param {const char *}pcName 任务名称(字符串)
 * @param {const uint16_t} usStackDepth  任务堆栈大小,单位为字
 * @param {void *} const pvParameters 任务传入参数
 * @param {UBaseType_t} uxPriority 任务优先级
 * @param {TaskHandle_t *} const pxCreatedTask 任务句柄
 * @return {const char *}pxTaskCode
 */
BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
						const char * const pcName,
						const uint16_t usStackDepth,
						void * const pvParameters,
						UBaseType_t uxPriority,
						TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
    TCB_t *pxNewTCB;
    BaseType_t xReturn;

	//硬件平台栈增长方式:M4是满减栈,portSTACK_GROWTH = -1
#if( portSTACK_GROWTH > 0 )
	{
		//略
	}
#else /* portSTACK_GROWTH */
	{
	    StackType_t *pxStack;

		/* 任务栈内存分配*/
		pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );

		if( pxStack != NULL )
		{
			/* 任务控制块内存分配 */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

			if( pxNewTCB != NULL )  
			{
				/* 赋值栈地址 */
				pxNewTCB->pxStack = pxStack;
			}
			else
			{
				/* 释放栈空间 */
				vPortFree( pxStack );
			}
		}
		else
		{
			pxNewTCB = NULL;
		}
	}
#endif /* portSTACK_GROWTH */

	if( pxNewTCB != NULL )
	{
		/*创建任务初始化,把栈空间的高地址分配给栈顶,任务分配的优先级,任务控制块
        链接到任务状态表中,任务控制块连接到事件表中,//任务堆栈初始化,之后返回任
        务栈顶*/
		prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
        //把任务添加到就绪列表中
		prvAddNewTaskToReadyList( pxNewTCB );
		xReturn = pdPASS;
	}
	else
	{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}

	return xReturn;
}

2.2 任务添加到就绪列表源码分析




static void prvInitialiseNewTask( 	TaskFunction_t pxTaskCode,
									const char * const pcName,
									const uint32_t ulStackDepth,
									void * const pvParameters,
									UBaseType_t uxPriority,
									TaskHandle_t * const pxCreatedTask,
									TCB_t *pxNewTCB,
									const MemoryRegion_t * const xRegions ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
    StackType_t *pxTopOfStack;
    UBaseType_t x;

	/*计算栈顶的指针*/
	#if( portSTACK_GROWTH < 0 ) //满减栈
	{
        //把栈空间的高地址分配给栈顶
		pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
        //栈对齐-----栈要8字节对齐
		pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 

		/*检查是否有错误*/
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
	}
	#else /* portSTACK_GROWTH */
	{
		//不是满减栈不需要关注
	}
	#endif /* portSTACK_GROWTH */

	/*存储任务名称,即数组拷贝*/
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];

		if( pcName[ x ] == 0x00 )
		{
			break;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	/*补齐字符串*/
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';

	/*判断任务分配的优先级是否大于最大值*/
	if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
	{
		uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

    //赋值任务优先级
	pxNewTCB->uxPriority = uxPriority;

    //状态表、事件表初始化
	vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

	/*任务控制块链接到状态表中*/
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

	/*任务控制块连接到事件表中 */
	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. */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
	
	#if( portUSING_MPU_WRAPPERS == 1 )
	{
		//不执行
	}
	#else /* portUSING_MPU_WRAPPERS */
	{
        //任务堆栈初始化,之后返回任务栈顶
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
	}
	#endif /* portUSING_MPU_WRAPPERS */

    
	if( ( void * ) pxCreatedTask != NULL )
	{
		/*赋值任务的句柄,即任务控制块*/
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

接下来就是分析上面任务堆栈的初始化

2.3任务堆栈的初始化源码分析

补充:资料阅读M4权威指南,第八章节,分析异常处理

为什么分析异常处理?任务调度其实就是通过CPU内核异常处理实现的

 

 

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    //入栈程序状态寄存器
	pxTopOfStack--; 
	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */

    //入栈PC指针
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */

    //入栈LR链接寄存器
    pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

    //不需要初始化
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */

    //R0作为传参入栈
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */

    //异常返回值入栈,返回值是确定程序使用的栈地址是哪一个?MSP PSP
    pxTopOfStack--;
    pxTopOfStack = portINITIAL_EXEC_RETURN;
    
    //不初始化
	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

    //最终返回栈顶
	return pxTopOfStack;
}

补充知识:栈帧

程序的写法顺序按照下图所示 

 

问:R0为什么要入栈保存?因为作为函数的第一个传入参数,必须也要保存。

问:为什么要入栈异常返回值?

返回值是确定程序使用的栈地址是哪一个?MSP PSP。

prvAddNewTaskToReadyList( pxNewTCB )

把任务添加到就绪列表这部分暂未分析,后续会结合其他内容分析

3 任务删除

任务删除业务流程:

  • 从就绪列表中删除
  • 从事件列表中删除
  • 释放任务控制块、释放任务堆栈内存
  • 开始任务调度
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
    TCB_t *pxTCB;

    //进入临界段
	taskENTER_CRITICAL();
	{
		/*获取任务控制块--参数传入的时任务句柄
        判断是任务本身还是,其他任务,如果是当前的任务,传入的参数未NULL    
        */
		pxTCB = prvGetTCBFromHandle( xTaskToDelete );

		/*从就绪列表中移除*/
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
		{
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/*从事件列表中移除*/
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
		{
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/*  */
		uxTaskNumber++;

        //如果是删除当前任务
		if( pxTCB == pxCurrentTCB )
		{
			/* 不能在这里删除,在空闲任务中删除,把任务添加到等待删除的任务列表中 */
			vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

			/* 给空闲任务一个标记 */
			++uxDeletedTasksWaitingCleanUp;

			/* 钩子函数----用户自己实现 */
			portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
		}
		else //不是当前任务
		{
            //直接删除任务控制块
			--uxCurrentNumberOfTasks;
			prvDeleteTCB( pxTCB );

			/* 复位任务锁定时间 -----时间片 操作系统会记录一个最新的时间,根据
                最新的时间进行调度。所以删除任务后,要更新锁定时间
             */
			prvResetNextTaskUnblockTime();
		}

		traceTASK_DELETE( pxTCB );
	}
    //退出临界段
	taskEXIT_CRITICAL();

	/* 判断调度器是否开启 */
	if( xSchedulerRunning != pdFALSE )
	{
        //如果是删除任务本身,马上进行任务调度(释放CPU使用权)
		if( pxTCB == pxCurrentTCB )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

4IOT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值