FreeRTOS原理剖析:任务的创建

2023-05-16

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权威指南(中文)》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS原理剖析:任务的创建 的相关文章

随机推荐

  • jsp中使用split函数

    jsp中使用split函数 xff0c 实现字符串劈开 截取 解决方法 xff1a 1 jsp页面头部引入 lt 64 taglib uri 61 34 http java sun com jsp jstl functions 34 pre
  • 可视化工具D3教程

    可视化工具D3教程 第1章 D3简介第2章 第一个程序 Hello World第3章 正式进入D3的世界第4章 选择 插入 删除元素第5章 做一个简单的图表第6章 比例尺的使用第7章 坐标轴第8章 完整的柱形图第9章 让图表动起来第10章
  • 鸿蒙OS Hi3861的芯片实际开发中遇到的问题

    鸿蒙OS Hi3861的芯片实际开发中遇到的问题 优点 比较适合开发者快速开发 xff0c 上面的硬件操作的函数封装很简单 xff0c 非常适合初学者使用封装好的函数调用硬件管脚 xff0c 比如I2c gpio spi sdio 都被封装
  • private和public

    一 结构体 类的定义和实现分离 xff0c 代码可读性更强 xff08 非必须 xff09 span class token comment xxx h头文件 span span class token keyword struct spa
  • Mac下配置adb命令到系统变量与其后基本的adb操作

    1 进入terminal并输入echo HOME 2 命令行输入touch bash profile 注意 xff0c touch后面有一个空格 xff0c 为了在访达下的个人文件夹下面创建一个 bash profile文件 xff0c 类
  • Ubuntu 16.04 安装samba服务

    安装samba服务 sudo apt get install samba samba common 创建用于共享的目录 sudo mkdir span class token operator span home long share 给共
  • Cortex M4 SVC 中断

    Cortex SVC 中断 SVC 系统管理调用 异常优先级为1 xff0c SVC异常是由SVC指令触发 也可以直接设置NVIC寄存器触发异常 应用程序工作在非特权访问模式 xff0c 当需要特权模式访问系统资源时 xff0c 可以使用S
  • FreeRTOS --(4)任务管理之启动调度器

    目录 1 vTaskStartScheduler 2 xPortStartScheduler 3 vPortSetupTimerInterrupt 4 prvStartFirstTask 5 vPortSVCHandler 在使用 Free
  • FreeRTOS --(7)任务管理之任务切换

    现在创建任务 xff08 xTaskCreate xff09 启动调度器 xff08 vTaskStartScheduler xff09 xff0c 任务控制 xTaskDelay xff0c 以及Tick 中断 xff08 xPortSy
  • Spring Aop到底有什么用处?

    假如没有aop xff0c 在做日志处理的时候 xff0c 我们会在每个方法中添加日志处理 xff0c 比如 但大多数的日子处理代码是相同的 xff0c 为了实现代码复用 xff0c 我们可能把日志处理抽离成一个新的方法 但是这样我们仍然必
  • 获取对象自身的属性

    问题 找出对象 obj 不在原型链上的属性 注意这题测试例子的冒号后面也有一个空格 1 返回数组 xff0c 格式为 key value 2 结果数组不要求顺序 答案 Object keys 方法 xff08 156 ms xff09 返回
  • 重启linux后无法ssh登录

    博客搬运自我的个人博客 chantAria的博客 精力有限 新博客我会同步到CSDN 但博客内容的更新只会出现在个人博客 欢迎大家来玩耍哦 重启llinux后无法ssh登录的情况很多 其中一种情况是在之前update的时候产生了一个坏的内核
  • 使用FFmpeg将视频编码格式转化为H264编码

    背景介绍 xff1a web开发中涉及到视频播放的前端一般会使用h5的video标签对后端提供的视频文件url直接加载进行播放 xff0c 虽然视频文件后缀都是mp4 xff0c 但并非所有视频文件的编码格式都支持用video播放 xff0
  • Cause: org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 26; 1 字节的 UTF-8 序列的字节 1 无效。

    报错为 xff1a Cause org xml sax SAXParseException lineNumber 5 columnNumber 26 1 字节的 UTF 8 序列的字节 1 无效 是你的xml文件里面的 设置成了默认的UTF
  • Centos7 java服务开机自启动

    1 在 etc systemd system 目录下 新建脚本 cd span class token operator span usr span class token operator span lib span class toke
  • 我使用过的linux命令之strings

    strings命令用于输出文件中可打印的字符串 不论文件是普通文本 xff0c 还是可执行文件 xff0c 任何文件都可以 最常用的选项 xff1a a 扫描整个文件的任何段 xff0c 这是strings的默认行为 xff0c 但是这种默
  • HashMap的工作原理

    HashMap主要是用来处理键值对数据 xff0c 随着JDK版本的更新 xff0c JDK1 8对HashMap的底层也做了一些优化 xff0c HashMap是基于哈希表对Map接口的实现类 xff0c 它的特点是访问数据速度快 xff
  • 如何配置终端代理apt 代理

    1 临时用代理 xff0c 直接在终端里export代理 export http proxy 61 http 127 0 0 1 7890 export https proxy 61 http 127 0 0 1 7890 2 在 etc
  • ssh修改连接端口,以及修改端口之后连接不上的问题

    SSh服务配置文件路径一般都是在 etc ssh这个目录下面 sshd config 这个文件 使用VI vim编辑器 xff0c 打开sshd config这个文件 xff0c 搜索找到 port字段 去掉 xff0c 修改port 后面
  • FreeRTOS原理剖析:任务的创建

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