本篇文章及记录我在学习FreeRTOS中关于任务创建的详细过程的了解。希望我的分享能给你带来不一样的收获。
目录
一、任务创建的相关函数
二、任务初始化函数分析
三、任务堆栈初始化函数
四、添加任务到就绪列表
一、任务创建的相关函数
前面学了任务创建可以使用动态方法或静态方法(不讨论使用MPU的情况),它们分别使用函数xTaskCreate()和 xTaskCreateStatic()。我们就以函数xTaskCreate()为例来分析一下FreeRTOS的任务创建过程,函数xTaskCreateStatic()类似,这里不做分析。函数xTaskCreate()代码如下。
(1)、使用函数pvPortMalloc()给任务的任务堆栈申请内存,申请内存的时候会做字节对齐处理。
(2)、如果堆栈的内存申请成功的话就接着给任务控制块申请内存,同样使用函数pvPortMalloc()。
(3)、任务控制块内存申请成功的话就初始化内存控制块中的任务堆栈字段pxStack,使用(1)中申请到的任务堆栈。(4)、如果任务控制块内存申请失败的话就释放前面已经申请成功的任务堆栈的内存。
(5)、标记任务堆栈和任务控制块是使用动态内存分配方法得到的。
(6)、使用函数 prvInitialiseNewTask()初始化任务,这个函数完成对任务控制块中各个字段的初始化工作!
(7)、使用函数prvAddNewTaskToReadyList()将新创建的任务加入到就绪列表中。
二、任务初始化函数分析
函数prvInitialiseNewTask()用于完成对任务的初始化,函数源码如下:
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( portUSING_MPU_WRAPPERS == 1 )
/* Should the task be created in privileged mode? */
BaseType_t xRunPrivileged;
if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */
/* Avoid dependency on memset() if it is not required. */
#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
{
/* Fill the stack with a known value to assist debugging. */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) ) */
/* Calculate the top of stack address. This depends on whether the stack
grows from high memory to low (as per the 80x86) or vice versa.
portSTACK_GROWTH is used to make the result positive or negative as required
by the port. */
#if( portSTACK_GROWTH < 0 )
{
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. */
/* Check the alignment of the calculated top of stack is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
}
#else /* portSTACK_GROWTH */
{
pxTopOfStack = pxNewTCB->pxStack;
/* Check the alignment of the stack buffer is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
/* The other extreme of the stack space is required if stack checking is
performed. */
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
#endif /* portSTACK_GROWTH */
/* Store the task name in the TCB. */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
configMAX_TASK_NAME_LEN characters just in case the memory after the
string is not accessible (extremely unlikely). */
if( pcName[ x ] == 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* Ensure the name string is terminated in the case that the string length
was greater or equal to configMAX_TASK_NAME_LEN. */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* This is used as an array index so must ensure it's not too large. First
remove the privilege bit if one is present. */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get
back to the containing TCB from a generic item in a list. */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* Event lists are always in priority order. */
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 ( portCRITICAL_NESTING_IN_TCB == 1 )
{
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
pxNewTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if ( portUSING_MPU_WRAPPERS == 1 )
{
vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
}
#else
{
/* Avoid compiler warning about unreferenced parameter. */
( void ) xRegions;
}
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
}
}
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Initialise this task's Newlib reent structure. */
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
{
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
/* Initialize the TCB stack to look as if the task was already running,
but had been interrupted by the scheduler. The return address is set
to the start of the task function. Once the stack has been initialised
the top of stack variable is updated. */
#if( portUSING_MPU_WRAPPERS == 1 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#else /* portUSING_MPU_WRAPPERS */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portUSING_MPU_WRAPPERS */
if( ( void * ) 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;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1)、如果使能了堆栈溢出检测功能或者追踪功能的话就使用一个定值tskSTACK_FILL_BYTE来填充任务堆栈,这个值为0xa5U。
(2)、计算堆栈栈顶pxTopOfStack,后面初始化堆栈的时候需要用到。(1)、保存任务的任务名。
(2)、任务名数组添加字符串结束符\O’。(1)、判断任务优先级是否合法,如果设置的任务优先级大于configMAX_PRIORITIES,则将优先级修改为configMAX_PRIORITIES-1。
(2)、初始化任务控制块的优先级字段uxPriority。
(3)、使能了互斥信号量功能,需要初始化相应的字段。(1)和(2)、初始化列表项xStateListItem和 xEventListIltem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。
(3)和(4)、设置列表项xStateListItem和xEventListItem属于当前任务的任务控制块,也就是设置这两个列表项的字段pvOwner为新创建的任务的任务控制块。
(5)、设置列表项xEventListltem的字段xItemValue为configMAX_PRIORITIES-uxPriority,比如当前任务优先级3,最大优先级为32,那么xItemValue就为32-3=29,这就意味着xltemValue值越大,优先级就越小。上一章学习列表和列表项的时候我们说过,列表的插入是按照xltemValue 的值升序排列的。(1)、初始化线程本地存储指针,如果使能了这个功能的话。
(2)、调用函数pxPortInitialiseStack()初始化任务堆栈。
(3)、生成任务句柄,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块。
三、任务堆栈初始化函数
在任务初始化函数中会对任务堆栈初始化,这个过程通过调用函数pxPortInitialiseStack()来完成,函数pxPortInitialiseStack()就是堆栈初始化函数,函数源码如下:
堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照: xPSR、R15(PC)、R14(LR)、R12、R3~R0、R11~R14。
(1)、寄存器xPSR值为portINITIAL_XPSR,其值为0x01000000。xPSR是Cortex-M4的一个内核寄存器,叫做程序状态寄存器,0x01000000表示这个寄存器的bit24为1,表示处于Thumb状态,即使用的Thumb 指令。
(2)、寄存器PC初始化为任务函数pxCode。(3)、寄存器LR初始化为函数prvTaskExitError。
(4)、跳过4个寄存器,R12,R3,R2,R1,这四个寄存器不初始化。
(5)、寄存器R0初始化为pvParameters,一般情况下,函数调用会将R0~R3作为输入参数,R0也可用作返回结果,如果返回值为64位,则Rl也会用于返回结果,这里的pvParameters是作为任务函数的参数,保存在寄存器R0中。(6)、保存EXC_RETURN值,用于退出SVC或PendSV中断的时候处理器应该处于什么状态。处理器进入异常或中断服务程序(ISR)时,链接寄存器R14(LR)的数值会被更新为EXC_RETURN 数值,之后该数值会在异常处理结束时触发异常返回。这里人为的设置为0XFFFFFFD,表示退出异常以后CPU进入线程模式并且使用进程栈!
(7)、跳过8个寄存器,R11、R10、R8、R7、R6、R5、R4。
经过上面的初始化之后,此时的堆栈结果如图所示:注:STM32为例,堆栈为向下增长模式
四、添加任务到就绪列表
任务创建完成以后就会被添加到就绪列表中,FreeRTOS使用不同的列表表示任务的不同状态,在文件tasks.c中就定义了多个列表来完成不同的功能,这些列表如下:
列表数组pxReadyTasksLists[]就是任务就绪列表,数组大小为configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数prvAddNewTaskToReadyList()来完成,函数如下:
(1)、变量uxCurrentNumberOfTasks为全局变量,用来统计任务数量。
(2)、变量 uxCurrentNumberOfTasks为1说明正在创建的任务是第一个任务!那么就需要先初始化相应的列表,通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。这个函数很简单,本质就是调用上一篇文章讲的列表初始化函数vListInitialise()来初始化几个列表,大家可以自行分析一下。
(3)、新创建的任务优先级比正在运行的任务优先级高,所以需要修改pxCurrentTCB为新建任务的任务控制块。
(4)、调用函数prvAddTaskToReadyList()将任务添加到就绪列表中,这个其实是个宏,如下:其中宏 portRECORD_READY_PRIORITY()用来记录处于就绪态的任务,具体是通过操作全局变量uxTopReadyPriority 来实现的。这个变量用来查找处于就绪态的优先级最高任务,具体操作过程后面讲解任务切换的时候会讲。接下来使用函数 vListInsertEnd()将任务添加到就绪列表末尾。
(5)、如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换。