FreeRTOS操作系统之任务控制块与任务静态创建函数

FreeRTOS列举一个简单的任务控制块结构体:

typedef struct tskTaskControlBlock
{
    volatile StackType_t *pxTopOfStack;   /* 栈顶 */
    ListItem_t	    xStateListItem;       /* 任务节点 */
    StackType_t     *pxStack;             /* 任务栈起始地址 */
    char            pcTaskName[ configMAX_TASK_NAME_LEN ];    /* 任务名称,长度默认16 */  
} tskTCB;

typedef tskTCB TCB_t;

在多任务系统中,任务的执行是由系统调度的,系统为每一个任务都额外定义了一个任务控制块,存放任务所有信息,例如任务的栈指针、任务名称、任务形式等。

 

静态任务创建函数:

typedef void * TaskHandle_t;
typedef void ( *TaskFunction_t )( void * )

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode,           /* 任务入口 */
					            const char * const pcName,           /* 任务名称,字符串形式 */
					            const uint32_t ulStackDepth,         /* 任务栈大小,单位为字 */
					            void * const pvParameters,           /* 任务形参 */
					            StackType_t * const puxStackBuffer,  /* 任务栈起始地址 */
					            TCB_t * const pxTaskBuffer )         /* 任务控制块指针 */
{
	TCB_t *pxNewTCB;
	TaskHandle_t xReturn;

	if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
	{		
		pxNewTCB = ( TCB_t * ) pxTaskBuffer; 
		pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;

		/* 创建新的任务 */
		prvInitialiseNewTask( pxTaskCode,        /* 任务入口 */
                              pcName,            /* 任务名称,字符串形式 */
                              ulStackDepth,      /* 任务栈大小,单位为字 */ 
                              pvParameters,      /* 任务形参 */
                              &xReturn,          /* 任务句柄 */ 
                              pxNewTCB);         /* 任务控制块指针 */      

	}
	else
	{
		xReturn = NULL;
	}

	/* 返回任务句柄,如果任务创建成功,此时xReturn应该指向任务控制块 */
    return xReturn;
}

#endif /* configSUPPORT_STATIC_ALLOCATION */

在FreeRTOS中,任务的创建可采用两种方法,一种是使用动态创建,另一种是使用静态创建。动态创建:任务控制块和栈的内存是创建任务时动态分配的,任务删除时,内存可以释放;静态创建:任务控制块和栈的内存需要事先定义好,是静态的内存,任务删除时,内存不能释放。

可以看到xTaskCreateStatic函数做了两个动作:

1.设置任务控制块栈内存起始地址,并返回任务控制块指针

2.调用prvInitialiseNewTask函数来创建新任务,接下来分析prvInitialiseNewTask函数做了什么

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,              /* 任务入口 */
		const char * const pcName,              /* 任务名称,字符串形式 */
		const uint32_t ulStackDepth,            /* 任务栈大小,单位为字 */
		void * const pvParameters,              /* 任务形参 */
		TaskHandle_t * const pxCreatedTask,     /* 任务句柄 */
		TCB_t *pxNewTCB )                       /* 任务控制块指针 */

{
	StackType_t *pxTopOfStack;
	UBaseType_t x;	
	
	/* 获取栈顶地址 */
	pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
	
	/* 向下做8字节对齐 */
	pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );	

	/* 将任务的名字存储在TCB中 */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];

		if( pcName[ x ] == 0x00 )
		{
			break;
		}
	}
	/* 任务名字的长度不能超过configMAX_TASK_NAME_LEN */
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';

    /* 初始化TCB中的xStateListItem节点 */
        vListInitialiseItem( &( pxNewTCB->xStateListItem ) );

    /* 设置xStateListItem节点的拥有者 */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
    
    
    /* 初始化任务栈 */
	pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );   


	/* 让任务句柄指向任务控制块 */
    if( ( void * ) pxCreatedTask != NULL )
	{		
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}
}

函数解析:

1)栈顶地址向下做8字节对齐,在Cortex-M3(Cortex-M4或Cortex-M7)内核的单片机中,因为总线宽度时32位的,通常只要栈保持4字节对齐即可,那么此处为何需要8字节?难道有哪些操作是64位?确实有,那就是浮点运算,所以要8字节对齐(但是目前还没有涉及浮点运算,此处的设置只是为了后续能够兼容浮点运算)。如果栈顶指针是8字节对齐的,在进行向下8字节对齐时,指针不会移动;如果不是8字节对齐的,在做向下8字节对齐时,就会空出几个字节,如pxTopOfStack是33时,明显不能被8整除,进行向下8字节对齐就是32,那么就会空出一个字节不能使用。进行8字节对齐,只需要将地址低三位清零(1000值等于8,所以x000形式代表地址8字节对齐)。

2)初始化TCB中的xStateListItem节点,即初始化该节点所在的链表为空,表士节点还没有插入任何链表

3)调用pxPortInitialiseStack()函数初始化任务栈,并更新栈顶指针,任务第一次运行的环境参数就存在任务栈中。

FreeRTOS操作系统之任务控制块与任务静态创建函数_第1张图片

继续深入分析pxPortInitialiseStack()函数:

#define portINITIAL_XPSR			        ( 0x01000000 )
#define portSTART_ADDRESS_MASK				( ( StackType_t ) 0xfffffffeUL )

static void prvTaskExitError( void )
{
    /* 函数停止在这里 */
    for(;;);
}

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    /* 异常发生时,自动加载到CPU寄存器的内容 */
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_XPSR;	                               /* xPSR的bit24必须置1 */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;   /* PC,即任务入口函数 */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	               /* LR,函数返回地址 */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1 默认初始化为0 */
	*pxTopOfStack = ( StackType_t ) pvParameters;	                       /* R0,任务形参 */
    
    /* 异常发生时,手动加载到CPU寄存器的内容 */    
	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4默认初始化为0 */

    /* 返回栈顶指针,此时pxTopOfStack指向空闲栈 */
    return pxTopOfStack;
}

1)异常发生时,CPU自动从栈中加载到CPU寄存器的内存,包括8个寄存器,分别是r0、r1、r2、r3、r12、r14、r15和xPSR的位24,且顺序不能改变

2)任务返回地址概念,通常任务时不会返回的,如果返回了就跳转到prvTaskExitError,该函数是一个无限循环

3)r12、r3、r2、r1默认初始化为0;异常发生时,需要手动加载到CPU寄存器的内容,总共有8个,分别为r4、r5、r6、r7、r8、r9、r10、r11,默认初始化为0

4)返回栈顶指针,任务第一次运行时,就是从这个栈顶指针开始手动加载8个字的内容到CPU寄存器:r4、r5、r6、r7、r8、r9、r10、r11,当推出异常时,栈中剩下的8个字的内容会自动加载到CPU寄存器:r0、r1、r2、r3、r12、r14、r15和xPSR的位24,此时PC指针就指向了任务入口地址,从而成功跳转到第一个任务。

 

 

你可能感兴趣的:(FreeRTOS)