FreeRTOS内核源码解读之-------任务创建

任务创建函数--------xTaskCreate(动态方法)

    FreeRTOS中任务控制块详解 FreeRTOS任务创建和删除的动态和静态方法区别 FreeRTOS动态创建和删除任务 FreeRTOS静态创建

1、FreeRTOS中任务控制块详解

任务控制块是将每个任务的属性集中到一个结构体中,这个结构体叫做任务控制块:TCB_t。它就是一个任务的身份证,以后系统对任务的任何操作,都是通过这个任务控制块来实现的。具体代码:

typedef struct tskTaskControlBlock
{
   //任务栈顶指针,必须设置为任务控制块的第一个成员变量
   volatile StackType_t	*pxTopOfStack;	
   //	启用MPU的情况下,设置。MPU内存保护单元
   #if ( portUSING_MPU_WRAPPERS == 1 )
   	//设置任务访问的内存权限
   	xMPU_SETTINGS	xMPUSettings;		
   #endif
   //状态链表项,任务会处于不同的状态,该项会被插入到对应的链表,供链表引用	任务
   ListItem_t			xStateListItem;	 
   //事件链表项,
   ListItem_t			xEventListItem;		/*< Used to reference a task from an event list. */
   //任务优先级
   UBaseType_t			uxPriority;			
   //任务栈内存起始地址
   StackType_t			*pxStack;		
   //任务名字,字符串形式,一般用于调试	,configMAX_TASK_NAME_LEN表示任务名字最长值
   char				pcTaskName[ configMAX_TASK_NAME_LEN ];
   //栈生长方向,对于向上生长的栈,用于指明栈的上边界,用于判断是否溢出
   #if ( portSTACK_GROWTH > 0 )
   	StackType_t		*pxEndOfStack;		
   #endif
   //保存临界区嵌套深度
   #if ( portCRITICAL_NESTING_IN_TCB == 1 )
   	UBaseType_t		uxCriticalNesting;	
   #endif

   #if ( configUSE_TRACE_FACILITY == 1 )
   	//用于调试,表示本任务是第几个创建,每创建一个任务,系统有一个全局变量加1,并将该值赋给新任务
   	UBaseType_t		uxTCBNumber;		
   	//调试用,用户可以设定特定的数值
   	UBaseType_t		uxTaskNumber;		
   #endif

   #if ( configUSE_MUTEXES == 1 )
   	//
   	UBaseType_t		uxBasePriority;		
   	UBaseType_t		uxMutexesHeld;
   #endif

   #if ( configUSE_APPLICATION_TASK_TAG == 1 )
   	TaskHookFunction_t pxTaskTag;
   #endif

   #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
   	void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
   #endif

   #if( configGENERATE_RUN_TIME_STATS == 1 )
   	uint32_t		ulRunTimeCounter;	/*< Stores the amount of time the task has spent in the Running state. */
   #endif

   #if ( configUSE_NEWLIB_REENTRANT == 1 )
   	/* Allocate a Newlib reent structure that is specific to this task.
   	Note Newlib support has been included by popular demand, but is not
   	used by the FreeRTOS maintainers themselves.  FreeRTOS is not
   	responsible for resulting newlib operation.  User must be familiar with
   	newlib and must provide system-wide implementations of the necessary
   	stubs. Be warned that (at the time of writing) the current newlib design
   	implements a system-wide malloc() that must be provided with locks. */
   	struct	_reent xNewLib_reent;
   #endif

   #if( configUSE_TASK_NOTIFICATIONS == 1 )
   	volatile uint32_t ulNotifiedValue;
   	volatile uint8_t ucNotifyState;
   #endif

   /* See the comments above the definition of
   tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
   #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
   	uint8_t	ucStaticallyAllocated; 		/*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
   #endif

   #if( INCLUDE_xTaskAbortDelay == 1 )
   	uint8_t ucDelayAborted;
   #endif

} tskTCB;
typedef tskTCB TCB_t;

2、FreeRTOS任务创建和删除的动态和静态方法区别

在FreeRTOS实时操作系统中,提供了两种任务创建和删除函数的方法,一种是动态方式,对应的API函数是xTaskCreate和xTaskDelete;另一种是静态方式,对应的函数是xTaskCreateStatic和xTaskCreateRestricted函数。 两种方法的区别如下: <1>在使用上,使用动态方式创建时,每个任务的任务堆栈需要从FreeRTOS的堆中自动申请,这时,FreeRTOS必须提供内存管理文件,在FreeRTOS提供了5中内存管理方式,后续会进行剖析;如果使用静态方式创建和删除任务,需要用户自己为每个任务指定任务堆栈。 <2>还有需要提醒的是,如果我们使用动态方式创建的任务,必须使用动态方式删除任务;使用静态方式创建任务,必须使用静态方式删除任务。 注: 相关代码 对于选择静态还是动态方式创建和删除任务,在FreeRTOS.h配置文件中可以指定,代码如下:

#ifndef configSUPPORT_STATIC_ALLOCATION
	/* Defaults to 0 for backward compatibility. */
	#define configSUPPORT_STATIC_ALLOCATION 0
#endif
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
	/* Defaults to 1 for backward compatibility. */
	#define configSUPPORT_DYNAMIC_ALLOCATION 1
#endif

3、FreeRTOS动态创建和删除任务

    a、函数原型 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) { 参数: 1>pxTaskCode:具体创建任务函数名 2>pcName:任务名字。在调试跟踪打印信息时会出现,注意在给任务取名字时,要符合系统名字长度限制,具体可以查看FreeRTOSConfig.h文件中的配置,在这里,我设置的是:
#define configMAX_TASK_NAME_LEN                 16

3>usStackDepth:任务堆栈大小。申请此空间是用来保存任务切换时的任务信息,任务函数申请的变量。 4>pvParameters:传递给任务函数的参数。 5>uxPriority:任务优先级。这里优先级设置返回是0~configMAX_PRIORITIES-1。其中,configMAX_PRIORITIES设置同样可以通过FreeRTOSConfig.h文件中查看到,这里设置为:

#define configMAX_PRIORITIES                    4

6>pxCreatedTask:任务句柄。当任务创建成功之后会返回一个任务句柄,该参数是用来保存该任务句柄的,这个任务句柄就是任务的堆栈。在其他API函数对任务进行操作时,一般都会用到这个任务句柄。 注: 对上述参数类型具体解释

typedef void (*TaskFunction_t)( void * );     //定义一种返回值为空,参数为空指针的函数类型
typedef unsigned long UBaseType_t;
typedef void * TaskHandle_t;
    xTaskCreate函数解读

在这里需要说明的是:第一步,为防止堆栈增长到TCB,判断栈(portSTACK_GROWTH)的生长方向,如果向上增长,则先申请TCB,在申请堆栈;如果向下增长,则先申请堆栈,在申请TCB。对于FreeRTOS内存管理方面的内容,我会在后续写一篇文章,详细讲述。 动态函数创建流程图:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
          
   
	TCB_t *pxNewTCB;//任务控制块
	BaseType_t xReturn;

		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		//portSTACK_GROWTH  ( -1 ) 表示栈的生长方向,对于ARM CM4是向下增长的
		#if( portSTACK_GROWTH > 0 )     // if stack grows up
		{
          
   
			/* Allocate space for the TCB.  Where the memory comes from depends on
			the implementation of the port malloc function and whether or not static
			allocation is being used. */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
          
   
				/* Allocate space for the stack used by the task being created.
				The base of the stack memory stored in the TCB so the task can
				be deleted later if required. */
				pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

				if( pxNewTCB->pxStack == NULL )
				{
          
   
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */ // Stack grows DOWN on M4F
		{
          
   
		StackType_t *pxStack;

			/* 每个任务申请栈空间,这里注意参数usStackDepth 不代表的是字节数,而是字数,对于32位
			的系统,栈空间的字节数位usStackDepth *4字节
			#define portSTACK_TYPE	uint32_t
			typedef portSTACK_TYPE StackType_t;
			*/
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

			if( pxStack != NULL )//栈空间申请成功
			{
          
   
				/* 为任务申请任务控制块 */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 

				if( pxNewTCB != NULL )
				{
          
   
					/* 任务控制块申请成功,初始化任务控制块中的成员变量任务堆栈指针为
					前面申请的任务堆栈地址 */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
          
   
					/* 任务控制块申请失败,释放之前申请的任务堆栈 */
					vPortFree( pxStack );
				}
			}
			else
			{
          
   
				/* 任务栈空间申请失败,将任务控制块句柄设置为空 */
				pxNewTCB = NULL;
			}     
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
          
   
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
          
   
				/* 表明任务控制块和任务栈占用的是heap还是stack,提供给任务会收时判断 */
				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;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
    a、函数原型 void vTaskDelete( TaskHandle_t xTaskToDelete ) 参数: 1>xTaskToDelete :xTaskCreate任务创建函数创建成功后返回的任务句柄,如果是删除自己,需要的参数为NULL。这里要注意:**如果下个任务的解锁,刚好是被删除的那个任务,那么变量NextTaskUnblockTime就不对了,所以要重新从延时列表中获取一下。它是从延时列表的头部来获取的任务TCB,也可以再次验证,延时任务列表是按延时时间排序的。如果延时列表是空的,直接给默认值MAX_DELAY赋给NextTaskUnblockTime. ** vTaskDelete函数解读
#if ( INCLUDE_vTaskDelete == 1 )

	void vTaskDelete( TaskHandle_t xTaskToDelete )
	{
	TCB_t *pxTCB;

		taskENTER_CRITICAL();//关闭中断,临界区代码保护
		{
			/* 传递参数为NULL,表示某个任务要删除自己 */
			pxTCB = prvGetTCBFromHandle( xTaskToDelete );//通过任务句柄求出任务控制块,在FreeRTOS中任务句柄就是任务控制块的地址

			/* 将任务移除就绪列表 */
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{//状态列表项为0
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );//判断任务同等优先级的任务是否都删除,如果都删除,该优先级对应的位设置为0.这里主要考虑的是FreeRTOS支持任务优先级相同的时间片轮转调度的问题
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* 该任务是否等待某一事件,则从事件列表中移除 */
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			uxTaskNumber++;//主要用于可视化跟踪调试,如果configUSE_TRACE_FACILITY没有开启,uxTaskNumber不起任何作用
	        
			if( pxTCB == pxCurrentTCB )//当前任务删除自己
			{
				/* 任务不能够删除自己,需要进行上下文切换,进入另一个任务。将任务从相应的工作链表中去下,放入xTasksWaitingTermination链表中,相应的在空闲任务中会删除该链表中的任务 */
				vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

				/* 记录空闲任务需要删除的 xTasksWaitingTermination列表项*/
				++uxDeletedTasksWaitingCleanUp;

				/* 删除任务钩子函数,用户看可以自定义该钩子函数的内容 */
				portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
			}
			else//如果删除的任务不是自己
			{
				--uxCurrentNumberOfTasks;//任务数减1
				prvDeleteTCB( pxTCB );//删除任务

				/* 重新计算下一个任务解除阻塞的时间,也就是下一个任务需要开始运行的时间 */
				prvResetNextTaskUnblockTime();
			}

			traceTASK_DELETE( pxTCB );//用户自己定义,用于调试追踪
		}
		taskEXIT_CRITICAL();//退出临界代码保护

		/* 判断调度器是否正在运行. */
		if( xSchedulerRunning != pdFALSE )//xSchedulerRunning用来指示调度器是否正在运行
		{
			if( pxTCB == pxCurrentTCB )//删除的是任务自己,由于是删除自己,因此需要进行一次任务切换
			{
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}

#endif /* INCLUDE_vTaskDelete */

4、FreeRTOS静态创建

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 ) 参数: 1>pxTaskCode:具体创建任务执行函数名 2>pcName:任务名字,主要用于调试 3>ulStackDepth:堆栈深度 4>pvParameters:传给任务执行函数的参数 5>uxPriority:任务优先级 6>puxStackBuffer:任务堆栈空间首地址 7>pxTaskBuffer:任务控制块首地址 需要说明的是:对于静态创建任务,用户需要自己声明任务堆栈和控制块,一般是申请一个全局的数组和TCB_t 的变量。

/*-----------------------------------------------------------*/

#if( 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 ) 
	{
          
   
	TCB_t *pxNewTCB;
	TaskHandle_t xReturn;

		configASSERT( puxStackBuffer != NULL );
		configASSERT( pxTaskBuffer != NULL );

		if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
		{
          
   
			
			pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*将pxTaskBuffer指针传过来的指针强制转换成任务控制块指针.pxTaskBuffer是用户自己申请的 */
			pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;//将puxStackBuffer强制转换成StackType_t *赋给任务控制块中指向任务堆栈的成员变量,这也是用户自己申请的

			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
          
   
				/* 标识这个任务控制块和栈内存时静态创建的,删除任务的时候, 系统不会做内存回收处理*/
				pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

			prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL );//新任务初始化函数
			prvAddNewTaskToReadyList( pxNewTCB );//将任务加入到就绪列表
		}
		else
		{
          
   
			xReturn = NULL;
		}

		return xReturn;
	}

#endif /* SUPPORT_STATIC_ALLOCATION */
经验分享 程序员 微信小程序 职场和发展