FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知
本文将分析阐述FreeRTOS的任务创建过程(以动态申请内存为例),并给出通用的步骤过程。
任务创建使用xTaskCreate()。
xTaskCreate内部步骤主要如下:
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
//动态内存申请,与内存时FreeRTOS自动分配。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
else
{
pxNewTCB = NULL;
}
if( pxNewTCB != NULL )
{
/* Store the stack location in the TCB. */
pxNewTCB->pxStack = pxStack;
}
else
{
vPortFreeStack( pxStack );
}
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif
//初始化新的任务
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
6.1. 如果使能了堆栈溢出检测功能或者追踪功能的话就使用一个定值 taskSTACK_FILL_BYTE 来填充任务堆栈,这个值为 0xa5U。
#if ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark2 == 1 ) )
#define tskSET_NEW_STACKS_TO_KNOWN_VALUE 1
#else
#define tskSET_NEW_STACKS_TO_KNOWN_VALUE 0
#endif
填充堆栈。
#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
6.2. 计算堆栈栈顶 pxTopOfStack,arm的堆栈是从高到低使用的,所以此时portSTACK_GROWTH < 0
#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 ) ) );
6.3. 保存任务的任务名,任务名数组添加字符串结束符’\0’。
if( pcName != NULL )
{
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}
6.4. 判断任务优先级是否合法,如果设置的任务优先级大于 configMAX_PRIORITIES,则
将优先级修改为 configMAX_PRIORITIES-1。初始化任务控制块的优先级字段 uxPriority。
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority;
6.5. 使能了互斥信号量功能,需要初始化相应的字段。
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
6.6. 、初始化列表项 xStateListItem和 xEventListItem,任务控制块结构体中有两个列表
项,这里对这两个列表项做初始化。
//初始化列表项
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
6.7. 设置列表项 xStateListItem和 xEventListItem属于当前任务的任务控制块,也就
是设置这两个列表项的字段 pvOwner 为新创建的任务的任务控制块。
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/******6.8所描述的内同********/
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
6.8. 设置列表项xEventListItem的字段xItemValue为configMAX_PRIORITIES- uxPriority,比如当前任务优先级 3,最大优先级为 32,那么 xItemValue 就为 32-3=29,这就意味着 xItemValue
值越大,优先级就越小。
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
列表的插入是按照xItemValue 的值升序排列的。
6.9. 调用函数 pxPortInitialiseStack()初始化任务堆栈。
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
在任务初始化函数中会对任务堆栈初始化,这个过程通过调用函数 pxPortInitialiseStack()来
完成,函数 pxPortInitialiseStack()就是堆栈初始化函数,函数源码如下:
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. */
pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先
进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保
存的顺序按照:xPSR、R15(PC)、R14(LR)、R12、R3 ~ R0、R11 ~ R14。
(1)、寄存器 xPSR 值为 portINITIAL_XPSR,其值为 0x01000000。xPSR 是 Cortex-M 的一个内核寄存器,叫做程序状态寄存器,0x01000000表示这个寄存器的bit24为1,表示处于Thumb状态,即使用的 Thumb 指令。
(2)、寄存器PC 初始化为任务函数 pxCode。
(3)、寄存器 LR初始化为函数 prvTaskExitError。
(4)、跳过 4 个寄存器,R12,R3,R2,R1,这四个寄存器不初始化。
(5)、寄存器 R0 初始化为 pvParameters,一般情况下,函数调用会将R0~R3 作为输入参数,R0 也可用作返回结果,如果返回值为 64 位,则 R1 也会用于返回结果(在《权威指南》“第 8章 深入了解异常处理”的 8.1.2 小节中有讲解,P188),这里的 pvParameters 是作为任务函数的参数,保存在寄存器 R0 中。
(6)、跳过 8 个寄存器,R11、R10、R8、R7、R6、R5、R4。
6.10. 生成任务句柄,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务
控制块。
if( pxCreatedTask != NULL )
{
/* Pass the handle out in an anonymous way. The handle can be used to
* change the created task's priority, delete the created task, etc.*/
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}