【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构

上篇讲述了list.c关于链表操作的源码阅读,此片文章将开始阅读task.c

task.h相关结构体


/* 由eTaskGetState返回的任务状态 */
typedef enum
{
	eRunning = 0,	/* 一个任务查询自己的状态,必定是运行状态 */
	eReady,			/* 被查询的任务处于Ready状态 */
	eBlocked,		/* 被查询的任务处于被阻塞的状态 */
	eSuspended,		/* 被查询的任务处于被挂起的状态 */
	eDeleted		/* 被查询的任务已经被删除了,但是他的TCB还没有被释放 */
} eTaskState;

/* 当调用vTaskNotify()函数是会有什么动作发生  */
typedef enum
{
	eNoAction = 0,				/* 不通过更新任务的 notify value 而通知任务 */
	eSetBits,					/* 设置任务的 notification value对应的bit */
	eIncrement,					/* 增加 notification value. */
	eSetValueWithOverwrite,		/* 直接覆盖任务的notification value. */
	eSetValueWithoutOverwrite	/* 当任务读取完notification value后再设置notification value. */
} eNotifyAction;

/* Used with the uxTaskGetSystemState() function to return the state of each task
in the system. */
typedef struct xTASK_STATUS
{
	TaskHandle_t xHandle;			/* The handle of the task to which the rest of the information in the structure relates. */
	const char *pcTaskName;			/* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	UBaseType_t xTaskNumber;		/* A number unique to the task. */
	eTaskState eCurrentState;		/* The state in which the task existed when the structure was populated. */
	UBaseType_t uxCurrentPriority;	/* The priority at which the task was running (may be inherited) when the structure was populated. */
	UBaseType_t uxBasePriority;		/* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
	uint32_t ulRunTimeCounter;		/* The total run time allocated to the task so far, as defined by the run time stats clock.  See http://www.freertos.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
	uint16_t usStackHighWaterMark;	/* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */
} TaskStatus_t;

/* Possible return values for eTaskConfirmSleepModeStatus(). */
typedef enum
{
	eAbortSleep = 0,		/* A task has been made ready or a context switch pended since portSUPPORESS_TICKS_AND_SLEEP() was called - abort entering a sleep mode. */
	eStandardSleep,			/* Enter a sleep mode that will not last any longer than the expected idle time. */
	eNoTasksWaitingTimeout	/* No tasks are waiting for a timeout so it is safe to enter a sleep mode that can only be exited by an external interrupt. */
} eSleepModeStatus;

task control block 结构体定义:


/*
 * 任务控制块,每个任务都会单独申请一个任务块,任务块存储任务的状态信息,其中包括任务的上下文信息(包括寄存器数值在内的任务运行时环境)
 */
typedef struct tskTaskControlBlock
{
	volatile StackType_t	*pxTopOfStack;	/*< 指向任务栈的最后一个元素的地址.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

	ListItem_t			xGenericListItem;	/*< 被用于表示任务状态链表中的项目 (Ready, Blocked, Suspended ). */
	ListItem_t			xEventListItem;		/*< 被用于表示事件的链表中的项目 */
	UBaseType_t			uxPriority;			/*< 任务的优先级,0是最低优先级 */
	StackType_t			*pxStack;			/*< 指向任务栈的起始地址 */
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];/*< 描述被创建任务的名称,调试时用到. */

	#if ( portCRITICAL_NESTING_IN_TCB == 1 )
		UBaseType_t 	uxCriticalNesting; 	/*< 用于那些无法使用嵌套调用的平台上保持嵌套层数,arm-cortex-m3不需要 */
	#endif

    UBaseType_t		uxTCBNumber;		/*< 每次TCB被创建都会增加的数. */
    UBaseType_t  	uxTaskNumber;		/*< 第三方使用 */

	#if ( configUSE_MUTEXES == 1 )
		UBaseType_t 	uxBasePriority;		/*< 最后分配给任务的优先级-由优先级继承机制使用。 */
		UBaseType_t 	uxMutexesHeld;
	#endif



    volatile uint32_t ulNotifiedValue;
    volatile eNotifyValue eNotifyState;

} tskTCB;

task.c用到的内部全局变量:

static volatile UBaseType_t uxCurrentNumberOfTasks 	= ( UBaseType_t ) 0U;//当前的任务数量
static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;//时钟计数
static volatile UBaseType_t uxTopReadyPriority 		= tskIDLE_PRIORITY;//最高的ready任务的优先级
static volatile BaseType_t xSchedulerRunning 		= pdFALSE;//调度器是否在运行
static volatile UBaseType_t uxPendedTicks 			= ( UBaseType_t ) 0U;//调度器被挂起的次数
static volatile BaseType_t xYieldPending 			= pdFALSE;//空闲任务被挂起,不允许切换到yield任务
static volatile BaseType_t xNumOfOverflows 			= ( BaseType_t ) 0;
static UBaseType_t uxTaskNumber 					= ( UBaseType_t ) 0U;//任务变更次数
static volatile TickType_t xNextTaskUnblockTime		= portMAX_DELAY;

BaseType_t xTaskGenericCreate( \
                    TaskFunction_t pxTaskCode, \
                    const char * const pcName, \
                    const uint16_t usStackDepth, \
                    void * const pvParameters, \
                    UBaseType_t uxPriority, \
                    TaskHandle_t * const pxCreatedTask, \
                    StackType_t * const puxStackBuffer, \
                    const MemoryRegion_t * const xRegions )
{
BaseType_t xReturn;
TCB_t * pxNewTCB;
StackType_t *pxTopOfStack;

	/* 申请TCB 和 新任务的栈 所需要的内存,并检查是否申请成功 */
	pxNewTCB = prvAllocateTCBAndStack( usStackDepth, puxStackBuffer );

	if( pxNewTCB != NULL )
	{
		/* 计算栈顶的地址,这个依赖平台的栈生长方向(arm-cortex-m3 增长方向为向下增长,高地址到低地址)*/
        
        pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( uint16_t ) 1 );
        //arm-cortex-m3 四字节对齐
        pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK  ) ); 

		/* Setup the newly allocated TCB with the initial state of the task. */
		prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );

		/* 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. */

		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

		if( ( void * ) pxCreatedTask != NULL )
		{
			/* Pass the TCB out - in an anonymous way.  The calling function/
			task can use this as a handle to delete the task later if
			required.*/
			*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* Ensure interrupts don't access the task lists while they are being
		updated. */
		taskENTER_CRITICAL();
		{
			uxCurrentNumberOfTasks++;
			if( pxCurrentTCB == NULL )
			{
				/* There are no other tasks, or all the other tasks are in
				the suspended state - make this the current task. */
				pxCurrentTCB =  pxNewTCB;

				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();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* If the scheduler is not already running, make this task the
				current task if it is the highest priority task to be created
				so far. */
				if( xSchedulerRunning == pdFALSE )
				{
					if( pxCurrentTCB->uxPriority <= uxPriority )
					{
						pxCurrentTCB = pxNewTCB;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}

			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 );

			xReturn = pdPASS;
			portSETUP_TCB( pxNewTCB );
		}
		taskEXIT_CRITICAL();
	}
	else
	{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		traceTASK_CREATE_FAILED();
	}

	if( xReturn == pdPASS )
	{
		if( xSchedulerRunning != pdFALSE )
		{
			/* If the created task is of a higher priority than the current task
			then it should run now. */
			if( pxCurrentTCB->uxPriority < uxPriority )
			{
				taskYIELD_IF_USING_PREEMPTION();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	return xReturn;
}

xTaskGenericCreate函数创建任务的流程如下图:

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第1张图片

 


static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer )
{
TCB_t *pxNewTCB;

	StackType_t *pxStack;

		/* 调用portMalloc给TCB的栈申请 usStackDepth * 32 Byte的空间(4Byte 对齐) */
		pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer ); 


		if( pxStack != NULL )
		{
			/* 给TCB 申请内存 */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
				/* Store the stack location in the TCB. */
				pxNewTCB->pxStack = pxStack;
			}
			else
			{
				/* 如果TCB的空间申请失败则释放掉栈的内存空间 */
				vPortFree( pxStack );
			}
		}
		else
		{
			pxNewTCB = NULL;
		}
·

	return pxNewTCB;
}

上面是在创建任务时给TCB和任务的栈分配内存空间的函数。


static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority, const MemoryRegion_t * const xRegions, const uint16_t usStackDepth ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
UBaseType_t x;

	/* 存储任务描述名称 */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxTCB->pcTaskName[ x ] = pcName[ x ];

		if( pcName[ x ] == 0x00 )
		{
			break;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	/* 确保任务描述名称在允许的最大长度截至 */
	pxTCB->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();
	}

	pxTCB->uxPriority = uxPriority;
	#if ( configUSE_MUTEXES == 1 )
	{
		pxTCB->uxBasePriority = uxPriority;
		pxTCB->uxMutexesHeld = 0;
	}
	#endif /* configUSE_MUTEXES */

    /*< 初始化TCB中状态链表项和事件链表项 >*/
	vListInitialiseItem( &( pxTCB->xGenericListItem ) );
	vListInitialiseItem( &( pxTCB->xEventListItem ) );

	/* 更新链表项的所属关系 */
	listSET_LIST_ITEM_OWNER( &( pxTCB->xGenericListItem ), pxTCB );

	/* 事件链表总是按任务的优先级排序 */
	listSET_LIST_ITEM_VALUE( &( pxTCB->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( &( pxTCB->xEventListItem ), pxTCB );

}

上面的代码是将新申请的TCB初始化。包含:任务的描述名称更新、任务优先级更新、任务状态链表和事件链表的初始化。

在FreeRTOS中任务优先级是降序排列(即优先级为10的任务优先级比优先级为1的任务高),在事件链表中记录时却是取反进行记录的(见listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );),因为链表插入排序是使用升序排列的(即数字小的在链表前端,数字大的在链表后端)。

在记录最高优先级的准备链表uxTopReadyPriority是是通过优先级通过移位记录portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) ),然后通过portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31 - __clz( ( uxReadyPriorities ) ) ) __clz读取最高非零位然后得到优先级,然后再去指定优先级的ReadyList中读取任务的TCB进行调度。


StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* 模拟由中断引起的上下文切换,arm-cortex-m3进入中断时的入栈顺序 */
	pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) pxCode;	/* PC */
	pxTopOfStack--;
	*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. */

	return pxTopOfStack;
}

初始化新建任务的栈,模拟因为中断到来引起的上下文切换,这个可以查询Cortex-M3权威手册,内容如下:

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第2张图片

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第3张图片

由此来看,经过任务创建函数之后,新任务的任务控制块如下图所示:

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第4张图片

FreeRTOS创建第一个任务后的链表结构如下:

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第5张图片

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第6张图片

【FreeRTOS源码阅读】 task.c (1) 任务创建以及TCB、List的结构_第7张图片

你可能感兴趣的:(FreeRTOS)