FreeRTOS原理剖析:任务的创建

1. 任务创建API函数

任务的最基本功能是任务管理,任务管理中最基本操作是任务的创建和删除。对于任务的创建和删除,由于篇幅有点长,分两篇分别讲解。在FreeRTOS中任务的创建函数如下:

函数描述
xTaskCreate()使用动态方法创建一个任务
xTaskCreateStatic()使用静态方法创建一个任务
xTaskCreateRestricted()创建一个使用MPU进行限制的任务,使用动态内存分配,要求对应的MCU有MPU功能(内存保护单元)

其它内部调用函数:

函数描述
prvInitialiseNewTask()初始化任务
pxPortInitialiseStack()初始化堆栈
prvAddNewTaskToReadyList()若第一次创建任务,将调函数prvInitialiseTaskLists(),初始化各个系统列表,最后调用函数prvAddTaskToReadyList(),将新创建的任务挂接到任务就绪表中
prvInitialiseTaskLists()初始化任务列表
prvAddTaskToReadyList()将新创建的任务挂接到任务就绪表中

2. 任务的动态创建

2.1 任务动态创建函数xTaskCreate()

该函数是使用动态方法创建一个任务,每个任务需要RAM来保存任务的状态信息,在FreeRTOS中称为任务控制块。该函数会自动给任务申请任务控制块空间、申请任务的堆栈空间,不需要用户自己定义堆栈和任务控制块储存空间。另外也会将任务添加到任务就绪表中,使创建的任务处于就绪态,供任务调度器调用。使用此函数,必须将configSUPPORT_DYNAMIC_ALLOCATION设置为1,此宏定义在FreeRTOSConfig.h文件中。

函数原型如下:

BaseType_t xTaskCreate(	TaskFunction_t 		 pxTaskCode,
					    const char * const 	 pcName,
						const uint16_t 		 usStackDepth,
						void * const 		 pvParameters,
					    UBaseType_t 		 uxPriority,
					    TaskHandle_t * const pxCreatedTask )

函数参数说明:

  • pxTaskCode: 任务函数指针,指向任务函数入口。该参数是新定义的类型typedef void ( * TaskFunction_t )(void * ),该类型在projdefs.h中定义,表示一个指向函数的指针,其无返回值(void)和参数是 void * 类型。
    通常 void* 用在函数参数值(或者返回值)中是为了兼容不同指针类型的传递。可以将别的类型指针无需强制类型转换,就能赋值给 void* 类型,也可以将 void* 强制类型转换成任何别的指针类型。
  • pcName: 任务的描述性名字,主要用于追踪和调试。字符串最大长度不超过configMAX_TASK_NAME_LEN,在FreeRTOSConfig.h中定义。
  • usStackDepth: 任务堆栈的大小。当MCU为STM32时,实际为字节数为:usStackDepth * 4。内存申请函数参数为size_t类型,即堆栈能分配的最大字节数为类型size_t能表示的最大数字。
  • pvParameters: 函数的参数。当任务创建时,作为一个参数传递给任务,当不需要参数时,设为NULL。
  • uxPriority: 任务的优先级。最大不能超过configMAX_PRIORITIES - 1,在FreeRTOSConfig中定义。
  • pxCreatedTask: 任务的句柄。创建一个任务时,会保存任务一个句柄(TCB的入口地址),可以使用这个句柄引用任务,如改变任务的优先级或删除任务等,如果程序中不需要句柄,可将该参数设为NULL。

返回值:

  • pdPASS: 任务创建成功
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,栈内存不足。

函数代码如下:

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;

	/* 
	 * portSTACK_GROWTH > 0时,表示堆栈向上增长
	 * portSTACK_GROWTH <=0时,表示堆栈向下增长
	 * portSTACK_GROWTH 表示栈的生长方向,对于ARM CM4是向下增长的
	 * portSTACK_GROWTH在portmacro.h中定义,默认为 -1
	 */
	#if( portSTACK_GROWTH > 0 )
	{
		/* 堆栈向上增长,为防止堆栈增长到任务控制块,则先为任务控制块申请内存 */
		pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

		/* 任务控制块申请内存成功 */
		if( pxNewTCB != NULL )
		{
			/* 
			 * 堆栈申请内存,堆栈的实际内存空间等于(usStackDepth  * sizeof( StackType_t ) )
			 * 其中,StackType_t 为uint32_t类型,即堆栈实际字节数为:usStackDepth  *  4
			 * 直接让任务控制块中堆栈指针指向申请的堆栈入口地址
			 */
			pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
			
			/* 如果堆栈内存申请失败 */
			if( pxNewTCB->pxStack == NULL )
			{
				vPortFree( pxNewTCB );	/* 堆栈内存申请失败,释放掉刚刚申请的任务控制块内存 */
				pxNewTCB = NULL;		/* pxNewTCB 指针指向空地址,因为释放掉了刚刚申请的任务控制块内存 */
			}
		}
	}
	#else /* portSTACK_GROWTH */
	{
		StackType_t *pxStack;

		/* 
		 * 堆栈申请内存,堆栈的实际内存空间等于(usStackDepth  * sizeof( StackType_t ) )
		 * 其中,StackType_t 为uint32_t类型,即堆栈实际字节数为:usStackDepth  *  4
		 */
		pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );

		/* 堆栈申请成功 */
		if( pxStack != NULL )
		{
			/* 任务控制块申请内存 */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 
			
			/* 任务控制块申请成功 */
			if( pxNewTCB != NULL )
			{
				/* 任务控制块中堆栈指针指向刚刚申请的堆栈入口地址 */
				pxNewTCB->pxStack = pxStack;
			}
			else
			{
				/* 任务控制块申请失败,则释放掉刚刚申请的堆栈空间 */
				vPortFree( pxStack );
			}
		}
		else	/* 堆栈申请失败 */
		{
			pxNewTCB = NULL;	/* pxNewTCB 指针指向空地址 */
		}
	}
	#endif /* portSTACK_GROWTH */

	/* 如果任务控制块申请内存成功 */
	if( pxNewTCB != NULL )
	{
		#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
		{
			/* 标记任务是用动态的方法申请堆栈和任务控制块。为后面任务删除,自动收回控制块和堆栈内存时提供标记
			 * 若使用动态方式创建任务,任务控制块和堆栈空间会自动释放,静态方式时需要用户自己释放内存空间
			 */
			pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
		}
		#endif /* configSUPPORT_STATIC_ALLOCATION */

		/* 初始化任务 */
		prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );

		/* 将新任务插入到就绪列表中 */
		prvAddNewTaskToReadyList( pxNewTCB );
		
		xReturn = pdPASS;
	}
	else	/* 任务控制块内存申请失败 */
	{
		/* 返回堆内存申请失败,返回错误码 */
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}

	return xReturn;
}

说明:

  • 为防止堆栈增长到TCB,避免栈溢出覆盖了自己任务控制块。当portSTACK_GROWTH<=0时,表示堆栈向下增长,先申请任务控制块空间,再申请堆栈空间,portSTACK_GROWTH > 0时相反。Cortex-M3使用的是“向下生长的满栈”模型
  • pxNewTCB->ucStaticallyAllocated表示用动态的方式申请任务堆栈和任务控制块,为后面任务删除释放空间时提供标记。
  • xTaskCreate()函数最后调用了函数prvInitialiseNewTask(),该函数初始化任务,同时调用函数prvAddNewTaskToReadyList(),将任务插入到就绪列表中。
  • 任务创建成功后,添加到就绪列表,处于就绪态。如果此时该任务优先级最高,就由调度器切换为运行态。
2.2 函数prvInitialiseNewTask()分析

任务创建函数调用函数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 )

函数参数说明:

  • pxTaskCode: 任务函数指针,指向任务函数入口。该参数是新定义的类型 typedef void ( * TaskFunction_t)(void * ),该类型在projdefs.h中定义,表示一个指向函数的指针,其无返回值(void)和参数是 void* 类型。
  • pcName: 任务的描述性名字,主要用于追踪和调试。字符串最大长度不超过configMAX_TASK_NAME_LEN,在FreeRTOSConfig中定义。
  • usStackDepth: 任务堆栈的大小。当MCU为STM32时,实际为字节数为:usStackDepth * 4。内存申请函数参数为size_t类型,即堆栈能分配的最大字节数为类型size_t能表示的最大数字。
  • pvParameters: 函数的参数。当任务创建时,作为一个参数传递给任务,当不需要参数时,设为NULL。
  • uxPriority: 任务的优先级。最大不能超过configMAX_PRIORITIES - 1,在FreeRTOSConfig中定义。
  • pxCreatedTask: 任务的句柄。创建一个任务时,会保存任务一个句柄(TCB的入口地址),可以使用这个句柄引用任务,如改变任务的优先级或删除任务等,如果程序中不需要句柄,可将该参数设为NULL。
  • pxNewTCB: 栈缓冲区,指向任务栈空间入口地址。
  • xRegions: 任务控制块缓存区,指向任务控制块入口地址。

函数返回值:

函数代码如下:

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 ) 
{
	StackType_t *pxTopOfStack;
	UBaseType_t x;
	
	/* 如果开启了 MPU, 判断任务是否运行在特权模式 */
	#if( portUSING_MPU_WRAPPERS == 1 )
		BaseType_t xRunPrivileged;
		if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
		{
		 	/* 优先级特权模式掩码置位,任务运行在特权模式 */
			xRunPrivileged = pdTRUE;
		}
		else
		{
			xRunPrivileged = pdFALSE;
		}
		uxPriority &= ~portPRIVILEGE_BIT;
	#endif /* portUSING_MPU_WRAPPERS == 1 */

	/* 如果使能了堆栈溢出检测功能或者可视化跟踪调试功能 */
	#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
	{
		/* 将申请的堆栈用tskSTACK_FILL_BYTE填充 ,其中tskSTACK_FILL_BYTE默认为 0xa5U */
		( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
	}
	#endif /* ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) ) */

	/* 如果堆栈向下增长 */
	#if( portSTACK_GROWTH < 0 )
	{
		/* 
		 * 向下增长,计算堆栈栈顶,栈顶在内存的高位
		 * pxNewTCB->pxStack固定为申请的堆栈起始位置,即对于向下增长的堆栈需要计算堆栈栈顶
		 * pxTopOfStack随着堆栈的分配不断变化。
		 * pxNewTCB->pxStack为uint32_t类型的指针,若指针加1,移动4个地址位
		 */
		pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
		/*
		 * 8字节对齐
		 * 为了后续兼容浮点(64位)运行,向下8字节对齐
		 * 假如pxTopOfStack是36,能被4整除,但不能整除8,进行向下8字节对齐就是32,那么就会空出4个字节不使用
		 */
		pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
		
		/* (断言) 检查申请的堆栈是否符合8字节对齐 */
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
	}
	#else /* portSTACK_GROWTH */
	{
		/* 向上增长的堆栈,计算堆栈栈顶,栈顶在内存的低位 */
		pxTopOfStack = pxNewTCB->pxStack;
		
		 /* (断言) 检查申请的堆栈是否符合8字节对齐 */
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

		/* 对于堆栈向上增长,设置上边界,用于检验堆栈是否溢出 */
		pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
	}
	#endif /* portSTACK_GROWTH */

	/* 将任务名字保存到任务控制块中,方便调试 */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];

		/* 如果字符为0x00,跳出for()循环 */
		if( pcName[ x ] == 0x00 )
		{
			break;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	/* 确保TCB中任务名字的最后一个字节为'\0',可以看出任务名字超出范围,将会无用 */
	pxNewTCB->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();
	}

	pxNewTCB->uxPriority = uxPriority;	/* 任务优先级保存到任务控制块中 */
	
	/* 如果使用任务互斥量信号 */
	#if ( configUSE_MUTEXES == 1 )
	{
		pxNewTCB->uxBasePriority = uxPriority;
		pxNewTCB->uxMutexesHeld = 0;
	}
	#endif /* configUSE_MUTEXES */

	vListInitialiseItem( &( pxNewTCB->xStateListItem ) );	/* 初始化任务状态列表项 */
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) );	/* 初始化任务事件列表项 */

	/* 
	 * 将任务状态列表项的 pvOwner 指向所属的TCB 
	 * 如任务切换到运行态,系统从就绪列表取出这一项,获得TCB(ListItem->pvOwner),切换到运行状态 
	 */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

	/* 
	 * 给任务事件列表项中成员xItemValue赋项值,等于configMAX_PRIORITIES - uxPriority。
	 * 该值用于在对应事件列表中排序,按照升序排序,写入优先级的 “补数”,因此优先级越高越靠前
	 * 即保证优先级高的任务,在列表中越靠前。
	 */
	listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
	
	/* 将事件列表项的 pvOwner 指向所属的TCB  */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

	/* 初始化嵌套值为0 */
	#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 */

	/* 如果使用MPU功能 */
	#if ( portUSING_MPU_WRAPPERS == 1 )
	{
		/* 设置 MPU,任务内存访问权限设置 */
		vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
	}
	#else
	{
		/* 避免编译报 warning 没有使用变量 */
		( void ) xRegions;
	}
	#endif

	/* 初始化任务局部数据指针 */
	#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
	{
		for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
		{
			pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
		}
	}
	#endif

	/* 如果使用了任务通知功能,初始化任务消息通知变量 */
	#if ( configUSE_TASK_NOTIFICATIONS == 1 )
	{
		pxNewTCB->ulNotifiedValue = 0;
		pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;	
	}
	#endif

	/* 如果使用Newlib */
	#if ( configUSE_NEWLIB_REENTRANT == 1 )
	{
		_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
	}
	#endif

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		pxNewTCB->ucDelayAborted = pdFALSE;
	}
	#endif
	
	/*
	 *	初始化栈空间
	 *	执行一次该函数,相当于被调度器中断切换其它任务,而原函数入栈做了现场保护
	 *	当任务再次被调度器取出后,可以直接执行出栈恢复现场,运行任务
	 */
	#if( portUSING_MPU_WRAPPERS == 1 )
	{
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
	}
	#else /* portUSING_MPU_WRAPPERS */
	{
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
	}
	#endif /* portUSING_MPU_WRAPPERS */

	if( ( void * ) pxCreatedTask != NULL )
	{
		/* 让任务句柄指向任务控制块,可用于修改优先级,通知或者删除任务等 */
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}
2.3 函数pxPortInitialiseStack()分析

调用函数pxPortInitialiseStack()初始化任务堆栈,并将最新的栈顶指针赋值给任务TCB的pxTopOfStack。调用一次该函数,相当于执行了一次系统节拍中断,将一些重要的寄存器入栈。当任务需要运行时, 可以直接执行出栈恢复现场,运行任务。对于不同的硬件架构,其入栈的寄存器不同。

函数的原型如下:

StackType_t *pxPortInitialiseStack( StackType_t 	*pxTopOfStack, 
									TaskFunction_t 	 pxCode, 
									void 			*pvParameters )

函数参数说明:

  • pxTopOfStack: 栈顶指针,随着入栈或出栈变化而变化。
  • pxCode: 任务函数入口地址指针
  • pvParameters: 任务函数参数

返回值: 当前堆栈的栈顶地址

函数代码如下:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    pxTopOfStack--; 									/* 栈顶地址减1,相当于向下移动4个地址 */
    *pxTopOfStack = portINITIAL_XPSR;   				/* xPSR = 0x01000000,其中bit24被置1,表示使用Thumb指令*/
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* 任务函数指针 */
    pxTopOfStack--;
   *pxTopOfStack = ( StackType_t ) prvTaskExitError;   	/* LR为函数指针prvTaskExitError,由移植层提供的一个出错处理函数*/
    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架构,需要依次入栈xPSR、PC、LR、R12、R3 ~ R0、R11 ~ R4,其中R0 ~ R3、R12、R11 ~ R4不需要初始化。
  • 当中断发生时,LR被设置成中断要返回的地址。每个任务是一个死循环,不会退出任务函数,因此,一旦有任务函数退出,说明出错了,即会调用寄存器LR指向的函数来处理这个错误,即prvTaskExitError
  • 根据ATPCS(ARM-Thumb过程调用标准),子函数调用通过寄存器R0~R3传递参数
  • 任务TCB结构体成员pxTopOfStack表示当前堆栈的栈顶,它指向最后一个入栈的项目,它会不断变化。而TCB结构体成员pxStack表示堆栈的起始位置,指向堆栈的最开始处,固定不变。
  • 对于不同的硬件架构,入栈的寄存器也不相同,这个函数是由移植层提供的。
2.4 函数prvAddNewTaskToReadyList()

该函数是将任务添加到任务就绪表中,供任务调度器调用。函数原型如下:

/********************************************************
参数:pxNewTCB:新建任务的TCB指针 
返回:无
*********************************************************/
static void prvAddNewTaskToReadyList(TCB_t *pxNewTCB  )

函数代码如下:

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
    /* 开启临界区保护, 关闭中断(平台相关,移植层实现)*/
    taskENTER_CRITICAL();
    {
        uxCurrentNumberOfTasks++;	 /* 当前总任务数加1 */
        
        if( pxCurrentTCB == NULL )
        {
            pxCurrentTCB = pxNewTCB;	/* 如当前没有运行态任务,设置新任务为运行态 */

			/* 如果当前任务为新创建的第一个任务 */
            if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
            {
                prvInitialiseTaskLists();	/* 初始化各个列表 */
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
        	/* 如果调度器没有运行 */
            if( xSchedulerRunning == pdFALSE )
            {
                /* 新任务优先级比当前运行的任务优先级更高 */
                if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    pxCurrentTCB = pxNewTCB;	/* 设置新任务为当前运行任务 */
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        
        uxTaskNumber++;	/* 记录创建任务数*/

		/* 如果使用调试追踪功能 */
        #if ( configUSE_TRACE_FACILITY == 1 )
        {
            pxNewTCB->uxTCBNumber = uxTaskNumber;
        }
        #endif /* configUSE_TRACE_FACILITY */
        
        traceTASK_CREATE( pxNewTCB );

        /* 将任务插入就绪列表中 */
        prvAddTaskToReadyList( pxNewTCB );

		/* 使用宏定义实现,标记任务就绪表中相关位 */
        portSETUP_TCB( pxNewTCB );
    }
    taskEXIT_CRITICAL();	    /* 退出边界,恢复中断 */

	/* 如果调度器已经启动 */
    if( xSchedulerRunning != pdFALSE )
    {
        /* 新任务优先级比正在运行的任务高 */
        if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
        {
            taskYIELD_IF_USING_PREEMPTION();	/* 系统执行任务切换 */
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
2.5 函数prvInitialiseTaskLists()

创建第一个任务时执行该函数,主要功能是初始化所有的内核列表
函数代码如下:

static void prvInitialiseTaskLists( void )
{
	UBaseType_t uxPriority;

	/* 初始化所有的就绪列表 */
	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 */

	/* 延时列表相关操作 */
	pxDelayedTaskList = &xDelayedTaskList1;
	pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
2.6 函数prvAddTaskToReadyList()

该函数是宏定义,主要功能是标记任务就绪表中相关位,uxTopReadyPriority是32位的数据,每一位表示一个优先级是否存在任务函数,如:第8位置1,表示优先级为8的就绪表中存在任务。同时,该函数也将任务的状态列表项插入到对应的任务就绪表末尾项中,如下:

#define prvAddTaskToReadyList( pxTCB )	\
	traceMOVED_TASK_TO_READY_STATE( pxTCB );	\
	taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );\
	vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );\
	tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

其中

/* 记任务就绪表中相关位 */
#define taskRECORD_READY_PRIORITY( uxPriority )	portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )

3. 任务的静态创建

3.1 任务静态创建任务xTaskCreateStatic()

使用静态方式创建任务时需要使将宏 configSUPPORT_STATIC_ALLOCATION设置为 1,即使用静态内存。

静态创建任务函数原型如下:

TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode,
									const char * const pcName,
									const uint32_t ulStackDepth,
									void * const pvParameters,
									UBaseType_t uxPriority,
									StackType_t * const puxStackBuffer,
									StaticTask_t * const pxTaskBuffer ) 

动态创建任务函数原型如下:

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
						const char * const pcName,
						const uint16_t usStackDepth,
						void * const pvParameters,
						UBaseType_t uxPriority,
						TaskHandle_t * const pxCreatedTask )

说明:

  • 静态方式创建任务和动态方式相比,多出两个参数,分别为:
    puxStackBuffer:指向任务栈空间,需要用户自己定义。
    pxTaskBuffer:指向任务控制块空间,需要用户自己定义。
  • 动态创建任务函数,句柄作为参数代入函数,静态创建函数句柄作为返回值。
  • 在静态创建函数中,中pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB。标记使用静态方式分配栈空间和任务控制块空间,后面删除任务时,需要用户释放对应的内存空间。
  • 使用静态方式创建任务时,需要给空闲任务和定时器的任务堆栈和任务控制块分配内存。
  • 除了以上区别外,其它方面的原理跟动态创建任务相似。

使用静态方式创建任务时,需要给空闲任务和定时器的任务堆栈和任务控制块分配内存,使用vApplicationGetIdleTaskMemory()和 ApplicationGetTimerTaskMemory()进行实现,如下:

#define configMINIMAL_STACK_SIZE		((unsigned short)130)   		/* 空闲任务使用的堆栈大小 */
#define configTIMER_TASK_STACK_DEPTH	(configMINIMAL_STACK_SIZE*2)	/* 软件定时器任务堆栈大小 */

/* 空闲任务 */
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
static StaticTask_t IdleTaskTCB;

/* 定时器任务 */
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
static StaticTask_t TimerTaskTCB;

/* 空闲任务所需内存 */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, 
									StackType_t **ppxIdleTaskStackBuffer, 
									uint32_t *pulIdleTaskStackSize )
{
	*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
	*ppxIdleTaskStackBuffer=IdleTaskStack;
	*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

/* 定时器任务所需内存 */
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, 
									 StackType_t **ppxTimerTaskStackBuffer, 
									 uint32_t *pulTimerTaskStackSize )
{
	*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
	*ppxTimerTaskStackBuffer=TimerTaskStack;
	*pulTimerTaskStackSize=configMINIMAL_STACK_SIZE;
}

参考资料:

【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》
【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》
【3】: 《Cortex M3权威指南(中文)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值