FreeRTOS中任务控制块中关于堆栈的定义
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
//指向放置在任务堆栈上的最后一个项目的位置,对于向下增长的栈,这个总是指向最后一个入栈的项目。
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t *pxStack; /*< Points to the start of the stack. */
//位置是固定的,随着任务的运行,堆栈可能溢出,堆栈往下增长的系统中,这个变量就可以判断堆栈是否溢出,对于堆栈往上增长的系统,想确定堆栈是否溢出,还需要pxEndOfStack这个变量。
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. *///如果堆栈向上生长(portSTACK_GROWTH > 0),指针pxEndOfStack指向堆栈尾部,用于检验堆栈是否溢出。
#endif
...
}
xTaskCreate()函数中对栈的申请的过程
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //函数指针,指向任务函数的入口
const char * const pcName, //任务描述,主要用于调试
const uint16_t usStackDepth, //指定任务堆栈大小
void * const pvParameters, //任务函数所需要的参数
UBaseType_t uxPriority, //任务优先级
TaskHandle_t * const pxCreatedTask ) //任务句柄,创建任务后可以使用这个句柄引用任务;
/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
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. */
//如果堆栈增长方式是往低地址增加,则分配堆栈然后分配TCB,以便堆栈不会增长到TCB。 同样,如果堆栈增长方式是从低地址向高地址增加,则分配TCB然后分配堆栈。
#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 = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
if( pxStack != NULL )
{
/* Allocate space for the TCB. */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
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 */
//确定任务控制块的pxStack的数值,栈的起始位置
可以看到在创建任务的开始阶段就进行了有关freertos任务的两个内存申请,一个是任务堆栈,一个是任务控制块所需占用空间;
这其中根据portSTACK_GROWTH这个宏对申请的顺序进行了调整,先看一下这个宏的定义和区别。
这个是用户根据实际使用的芯片的堆栈增长方式来设定的,如果portSTACK_GROWTH大于0,那么堆栈就是向上增长的,否则就是向下增长的。
上图显示了堆栈 向上增长和向下增长的区别。
如果堆栈是向下增长,也就是从高地址向低地址增长,那么在任务刚开始创建后,堆栈是空的。如图中例子,栈顶在为TaskStk[0][511],栈底为在TaskStk[0][0]。
相反,如果堆栈是向上增长的,栈顶在为TaskStk[0][0],栈底为在TaskStk[0][511]。
那么,如果我们向堆栈中压入数据,例如推入0x0012ff78后,堆栈变化如下图:
如图,压栈后,若堆栈向下增长,在原来栈顶位置插入数据0x0012ff78,然后栈顶位置向低地址方向移4个字节,指向TaskStk[0][510]。若堆栈向上增长,在原来栈顶位置压入0x0012ff78,栈顶变为TaskStk[0][1]。
堆栈中向上增长----低地址向高地址方向增长。
向下增长----高地址向低地址方向增长。
堆栈中数据插入、删除遵循 后进先出的规则,因此插入数据向上增长和向下增长需要注意插入的方向。
申请过程中为什么要区别栈的增长方式,因为两个数据区,一个是任务栈一个是任务控制块TCB,TCB的大小是固定的,任务栈的大小存在变动。创建任务时候的申请是在堆上面进行的,堆上先申请的地址空间是低的,后申请的空间是高的。申请后任务栈以堆栈的方式进行访问。
如果栈往下增长,也就是栈顶的地址高,栈底的地址低,在这种情况下如果先申请的是TCB的空间后申请任务栈的空间,TCB地址<任务栈地址,在这种情况下,使用任务栈的时候,栈顶指针往低地址走,如果事先申请的任务栈空间不够,那么就会触碰到TCB的地址空间。而如果先申请任务栈再申请TCB,任务栈地址
到此为止其实就是确定了任务控制块pxStack的具体数据,pxStack是申请到的任务栈的起始地址,但是这个地址是不是栈顶地址是不确定的,因为如果栈的增长是往低地址增长的,此时pxStack的数值就不是栈顶地址;如果栈的增长方式是从低地址往高地址的,那么此时pxStack的数值就是栈顶地址。
到此为止任务控制块中pxStack的含义就明白了。
上述xTaskCreate函数的完成之处也就是设置任务控制块中pxStack的数值。
接下来xTaskCreate函数将调用prvInitialiseNewTask函数,这个函数就是初始化xTaskCreate中申请的任务控制块中的其他数据,包括另外两个关于堆栈地址的变量pxEndOfStack、pxTopOfStack。
这两个根据名字也知道,一个是栈顶地址,一个是栈底地址,栈顶地址将会随着任务的进行而发生变化(入栈和出栈操作)。
接下来是prvInitialiseNewTask函数的代码段
/* 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. */
//计算栈顶地址,这个取决于堆栈是往高地址增长还是往低地址增长(x86)的,确定栈顶位置
#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 */
根据前面的分析,如果portSTACK_GROWTH<0,那么栈是往低地址增长的,此时栈顶的地址其实不是pxStack,而是pxStack+任务栈总大小的位置,pxStack其实就是栈底地址;如果portSTACK_GROWTH>0,那么栈是往高地址增长的,此时栈顶地址就是pxStack,这时候,我们并不知道栈底地址了,所以此时需要pxEndOfStack这个变量来保存一下栈底地址。
为什么需要栈底地址,就是需要判断任务栈再任务运行过程中是否出现了栈溢出,什么时候发生栈溢出,栈顶地址和栈尾地址重合了,接下来栈顶地址继续走就会溢出了,所以栈底地址就是为了判断任务运行过程中是否会溢出。