目录
1 任务的句柄(结构体)
2 创建任务主要工作
2.1 创建任务初始化源码分析
2.2 任务添加到就绪列表源码分析
2.3任务堆栈的初始化源码分析
问:R0为什么要入栈保存?因为作为函数的第一个传入参数,必须也要保存。
问:为什么要入栈异常返回值?
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 )