FreeRTOS任务创建过程详解(全网最细)

一、任务创建函数分析

FreeRTOS的任务创建由函数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 )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
StackType_t *pxStack;
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );      (1)
		if( pxStack != NULL )
		{
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );    (2)
			if( pxNewTCB != NULL )
			{
				/* Store the stack location in the TCB. */
				pxNewTCB->pxStack = pxStack;                           (3)
			}
			else
			{
				/* The stack cannot be used as the TCB was not created.  Free
				it again. */
				vPortFree( pxStack );                                  (4)
			}
		}
		else
		{
			pxNewTCB = NULL;
		}

	if( pxNewTCB != NULL )
	{
		#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
		{
			pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; (5)
		}
		#endif /* configSUPPORT_STATIC_ALLOCATION */

		prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );    (6)
		prvAddNewTaskToReadyList( pxNewTCB );
		xReturn = pdPASS;                                     (7)
	}
	else
	{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}

	return xReturn;
}
  • (1)、使用函数pvPortMalloc()给任务的任务堆栈申请内存,申请内存的时候会做字节对齐处理。
  • (2)、如果堆栈的内存申请成功的话就接着给任务控制块申请内存,同样使用函数pvPortMalloc()。
  • (3)、任务控制块内存申请成功的话就初始化内存控制块中的任务堆栈字段pxStack,使用第一步中申请到的任务堆栈。
  • (4)、如果任务控制块内存申请失败的话就释放前面已经申请成功的任务堆栈的内存。
  • (5)、标记任务堆栈和任务控制块是使用动态内存分配方法得到的。
  • (6)、使用函数 prvInitialiseNewTask()初始化任务,这个函数完成对任务控制块中各个字段的初始化工作!
  • (7)、使用函数prvAddNewTaskToReadyList()将新创建的任务加入到就绪列表中。

二、任务初始化函数分析

函数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 ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
StackType_t *pxTopOfStack;
UBaseType_t x;
	#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 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 ) );                                           (1)
	}
	#endif 
		pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );     (2)
		pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];                                  (3)
		if( pcName[ x ] == 0x00 )
		{
			break;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';                   (4)
	if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )                      (5)
	{
		uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	pxNewTCB->uxPriority = uxPriority;                                            (6)
	#if ( configUSE_MUTEXES == 1 )                                                (7)
	{
		pxNewTCB->uxBasePriority = uxPriority;
		pxNewTCB->uxMutexesHeld = 0;
	}
	#endif 

	vListInitialiseItem( &( pxNewTCB->xStateListItem ) );                         (8)
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) );                         (9)

	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );           (10)

	listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t )    configMAX_PRIORITIES - ( TickType_t ) uxPriority );                               (11)
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );           (12)

	#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( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
	{
		for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
		{
			pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;                   (12)
		}
	}
	#endif

	#if ( configUSE_TASK_NOTIFICATIONS == 1 )//使能任务通知功能
	{
		pxNewTCB->ulNotifiedValue = 0;
		pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
	}
	#endif

	#if ( configUSE_NEWLIB_REENTRANT == 1 )//使能NEWLIB
	{
		/* Initialise this task's Newlib reent structure. */
		_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
	}
	#endif

	#if( INCLUDE_xTaskAbortDelay == 1 )//使能函数INCLUDE_xTaskAbortDelay()
	{
		pxNewTCB->ucDelayAborted = pdFALSE;
	}
	#endif
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );                                                                   (13)
	if( ( void * ) pxCreatedTask != NULL )
	{
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;                               (14)
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}
  • (1)、如果使能了堆栈溢出检测功能或者追踪功能的话就使用一个定值tskSTACK_FILL_BYTE来填充任务堆栈,这个值为0xa5U。
  • (2)、计算堆栈栈顶pxTopOfStack,后面初始化堆栈的时候需要用到。
  • (3)、保存任务的任务名。
  • (4)、任务名数组添加字符串结束符’\0’。
  • (5)、判断任务优先级是否合法,如果设置的任务优先级大于configMAX_PRIORITIES,将优先级修改为configMAX_PRIORITIES - 1。
  • (6)、初始化任务控制块的优先级字段uxPriority。
  • (7)、使能了互斥信号量功能,需要初始化相应的字段。
  • (8)和(9)、初始化列表项xStateListltem和xEventListltem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。
  • (10)和(12)、设置列表项xStateListltem和xEventListltem属于当前任务的任务控制块,也就是设置这两个列表项的字段pvOwner为新创建的任务的任务控制块。
  • (11)、设置列表项xEventListltem的字段xItemValue为configMAX_PRIORITIES -uxPriority,比如当前任务优先级3,最大优先级为32,那么xItemValue就为32-3=29,这就意味着xItemValue值越大,优先级就越小。列表和列表项的时候说过,列表的插入是按xItemValue的值升序排列的
  • (12)、初始化线程本地存储指针,如果使能了这个功能的话。
  • (13)、调用函数pxPortInitialiseStack()初始化任务堆栈。
  • (14)、生成任务句柄,返回给参数pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块

三、任务堆栈初始化函数分析

任务初始化函数中会对任务堆栈初始化,这个过程通过调用函数pxPortInitialiseStack()来完成。下面就是该函数的源码:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	pxTopOfStack--;

	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */                                (1)
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	      (2)
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */                  (3)

	/* Save code space by skipping register initialisation. */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */                                 (4)
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */                      (5)

	/* A save method is being used that requires each task to maintain its
	own exec return value. */
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_EXEC_RETURN;                                      (6)

	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */                (7)

	return pxTopOfStack;
}

堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照:XPSR、R15(PC)、R14(LR)、R12、R3 ~ RO、R11 ~ R14。
(1)、寄存器xPSR值为portINITIAL_XPSR,其值为0×01000000。xPSR是Cortex-M4的一个内核寄存器,叫做程序状态寄存器,0x01000000表示这个寄存器的bit24为1,表示处于Thumb状态,即使用的Thumb指令。
(2)、寄存器PC初始化为任务函数pxCode。
(3)、寄存器LR初始化为函数prvTaskExitError。
(4)、跳过4个寄存器,R12、R3、R2、R1,这四个寄存器不初始化。
(5)、寄存器R0初始化为pvParameters,一般情况下,函数调用会将R0~R3作为输入参数,
R0也可用作返回结果,如果返回值为64位,则R1也会用于返回结果,这里的pvParameters是作为任务函数的参数,保存在寄存器R0中。
(6)、保存EXC_RETURN值,用于退出SVC或PendSV中断的时候处理器应该处于什么态。处理器进入异常或中断服务程序(ISR)时,链接寄存器R14(LR)的数值会被更新为
EXC_RETURN数值,之后该数值会在异常处理结束时触发异常返回。这里人为的设置为OXFFFFFFD,表示退出异常以后CPU进入线程模式并且使用进程栈!
(7)、跳过8个寄存器,R11、R10、R8、R7、R6、R5、R4。
初始化后堆栈结果如下图所示:

FreeRTOS任务创建过程详解(全网最细)_第1张图片

四、创建任务完成到就绪列表

任务创建完成以后就会被添加到就绪列表中,FreeRTOS使用不同的列表表示任务的不同状态,在文件tasks.c中就定义了多个列表来完成不同的功能,这些列表如下:

PRIVILEGED_DATA static List_t pxReadyTasksLists[configMAX_PRIORITIES];
PRIVILEGED_DATA static List_t xDelayedTaskListl;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t* volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t* volatile pxOverflowDelayedTaskList;
PRIVILEGED_DATA static List_t xPendingReadyList;

列表数组pxReadyTasksLists [] 就是任务就绪列表,数组大小为configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数prvAddNewTaskToReadyList()来完成,函数如下:

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
	taskENTER_CRITICAL();
	{
		uxCurrentNumberOfTasks++;                                                  (1)
		if( pxCurrentTCB == NULL )//正在运行任务块为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();                                          (2)
			}
			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 <= pxNewTCB->uxPriority )
				{
					pxCurrentTCB = pxNewTCB;                                       (3)
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		uxTaskNumber++;//任务控制块编号 + 1

		#if ( configUSE_TRACE_FACILITY == 1 )
		{
			/* Add a counter into the TCB for tracing only. */
			pxNewTCB->uxTCBNumber = uxTaskNumber;
		}
		#endif /* configUSE_TRACE_FACILITY */
		prvAddTaskToReadyList( pxNewTCB );                                         (4)         

		portSETUP_TCB( pxNewTCB );
	}
	taskEXIT_CRITICAL();

	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();                                      (5)
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}
  • (1)、变量uxCurrentNumberOfTasks为全局变量,用来统计任务数量。
  • (2)、变量uxCurrentNumberOfTasks为1说明正在创建的任务是第一个任务!那么就需要先初始化相应的列表,通过调用函数prvInitialiseTaskLists()来初始化相应的列表。
  • (3)、新创建的任务优先级比正在运行的任务优先级高,所以需要修改pxCurrentTCB为新 建任务的任务控制块。
  • (4)、调用函数prvAddTaskToReadyList0将任务添加到就绪列表中,这个其实是个宏。
  • (5)、如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTIONO完成一次任务切换。

任务创建过程就讲解到这里啦!!!

你可能感兴趣的:(FreeRTOS操作系统,嵌入式,freertos)