转载自https://blog.csdn.net/zhoutaopower/article/details/107034995
在《FreeRTOS --(7)任务管理之入门篇》文章基本分析了任务相关的轮廓后,我们知道使用什么接口来创建一个任务、怎么去开启调度器、以及根据宏配置,选择调度器的行为;接下来我们深入到 FreeRTOS 任务创建的源码来看看一个任务是怎么被创建的(某大神说过,Read The F**king Source Code ,能用代码解决的,尽量不 BB);
1、描述任务的结构
在 FreeRTOS 中,使用 TCB_t 来描述一个任务:
/* * Task control block. A task control block (TCB) is allocated for each task, * and stores task state information, including a pointer to the task's context * (the task's run time environment, including register values) */ typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /*当前堆栈的栈顶,必须位于结构体的第一项*/ #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; /*MPU设置,必须位于结构体的第二项*/ #endif ListItem_t xStateListItem; /*任务的状态列表项,以引用的方式表示任务的状态*/ ListItem_t xEventListItem; /*事件列表项,用于将任务以引用的方式挂接到事件列表*/ UBaseType_t uxPriority; /*保存任务优先级,0表示最低优先级*/ StackType_t *pxStack; /*指向堆栈的起始位置*/ char pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/ #if ( portSTACK_GROWTH > 0 ) StackType_t *pxEndOfStack; /*指向堆栈的尾部*/ #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; /*保存临界区嵌套深度*/ #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTCBNumber; /*保存一个数值,每个任务都有唯一的值*/ UBaseType_t uxTaskNumber; /*存储一个特定数值*/ #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; /*保存任务的基础优先级*/ UBaseType_t uxMutexesHeld; #endif #if ( configUSE_APPLICATION_TASK_TAG == 1 ) TaskHookFunction_t pxTaskTag; #endif #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) void *pvThreadLocalStoragePointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; #endif #if( configGENERATE_RUN_TIME_STATS == 1 ) uint32_t ulRunTimeCounter; /*记录任务在运行状态下执行的总时间*/ #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) /* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/ struct _reent xNewLib_reent; #endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) volatile uint32_t ulNotifiedValue; /*与任务通知相关*/ volatile uint8_t ucNotifyState; #endif #if( configSUPPORT_STATIC_ALLOCATION == 1 ) uint8_t ucStaticAllocationFlags; /* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/ #endif #if( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif } tskTCB; typedef tskTCB TCB_t;
1、pxTopOfStack 必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack 总是指向最后一个入栈的内容。此指针会移动;
2、如果使用 MPU,xMPUSettings 必须位于结构体的第二项,用于 MPU 设置。
3、状态链表 xStateListItem ,用于将任务挂接到不同的状态链表中,比如任务处于 Ready 状态,那么就要将其挂到 Ready 链表;
4、事件链表 xEventListItem,类似;
5、uxPriority 用于保存任务的优先级,0 为最低优先级;
6、pxStack 指向堆栈的起始位置,申请堆栈内存函数返回的指针就被赋给该变量。pxTopOfStack 指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack 指向的位置是会变化的;pxStack 指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要 pxStack 变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量 pxEndOfStack 来辅助诊断是否堆栈溢出;
7、pcTaskName 用于保存任务的描述或名字,名字的长度由宏 configMAX_TASK_NAME_LEN(位于 FreeRTOSConfig.h 中)指定,包含字符串结束标志。
8、如果堆栈向上生长(portSTACK_GROWTH>0),指针 pxEndOfStack 指向堆栈尾部,用于检验堆栈是否溢出。
9、uxCriticalNesting 用于保存临界区嵌套深度,初始值为 0。
10、仅当宏 configUSE_TRACE_FACILITY(位于 FreeRTOSConfig.h 中)为 1 时有效。变量 uxTCBNumber 存储一个数值,在创建任务时由内核自动分配数值(通常每创建一个任务,值增加 1),每个任务的 uxTCBNumber 值都不同,主要用于调试。变量 uxTaskNumber 用于存储一个特定值,与变量 uxTCBNumber 不同,uxTaskNumber 的数值不是由内核分配的,而是通过 API 函数 vTaskSetTaskNumber() 来设置的,数值由函数参数指定。
11、如果使用互斥量(configUSE_MUTEXES==1),任务优先级被临时提高时,变量 uxBasePriority 用来保存任务原来的优先级。
12、变量 ucStaticAllocationFlags 也需要说明一下,我们前面说过任务创建 API 函数 xTaskCreate() 只能使用动态内存分配的方式创建任务堆栈和任务 TCB,如果要使用静态变量实现任务堆栈和任务 TCB 就需要使用函数 xTaskGenericCreate() 来实现。如果任务堆栈或任务 TCB 由静态数组和静态变量实现,则将该变量设置为 pdTRUE(任务堆栈空间由静态数组变量实现时为 0x01,任务 TCB 由静态变量实现时为 0x02,任务堆栈和任务 TCB 都由静态变量实现时为 0x03),如果堆栈是动态分配的,则将该变量设置为 pdFALSE。
2、任务创建
2.1、xTaskCreate
创建一个任务,使用 xTaskCreate 接口,传入的参数在《FreeRTOS --(7)任务管理之入门篇》中有描述,这里不在多说,我们直接看看他的实现,在 task.c 中:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) { TCB_t *pxNewTCB; BaseType_t xReturn; /* If the stack grows down then allocate the stack then the TCB so the stack does not grow into the TCB. Likewise if the stack grows up then allocate the TCB then the stack. */ #if( portSTACK_GROWTH > 0 ) { /* Allocate space for the TCB. Where the memory comes from depends on the implementation of the port malloc function and whether or not static allocation is being used. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); if( pxNewTCB != NULL ) { /* Allocate space for the stack used by the task being created. The base of the stack memory stored in the TCB so the task can be deleted later if required. */ pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ if( pxNewTCB->pxStack == NULL ) { /* Could not allocate the stack. Delete the allocated TCB. */ vPortFree( pxNewTCB ); pxNewTCB = NULL; } } } #else /* portSTACK_GROWTH */ { StackType_t *pxStack; /* Allocate space for the stack used by the task being created. */ pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */ if( pxStack != NULL ) { /* Allocate space for the TCB. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */ if( pxNewTCB != NULL ) { /* Store the stack location in the TCB. */ pxNewTCB->pxStack = pxStack; } else { /* The stack cannot be used as the TCB was not created. Free it again. */ vPortFree( pxStack ); } } else { pxNewTCB = NULL; } } #endif /* portSTACK_GROWTH */ if( pxNewTCB != NULL ) { #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */ { /* Tasks can be created statically or dynamically, so note this task was created dynamically in case it is later deleted. */ pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; } #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */ prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); prvAddNewTaskToReadyList( pxNewTCB ); xReturn = pdPASS; } else { xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; } return xReturn; }
首先是根据 portSTACK_GROWTH 宏来判断当前处理器体系结构堆栈的生长方向,portSTACK_GROWTH > 0 代表堆栈向上生长,portSTACK_GROWTH < 0 代表堆栈向下生长;堆栈的生长方向是和处理器的体系结构息息相关,这里拿 Cortex-M 系列的处理器做例子,它的堆栈是向下生长的,所以我们在往 Cortex-M 系列处理器上移植 FreeRTOS 的时候,一定记得这里将 portSTACK_GROWTH 定义得小于 0;
反过来,为啥要在创建任务的时候,判断堆栈的生长方向呢?根本原因是因为,在创建任务的时候,需要为任务的 TCB 和任务的 Stack 分配内存,分配内存需要通过 pvPortMalloc 接口来实现,而 pvPortMalloc 分配内存是从小地址开始分配的,所以:
如果堆栈是向上生长的,先调用 pvPortMalloc 分配任务的 TCB 结构,再去分配任务的 Stack,因为 TCB 大小是固定,但是堆栈要向上生长,这样就避免了堆栈踩到 TCB;
如果堆栈是向下生长的,先调用 pvPortMalloc 分配任务的 Stack,再去分配任务的 TCB 结构,这样 Stack 生长的时候,也可以避免踩到 TCB 结构;
分配 Stack 完成后,将 TCB 的 pxNewTCB->pxStack = pxStack; 此刻 pxStack 便初始化完毕;
这里还要唠叨一句,分配任务栈的空间是:
( ( size_t ) usStackDepth ) * sizeof( StackType_t )
这里的 StackType_t 和 CPU 体系架构相关,32 bit 的 CPU 下,StackType_t 被定义为 uint32_t,也就是 4 字节;
如果为任务分配 TCB 结构和任务 Stack 都成功了,那么会调用到:prvInitialiseNewTask 和 prvAddNewTaskToReadyList;
prvInitialiseNewTask 主要是初始化任务的 TCB 相关的内容;
prvAddNewTaskToReadyList 将初始化好的任务添加到 Ready 链表,即允许投入执行;
如果创建任务成功,返回 pdPASS 否则返回 pdFLASE;
2.2、prvInitialiseNewTask
下面来看看 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; #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( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 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 /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */ /* 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 !e9033 !e9078 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. Checked by assert(). */ /* Check the alignment of the calculated top of stack is correct. */ configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); #if( configRECORD_STACK_HIGH_ADDRESS == 1 ) { /* Also record the stack's high address, which may assist debugging. */ pxNewTCB->pxEndOfStack = pxTopOfStack; } #endif /* configRECORD_STACK_HIGH_ADDRESS */ } #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. */ if( pcName != NULL ) { 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 ] == ( char ) 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 ] = '