目录
1、描述任务的结构
2、任务创建
2.1、xTaskCreate
2.2、prvInitialiseNewTask
2.3、pxPortInitialiseStack
2.4、prvAddNewTaskToReadyList
在《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设置,必须位于结构体的第二项*/
-
-
-
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;
/*指向堆栈的尾部*/
-
-
-
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
-
UBaseType_t uxCriticalNesting;
/*保存临界区嵌套深度*/
-
-
-
#if ( configUSE_TRACE_FACILITY == 1 )
-
UBaseType_t uxTCBNumber;
/*保存一个数值,每个任务都有唯一的值*/
-
UBaseType_t uxTaskNumber;
/*存储一个特定数值*/
-
-
-
#if ( configUSE_MUTEXES == 1 )
-
UBaseType_t uxBasePriority;
/*保存任务的基础优先级*/
-
UBaseType_t uxMutexesHeld;
-
-
-
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
-
TaskHookFunction_t pxTaskTag;
-
-
-
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
-
void *pvThreadLocalStoragePointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
-
-
-
#if( configGENERATE_RUN_TIME_STATS == 1 )
-
uint32_t ulRunTimeCounter;
/*记录任务在运行状态下执行的总时间*/
-
-
-
#if ( configUSE_NEWLIB_REENTRANT == 1 )
-
/* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/
-
struct
_reent xNewLib_reent;
-
-
-
#if( configUSE_TASK_NOTIFICATIONS == 1 )
-
volatile
uint32_t ulNotifiedValue;
/*与任务通知相关*/
-
volatile
uint8_t ucNotifyState;
-
-
-
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
-
uint8_t ucStaticAllocationFlags;
/* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/
-
-
-
#if( INCLUDE_xTaskAbortDelay == 1 )
-
-
-
-
-
-
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,
-
-
TaskHandle_t * const pxCreatedTask )
-
-
-
-
-
/* 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 ) );
-
-
-
-
/* 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. */
-
-
-
-
-
-
#else /* portSTACK_GROWTH */
-
-
-
-
/* 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. */
-
-
-
-
/* 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. */
-
-
-
-
/* Store the stack location in the TCB. */
-
pxNewTCB->pxStack = pxStack;
-
-
-
-
/* The stack cannot be used as the TCB was not created. Free
-
-
-
-
-
-
-
-
-
-
#endif /* portSTACK_GROWTH */
-
-
-
-
#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 = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
-
-
-
-
首先是根据 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 结构;
![](https://img-blog.csdnimg.cn/20200630161003420.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob3V0YW9wb3dlcg==,size_16,color_FFFFFF,t_70)
分配 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,
-
-
TaskHandle_t * const pxCreatedTask,
-
-
const MemoryRegion_t * const xRegions )
-
-
StackType_t *pxTopOfStack;
-
-
-
#if( portUSING_MPU_WRAPPERS == 1 )
-
/* Should the task be created in privileged mode? */
-
BaseType_t xRunPrivileged;
-
if( ( uxPriority & portPRIVILEGE_BIT ) !=
0U )
-
-
-
-
-
-
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
-
-
#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
-
-
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
-
-
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 ] == (
char )
0x00 )
-
-
-
-
-
-
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';
-
-
-
-
/* The task has not been given a name, so just ensure there is a NULL
-
terminator when it is read out. */
-
pxNewTCB->pcTaskName[
0 ] =
0x00;
-
-
-
/* 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;
-
-
-
-
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 );
-
-
-
-
/* Avoid compiler warning about unreferenced parameter. */
-
-
-
-
-
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
-
-
memset( (
void * ) &( pxNewTCB->pvThreadLocalStoragePointers[
0 ] ),
0x00,
sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
-
-
-
-
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
-
-
memset( (
void * ) &( pxNewTCB->ulNotifiedValue[
0 ] ),
0x00,
sizeof( pxNewTCB->ulNotifiedValue ) );
-
memset( (
void * ) &( pxNewTCB->ucNotifyState[
0 ] ),
0x00,
sizeof( pxNewTCB->ucNotifyState ) );
-
-
-
-
#if ( configUSE_NEWLIB_REENTRANT == 1 )
-
-
/* Initialise this task's Newlib reent structure.
-
See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
-
for additional information. */
-
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
-
-
-
-
#if( INCLUDE_xTaskAbortDelay == 1 )
-
-
pxNewTCB->ucDelayAborted = pdFALSE;
-
-
-
-
/* 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 )
-
-
/* If the port has capability to detect stack overflow,
-
pass the stack end address to the stack initialization
-
-
#if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
-
-
#if( portSTACK_GROWTH < 0 )
-
-
pxNewTCB->pxTopOfStack =
pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
-
-
#else /* portSTACK_GROWTH */
-
-
pxNewTCB->pxTopOfStack =
pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
-
-
#endif /* portSTACK_GROWTH */
-
-
#else /* portHAS_STACK_OVERFLOW_CHECKING */
-
-
pxNewTCB->pxTopOfStack =
pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
-
-
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
-
-
#else /* portUSING_MPU_WRAPPERS */
-
-
/* If the port has capability to detect stack overflow,
-
pass the stack end address to the stack initialization
-
-
#if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
-
-
#if( portSTACK_GROWTH < 0 )
-
-
pxNewTCB->pxTopOfStack =
pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
-
-
#else /* portSTACK_GROWTH */
-
-
pxNewTCB->pxTopOfStack =
pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
-
-
#endif /* portSTACK_GROWTH */
-
-
#else /* portHAS_STACK_OVERFLOW_CHECKING */
-
-
pxNewTCB->pxTopOfStack =
pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
-
-
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
-
-
#endif /* portUSING_MPU_WRAPPERS */
-
-
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;
-
-
-
-
mtCOVERAGE_TEST_MARKER();
-
-
内容不少,逐行分析;
配置 MPU 部分暂时不关注;
如果使用了 tskSET_NEW_STACKS_TO_KNOWN_VALUE 宏,代表需要将被初始化后任务的 Stack 使用一个固定的值(0xA5)进行填充;
然后判断堆栈的生长方向,为何这里又在判断堆栈的生长方向呢?之前是对初始化完成后的堆栈头指针 pxNewTCB->pxStack 进行了赋值,但是此刻我们需要对当任务跑起来后,实际上用到的堆栈指针(会移动的那个)进行赋初值;
如果堆栈向下生长的话,我们需要将栈顶指针 pxTopOfStack 放置到我们分配的堆栈的末尾,这样才能向下增长;
如果堆栈向上生长的话,我们直接将 pxNewTCB->pxStack 赋值给 pxTopOfStack 就好:
![](https://img-blog.csdnimg.cn/2020063016134755.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob3V0YW9wb3dlcg==,size_16,color_FFFFFF,t_70)
以 Cortex-M3 为例,堆栈是向下生长的 ,所以会走进 portSTACK_GROWTH < 0 的这个分支,将 pxTopOfStack 指向了被分配到堆栈深度的最后,并进行堆栈的 8 字节对齐操作(处理器架构要求);
注意,此刻的 pxTopOfStack 还只是本地的局部变量,并没有直接赋值给 TCB 的 pxTopOfStack;
接着给 TCB->pcTaskName 字段(任务名字)赋值,此字段是字符串,长度受限于 configMAX_TASK_NAME_LEN;
接着给 TCB->uxPriority 任务优先级字段赋值;
如果使用了 MUTEXES 的话,将 uxPriority 赋值给 BasePriority,并将当前 mutex hold 字段设置为 0;
初始化 TCB 的状态 Item,设置 Item 的 Owner 为当前初始化的 TCB;
初始化 TCB 的 Event Item,设置 Item 的 Owner 为当前初始化的 TCB,配置 Event Item 的值为最大的优先级减去当前优先级,这里的 Item 里面的 Value 不直接保存优先级,而是保存优先级的补数,意味着 xItemValue 的值越大,对应的任务优先级越小;
初始化临界区的嵌套次数为 0;
任务通知 Value 初始化为 0;通知状态初始化等;
这里要注意一个地方,portHAS_STACK_OVERFLOW_CHECKING 这个宏在 CM3 中未定义,我想应该是处理器对应堆栈溢出的检测;
调用 pxPortInitialiseStack 传入之前设置的栈顶指针,Task 执行函数,以及要传递给 Task 的参数指针;将结果赋值给了 TCB->pxTopOfStack;
最后一切成功,将 TCB 的地址付给了创建 Task 成功后的句柄;
这里我们要看一下 pxPortInitialiseStack 函数:
2.3、pxPortInitialiseStack
带 Port,说明和处理器的体系架构相关,要看懂这个函数在干嘛,需要一点点体系架构方面的知识,这里依然以 Cortex-M3 为例;
在 Cortex-M3 处理器中,当发生异常/中断后,处理器会将关键的寄存器入栈,保护现场,然后跳转到 ISR 中执行,这个是纯硬件行为;当执行完 ISR 后,处理器会顺序出栈;
入栈的时候,再空间上顺序存储内容为:
xPSR、PC、LR、R12、R3、R2、R1、R0;
当之行为 ISR,处理器硬件上,就会到之前存储这些玩意的地方(SP)去弹栈,恢复现场;
而 OS 调度切换上下文就是在 ISR 中做;
所以呢,在初始化的时候(CM3 的线程模式),咱们就手动的模拟处理器入栈的行为,将这些玩意先给他放进去准备好,等 OS 调度的时候,只要告诉它堆栈指针,嘿,处理器就会去弹栈;
更多的关于 CM3 处理器的内容参考《Cortex-M3 处理器窥探》
![](https://img-blog.csdnimg.cn/20200628101751750.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob3V0YW9wb3dlcg==,size_16,color_FFFFFF,t_70)
好了,解释完了后,可以看代码了:
-
/* Constants required to set up the initial stack. */
-
#define portINITIAL_XPSR ( 0x01000000 )
-
/* For strict compliance with the Cortex-M spec the task start address should
-
have bit-0 clear, as it is loaded into the PC on exit from an ISR. */
-
#define portSTART_ADDRESS_MASK ( ( StackType_t ) 0xfffffffeUL )
-
-
static void prvTaskExitError( void )
-
-
/* A function that implements a task must not exit or attempt to return to
-
its caller as there is nothing to return to. If a task wants to exit it
-
should instead call vTaskDelete( NULL ).
-
Artificially force an assert() to be triggered if configASSERT() is
-
defined, then stop here so application writers can catch the error. */
-
configASSERT( uxCriticalNesting == ~
0UL );
-
portDISABLE_INTERRUPTS();
-
-
-
-
-
* See header file for description.
-
-
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
-
-
/* Simulate the stack frame as it would be created by a context switch
-
-
pxTopOfStack--;
/* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
-
*pxTopOfStack = portINITIAL_XPSR;
/* xPSR */
-
-
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
/* PC */
-
-
*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. */
-
-
-
按照 CM3 压栈方式,这个函数手动做了一次:
xPSR 的位置给了初值为:0x01000000,其中 bit24 被置 1,表示使用 Thumb 指令;
![](https://img-blog.csdnimg.cn/20200628100959354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob3V0YW9wb3dlcg==,size_16,color_FFFFFF,t_70)
PC 的位置给了这个任务的入口,当 OS 调度选择好 SP 后,退出调度,这个就会被赋值给 CPU 的 PC 指针,也就是,任务函数被调用;
LR 的位置赋值了一个叫做 prvTaskExitError 的函数,因为我们的任务是永不返回的,所以如果任务返回了,说明出错了;
R12,R3,R2,R1 的位置预留;
R0 的位置保存了传递给任务的参数指针 pvParameters,根据汇编的调用规则,R0 是第一个传参;
剩余的位置给了 R11, R10, R9, R8, R7, R6, R5 和 R4;
此刻的 pxTopOfStack 便可用直接赋值给 TCB->pxTopOfStack;
OK,此刻 TCB 的基本元素已经悉数初始化完毕;
我们在回到 xTaskCreate 调用,看最后,最后还调用了 prvAddNewTaskToReadyList,将当前已经初始化完毕的任务添加到 Ready 链表;
2.4、prvAddNewTaskToReadyList
prvAddNewTaskToReadyList,将任务添加到 Ready 链表,直接干代码:
-
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
-
-
/* Ensure interrupts don't access the task lists while the lists are being
-
-
-
-
uxCurrentNumberOfTasks++;
-
if( pxCurrentTCB ==
NULL )
-
-
/* There are no other tasks, or all the other tasks are in
-
the suspended state - make this the current task. */
-
-
-
if( uxCurrentNumberOfTasks == ( UBaseType_t )
1 )
-
-
/* This is the first task to be created so do the preliminary
-
initialisation required. We will not recover if this call
-
fails, but we will report the failure. */
-
prvInitialiseTaskLists();
-
-
-
-
mtCOVERAGE_TEST_MARKER();
-
-
-
-
-
/* If the scheduler is not already running, make this task the
-
current task if it is the highest priority task to be created
-
-
if( xSchedulerRunning == pdFALSE )
-
-
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
-
-
-
-
-
-
mtCOVERAGE_TEST_MARKER();
-
-
-
-
-
mtCOVERAGE_TEST_MARKER();
-
-
-
-
-
-
#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 );
-
-
-
-
if( xSchedulerRunning != pdFALSE )
-
-
/* If the created task is of a higher priority than the current task
-
then it should run now. */
-
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
-
-
taskYIELD_IF_USING_PREEMPTION();
-
-
-
-
mtCOVERAGE_TEST_MARKER();
-
-
-
-
-
mtCOVERAGE_TEST_MARKER();
-
-
Ready 链表在多处访问,ISR 中也有访问,为了保证访问的有效性,这里先调用 taskENTER_CRITICAL() 进入临界区;
uxCurrentNumberOfTasks 记录了当前任务的个人,此刻任务新增了,所以这里自加;
pxCurrentTCB 是一个全局变量,在调度器还未工作的时候,指向的是 Ready 中优先级最高的任务;这里判断是否当前 Add 进来的任务是第一个任务,如果是第一个任务,那么调用 prvInitialiseTaskLists 来初始化任务链表;
-
PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
/*按照优先级排序的就绪态任务*/
-
PRIVILEGED_DATAstatic List_t xDelayedTaskList1;
/*延时的任务 */
-
PRIVILEGED_DATAstatic List_t xDelayedTaskList2;
/*延时的任务 */
-
PRIVILEGED_DATAstatic List_t xPendingReadyList;
/*任务已就绪,但调度器被挂起 */
-
-
#if (INCLUDE_vTaskDelete == 1 )
-
PRIVILEGED_DATA
static List_t xTasksWaitingTermination;
/*任务已经被删除,但内存尚未释放*/
-
-
-
#if (INCLUDE_vTaskSuspend == 1 )
-
PRIVILEGED_DATA
static List_t xSuspendedTaskList;
/*当前挂起的任务*/
-
-
-
-
static void prvInitialiseTaskLists( void )
-
-
-
-
for( uxPriority = ( UBaseType_t )
0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
-
-
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
-
-
-
vListInitialise( &xDelayedTaskList1 );
-
vListInitialise( &xDelayedTaskList2 );
-
vListInitialise( &xPendingReadyList );
-
-
#if ( INCLUDE_vTaskDelete == 1 )
-
-
vListInitialise( &xTasksWaitingTermination );
-
-
#endif /* INCLUDE_vTaskDelete */
-
-
#if ( INCLUDE_vTaskSuspend == 1 )
-
-
vListInitialise( &xSuspendedTaskList );
-
-
#endif /* INCLUDE_vTaskSuspend */
-
-
/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
-
-
pxDelayedTaskList = &xDelayedTaskList1;
-
pxOverflowDelayedTaskList = &xDelayedTaskList2;
-
Ready 链表是一个链表数组的形式,将不同优先级的分开来放,典型的空间换时间;
这里基本上都是调用 vListInitialise 函数来初始化各个链表结构,具体的不深入聊,参考《FreeRTOS --(1)链表》
相关链表已经初始化完毕,如果当前 pxCurrentTCB 不为 NULL,那么一定就不是第一次添加任务,此刻判断调度器是否已经开始工作了(创建任务可以在调度器开始工作之前,也可以在调度器工作之后);
pxCurrentTCB 是一个静态的全局变量,这个变量用来指向当前正在运行的任务 TCB
如果调度器还没有开始工作,则比较当前新增的任务的优先级是否大于上一个任务,如果是,那么更新 pxCurrentTCB 到这个最新的任务;
调用 prvAddTaskToReadyList 将当前的这个 TCB 添加到以这个 TCB 优先级为数组下标的那个 Ready 链表尾部;
-
#define prvAddTaskToReadyList( pxTCB ) \
-
taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority ); \
-
vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );
宏 taskRECORD_READY_PRIORITY() 用来更新静态全局变量 uxTopReadyPriority,它记录处于就绪态的最高任务优先级。这个变量参与了 FreeRTOS 的最核心代码:确保处于优先级最高的就绪任务获得 CPU 运行权。它在这里参与如何最快的找到优先级最高的就绪任务。
调用 taskEXIT_CRITICAL(); 退出临界区;
根据 xSchedulerRunning 标准,判断是否调度器已经跑起来了,如果没有跑起来,什么都不做,等着调度器起来,调度(因为前面已经将最高优先级的任务更新到了 pxCurrentTCB ),如果此刻调度器已经在运转,而新加入的这个任务优先级高于了当前运行任务的优先级,那么调用 taskYIELD_IF_USING_PRREEMPTION(),进而走到 portYIELD 强制触发一次任务切换,让最高优先级的任务得到调度;
总体来说,创建一个任务的流程如下:
![](https://img-blog.csdnimg.cn/202006302023206.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob3V0YW9wb3dlcg==,size_16,color_FFFFFF,t_70)