任务创建函数分析
任务创建可以使用xTaskCreate()和 xTaskCreateStatic(),我们就以函数 xTaskCreate()为例来分析一下FreeRTOS 的任务创建过程,函数 xTaskCreateStatic()类似。
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t *pxNewTCB;BaseType_t xReturn;/********************************************************************/..................../********************************************************************/StackType_t *pxStack;//动态堆栈空间pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );if( pxStack != NULL ){//申请任务控制块空间pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){//将之前申请的堆栈空间赋值到任务控制块内pxNewTCB->pxStack = pxStack;}else{vPortFree( pxStack );}}else{pxNewTCB = NULL;}if( pxNewTCB != NULL ){#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){//标记任务堆栈和任务控制块是使用动态内存分配方法得到的pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif /* configSUPPORT_STATIC_ALLOCATION *///完成对任务控制块中各个字段的初始化工作prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, \pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );//将任务控制块加入到就绪列表中prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;}
任务初始化函数分析
在任务创建函数中我们看到了,任务的初始化是通过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 ){StackType_t *pxTopOfStack;UBaseType_t x;/*如果使能了堆栈溢出检测功能或者追踪功能的话 就使用一个定值tskSTACK_FILL_BYTE 来填充任务堆栈,这个值为 0xa5U。 */#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/* Calculate the top of stack address. This depends on whether the stackgrows from high memory to low (as per the 80x86) or vice versa.portSTACK_GROWTH is used to make the result positive or negative as requiredby 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 ) ) );/* 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 isperformed. */pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );}#endif /* portSTACK_GROWTH *//* 将任务名字存储在任务块中 */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 thanconfigMAX_TASK_NAME_LEN characters just in case the memory after thestring 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 lengthwas greater or equal to configMAX_TASK_NAME_LEN. */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;//如果有互斥信号量,那就要初始化互斥信号量#if ( configUSE_MUTEXES == 1 ){pxNewTCB->uxBasePriority = uxPriority;pxNewTCB->uxMutexesHeld = 0;}#endif /* configUSE_MUTEXES *///初始化列表项 xStateListItem 和 xEventListItem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );/* 设置列表项 xStateListItem 和 xEventListItem 属于当前任务的任务控制块,也就是设置这两个列表项的字段 pvOwner 为新创建的任务的任务控制块。 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* 设置列表项xEventListItem的字段xItemValue为configMAX_PRIORITIES- uxPriority,* 比如当前任务优先级 3,最大优先级为 32,那么 xItemValue 就为 32-3=29,这就意味着 xItemValue* 值越大,优先级就越小。上一章学习列表和列表项的时候我们说过,列表的插入是按照* xItemValue 的值升序排列的。*/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/* 调用函数 pxPortInitialiseStack()初始化任务堆栈。*/#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 *///生成任务控制块,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块。if( ( void * ) pxCreatedTask != NULL ){/* Pass the handle out in an anonymous way. The handle can be used tochange the created task's priority, delete the created task, etc.*/*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}
任务堆栈初始化
通过任务初始化的分析我们知道,最终会调用堆栈初始化函数pxPortInitialiseStack()。
StackType_t *pxPortInitialiseStack( StackType_t * pxTopOfStack,TaskFunction_t pxCode,void * pvParameters ){pxTopOfStack--;// 设置xPSR 值为0x01000000,表示使用 Thumb 指令*pxTopOfStack = portINITIAL_XPSR;pxTopOfStack--;//设置PC寄存器地址为pxcode,也就是我们的任务函数*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;pxTopOfStack--;//设置LR的寄存器为 prvTaskExitError函数*pxTopOfStack = ( StackType_t ) prvTaskExitError;//跳过 4 个寄存器,R12,R3,R2,R1pxTopOfStack -= 5;//设置R0 初始化为 pvParameters。一般情况下,函数调用会将 R0~R3 作为输入参数,R0 也可用作返回结果,如果返回值为 64 位,则 R1 也会用于返回结果*pxTopOfStack = ( StackType_t ) pvParameters;pxTopOfStack--;//保存 EXC_RETURN 值,用于退出 SVC 或 PendSV 中断的时候处理器应该处于什么壮态。*pxTopOfStack = portINITIAL_EXEC_RETURN;//跳过 8 个寄存器,R11、R10、R8、R7、R6、R5、R4。pxTopOfStack -= 8;return pxTopOfStack;}
堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对 Cortex-M 内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照:xPSR、R15(PC)、R14(LR)、R12、R3R0、R11R14。
添加任务到就绪列表
任务创建完成以后就会被添加到就绪列表中,FreeRTOS 使用不同的列表表示任务的不同状态,在文件 tasks.c 中就定义了多个列表来完成不同的功能,这些列表如下:
//列表数组 pxReadyTasksLists[]就是任务就绪列表,数组大小为 configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];PRIVILEGED_DATA static List_t xDelayedTaskList1;PRIVILEGED_DATA static List_t xDelayedTaskList2;PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;PRIVILEGED_DATA static List_t xPendingReadyList;
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ){taskENTER_CRITICAL();{//变量 uxCurrentNumberOfTasks 为全局变量,用来统计任务数量。uxCurrentNumberOfTasks++;//正在运行任务块为 NULL,说明没有任务运行!if( pxCurrentTCB == NULL ){//将新任务的任务控制块赋值给 pxCurrentTCBpxCurrentTCB = pxNewTCB;//新创建的任务是第一个任务if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){//需要先初始化相应的列表,通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();}}else{if( xSchedulerRunning == pdFALSE ){//新任务的任务优先级比正在运行的任务优先级高。if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}//uxTaskNumber 加一,用作任务控制块编号。uxTaskNumber++;#if ( configUSE_TRACE_FACILITY == 1 ){/* Add a counter into the TCB for tracing only. */pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif /* configUSE_TRACE_FACILITY */traceTASK_CREATE( pxNewTCB );//将任务控制块插入到就绪列表中prvAddTaskToReadyList( pxNewTCB );portSETUP_TCB( pxNewTCB );}taskEXIT_CRITICAL();if( xSchedulerRunning != pdFALSE ){//新任务优先级比正在运行的任务优先级高if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){//如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换。taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}
