freertos之task浅析

前言

rtos调度的基本单位是task(任务),其重要性不言而喻,一般都会包括任务的创建,删除,阻塞,挂起,回复等等操作。当然,freertos也不例外。
一般一个task包含三个基础部分TCB结构、stack结构、任务代码。
下面就从这几方面来讲一讲



task有关的数据结构


TCB结构体

typedef struct tskTaskControlBlock
{
	volatile StackType_t	*pxTopOfStack;	//任务栈顶指针PSP,在任务切换的时候至关重要,通过TCB结构的栈顶指针来找到具体任务代码
	ListItem_t			xStateListItem;	//任务状态列表项
	ListItem_t			xEventListItem;	//任务事件列表项
	UBaseType_t			uxPriority;		//任务优先级
	StackType_t			*pxStack;		//任务栈底
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];	//任务名称
	#if ( configUSE_TRACE_FACILITY == 1 )	//启用任务跟踪功能
		UBaseType_t		uxTCBNumber;		
		UBaseType_t		uxTaskNumber;
	#endif

	#if ( configUSE_MUTEXES == 1 )			//使能mutex功能
		UBaseType_t		uxBasePriority;	
		UBaseType_t		uxMutexesHeld;
	#endif

	#if( configUSE_TASK_NOTIFICATIONS == 1 )	//使能任务通知功能
		volatile uint32_t ulNotifiedValue;
		volatile uint8_t ucNotifyState;
	#endif

	#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )	//判断TCb结构的内存能使用方式
		uint8_t	ucStaticallyAllocated; 		
	#endif

	#if( INCLUDE_xTaskAbortDelay == 1 )	//使用任务放弃延时功能
		uint8_t ucDelayAborted;
	#endif
} tskTCB;
typedef tskTCB TCB_t;

一些跟任务相关的全局变量

PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;	//指向当前任务TCB结构体的指针
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];	//列表数组表示已经就绪的任务,因为每一个优先级都有一个就绪列表,所以是个列表数组;
PRIVILEGED_DATA static List_t xDelayedTaskList1;	//延时任务列表1				
PRIVILEGED_DATA static List_t xDelayedTaskList2;	//延时任务列表2	,使用两个延时任务列表的原因主要是解决tick溢出问题的,下面代码具体分析				
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;	//因为两个延时任务列表,这个指针是指向当前被使用的那个列表的	
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;	//这个指针是指向溢出的那个延时列表的		
/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;	//当调度器上锁之后,就绪的task就会加入到此列表中来,在调度器恢复之后,就会转到就绪态中去			
#if( INCLUDE_vTaskDelete == 1 )
	PRIVILEGED_DATA static List_t xTasksWaitingTermination;	//在任务删除自己的时候由于不能立刻释放自己TCB 和stack所占用的内存(前提是动态分配,而且是任务自己删除自己),就会将自己添加到这个列表中去,等待空闲任务来遍历这个列表来释放动态ram
	PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;	//这个如果为01的话,空闲任务就会忽略去遍历上面这个列表,只有部位0的情况下才会去释放动态内存。
#endif

#if ( INCLUDE_vTaskSuspend == 1 )
	PRIVILEGED_DATA static List_t xSuspendedTaskList;			//被阻塞的任务列表
#endif

PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks 	= ( UBaseType_t ) 0U;	//当前任务总数
PRIVILEGED_DATA static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;	//当前tick时间
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority 		= tskIDLE_PRIORITY;	//空闲任务优先级
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning 		= pdFALSE;	//调度器运行状态
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks 			= ( UBaseType_t ) 0U;	//suspend期间发生的tick数
PRIVILEGED_DATA static volatile BaseType_t xYieldPending 			= pdFALSE;	//
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows 			= ( BaseType_t ) 0;	
PRIVILEGED_DATA static UBaseType_t uxTaskNumber 					= ( UBaseType_t ) 0U;	
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U; 	
/* Initialised to portMAX_DELAY before the scheduler starts. */
PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle					= NULL;			
/*< Holds the handle of the idle task.  The idle task is created


task相关代码分析


任务创建

rtos中资源使用调度的最小单位一般就是task了,先看任务创建代码。分为两种,一种是静态static创建task(任务stack,tcb都是提前静态申请好),另一种是动态创建(任务stack,tcb都是创建的时候申请),其中tcb结构中的ucStaticallyAllocated元素就是记录此tcb是用那种方式创建的。看代码

/*这是静态任务创建方式,在创建任务之前需要将tcb、stack创建好,地址作为参数传入,里面主要是
对pxNewTCB、pxStack 赋值,然后调用prvInitialiseNewTask初始化TCB结构,
最后调用prvAddNewTaskToReadyList将task加入到readylist中去*/
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;
	if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) ){
		pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */
		pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;
		#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){
			pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
		}
		#endif 

		prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL );
		prvAddNewTaskToReadyList( pxNewTCB );
	}else{
		xReturn = NULL;
	}
	return xReturn;
}

/*这是动态任务创建方式,里面先动态分配tcb、stack结构的ram,
然后调用prvInitialiseNewTask初始化TCB结构,最后调用prvAddNewTaskToReadyList将task加入到readylist中去*/
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;

	/* 如果栈是向下生长的,那先分配stack,然后分配TCB,
	这样就算stack溢出,也不会将TCb结构体覆盖掉 。。。这个地方代码实现我认为是有问题的*/
	/* 给TCb分配内存,内存来源取决于malloc function  的实现*/
	pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
	if( pxNewTCB != NULL ){
		pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
		if( pxNewTCB->pxStack == NULL ){
			vPortFree( pxNewTCB );
			pxNewTCB = NULL;
		}
	}
	if( pxNewTCB != NULL ){
		prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, 
							pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
		prvAddNewTaskToReadyList( pxNewTCB );
		xReturn = pdPASS;
	}else{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}

	return xReturn;
}

其中都调用了prvInitialiseNewTaskprvAddNewTaskToReadyList,下面来看这两个函数源码

/*这个函数主要是存储task名称,设置task的优先级,在设置task的tcb的xStateListItem、xEventListItem两个列表项
然后初始化任务栈(涉及到任务切换,硬件架构),最后将创建的task得到句柄传递出来*/
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;

	/* 计算栈顶地址;这取决于栈的生长方向. */
	#if( portSTACK_GROWTH < 0 ){
		pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
		pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 
		/* 检查字节对齐操作,一般情况下都是要做4字节或者8字节对齐的*/
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
	}
	#endif /* portSTACK_GROWTH */

	/* 存储task名字 */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];
		/*不要复制整个字符串,防止字符串之后的ram无法访问,可能会造成错误*/
		if( pcName[ x ] == 0x00 ){
			break;
		}else{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	/* 保证任务名字有结尾。万一任务名字跟名字数组一样长  或者 比名字数组更长 */
	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 ){//使用mutex的话,额外需要记录的数据
		pxNewTCB->uxBasePriority = uxPriority;
		pxNewTCB->uxMutexesHeld = 0;
	}
	#endif /* configUSE_MUTEXES */

	/*初始化tcb的xStateListItem 和 xEventListItem列表项 以便下面将他们插入到对应的list中去*/
	vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

	/* 设置列表项的owner */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

	/* 事件列表一般是按照优先级排列的*/
	listSET_LIST_ITEM_VALUE( &( pxNewTCB->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( &( pxNewTCB->xEventListItem ), pxNewTCB );

	/*初始化TCB堆栈,使其看起来好像任务已经在运行,但是被调度程序打断了。
	返回地址已设置到任务函数的开头。一旦堆栈被初始化堆栈变量的顶部被更新。 
	这部分跟任务切换密切相关,之后应该还有一篇系列博客来分析*/
	pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

	if( ( void * ) pxCreatedTask != NULL ){
		/* 匿名传递任务句柄,通过这个句柄(其实就是指向tcb的指针)可以对任务进行一些了操作*/
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}else{
		mtCOVERAGE_TEST_MARKER();
	}
}

/*这个函数将NEW的task加入到就绪列表中去分为好几种情况
1.如果现在没有任何任务运行,那么这个任务机会被立即运行(其实也就是pxCurrentTCB 指向NEW的tcb),需要prvInitialiseTaskLists去初始化各个全局列表变量
2.如果这不是系统中第一个任务,但是调度器还没运行,就看pxCurrentTCB 和 pxNewTCB的优先级,如果pxNewTCB优先级高的话,就会替代原来的TCB
3.如果不是系统中第一个任务,系统调度器已经开始运行了的话,那就看现在运行的task的pxCurrentTCB 和 pxNewTCB 的优先级那个高,如果pxNewTCB的优先级高,那么就会直接导致任务切换,注意case1 \ 2 不会直接导致任务切换,仅仅是pxCurrentTCB 指针的指向发生变化而已。

这里不管是case几,都会调用prvAddTaskToReadyList将task加入到readylist中去*/
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
	/* 确保在更新任务列表时中断不会访问任务列表。 */
	taskENTER_CRITICAL();{
		uxCurrentNumberOfTasks++;
		if( pxCurrentTCB == NULL ){
		/* 没有其他task,或者其他task都是处于暂停、挂起态的时候,直接 让这个NEW任务去运行*/
			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++;
		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();
	}
}

prvAddNewTaskToReadyList又涉及到prvAddTaskToReadyList这个宏,如下
先是更新已经就绪的最高优先级任务,然后将此task插入到对应优先级列表的readylist的列表尾部,主要是实现时间片轮转调度

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


任务删除

有任务创建就有任务删除,不仅动态任务可以删除,静态任务也可以删除,动态任务删除的更彻底,连TCB、stack都会删掉,静态任务在删除的时候由于TCB、stack都是静态分配的无法删除。
下面看源码

/*任务删除函数,先将两个列表项从可能挂接的列表上移除,然后分两种情况
1.删除的任务是本身的话,删除TCB、stack的操作需要在空闲任务中去完成
2.删除的是别的任务的话,prvDeleteTCB就会将任务tcb、stack删除
最后如果是删除任务本身的话,由于本身已经被删除了,需要立即进行任务切换。*/
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;

	taskENTER_CRITICAL();{
		/* 如果传入的xTaskToDelete是NULL,那就是删除当前任务,pxTCB = pxCurrentTCB,
		否则 pxTCB = xTaskToDelete*/
		pxTCB = prvGetTCBFromHandle( xTaskToDelete );

		/* 由于任务即将被删除了,需要将其从任务状态列表中删除,如果删除
		这个任务之后,这个列表中的列表项个数为0了,那就看你需要修改已经就绪的优先级了 */
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );

		/* 由于任务即将被删除了,也需要将在其他等待列表中的列表项移除了(前提是有挂在别的事件列表上) */
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );

		/* 增加uxTaskNumber,这样内核感知调试器也可以检测任务列表是否需要重新生成。
		这是之前做过的portPRE_TASK_DELETE_HOOK()与宏将在Windows端口中使用的方法相同
		没有回来。*/
		uxTaskNumber++;//其实这个操作我也没搞懂。。。。。

		if( pxTCB == pxCurrentTCB ){//任务删除自己
		/* 一个任务正在删除它自己。这不能在任务本身中完成,作为上下文切换到另一个任务是必需的。
		将任务放在终止列表xTasksWaitingTermination中。
		空闲任务将检查终止列表并释放所分配的任何内存已删除任务的TCB和堆栈的调度器。*/
			vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

		/*增加uxDeletedTasksWaitingCleanUp变量,以便空闲任务知道有一个任务已经被删除,
		因此它应该被删除检查xTasksWaitingTermination列表。这在前面讲全局变量的时候提到过*/
			++uxDeletedTasksWaitingCleanUp;

		/*预删除钩子主要用于Windows模拟器,在其中执行Windows特定的清理操作,
		在此之后,就不可能放弃这项任务了因此,xYieldPending用于锁定上下文切换必需的。*/
			portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
		}else{//一个任务删除另一个任务
			--uxCurrentNumberOfTasks;	//全局变量减少,
			prvDeleteTCB( pxTCB );		//真正删除TCB、Stack的函数,会根据是否动态分配来决定
			/* 重置下一个预期的解锁时间,刚刚删除的任务就是下一个解锁的task。*/
			prvResetNextTaskUnblockTime();
		}
	}
	taskEXIT_CRITICAL();
	/* Force a reschedule if it is the currently running task that has just been deleted. */
	if( xSchedulerRunning != pdFALSE ){//如果调度器在运行 且 删除的任务是当前任务
		if( pxTCB == pxCurrentTCB )
			portYIELD_WITHIN_API();		//那么久了进行任务切换,因为pxCurrentTCB将要被删除了
	}
}

其中调用了prvDeleteTCBprvResetNextTaskUnblockTime,来看源码

/*实际删除tcb、stack的函数。需要根据ram的申请方式来决定删除操作,其实很简单。。。*/
static void prvDeleteTCB( TCB_t *pxTCB ){
	#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) ){
		/* The task can only have been allocated dynamically - free both
		the stack and TCB. */
		vPortFree( pxTCB->pxStack );
		vPortFree( pxTCB );
	}
	#elif( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE == 1 ){
		/* The task could have been allocated statically or dynamically, so
		check what was statically allocated before trying to free the
		memory. */
		if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB ){
			/* Both the stack and TCB were allocated dynamically, so both
			must be freed. */
			vPortFree( pxTCB->pxStack );
			vPortFree( pxTCB );
		}else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY ){
			/* Only the stack was statically allocated, so the TCB is theonly memory that must be freed. */
			vPortFree( pxTCB );
		}else{
			/* Neither the stack nor the TCB were allocated dynamically, so
			nothing needs to be freed. */
			configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB	)
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}

/*主要是更新预期unblock tine,不然每次tick都去尝试去查delaylist看有没有任务时间到期很麻烦,占时间*/
static void prvResetNextTaskUnblockTime( void ){
TCB_t *pxTCB;
	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ){
		/* 新的当前延迟列表为空。xNextTaskUnblockTime设置为的最大可能值,所以它是非常不可能的
		if(xTickCount >= xNextTaskUnblockTime)测试将通过,直到延迟列表中有一项。*/
		xNextTaskUnblockTime = portMAX_DELAY;
	}else{
		/* 当前新延迟列表不为空,获取的值为在延迟列表顶部的项目。这是时间
		应该删除延迟列表顶部的哪个任务从阻塞状态。都是列表操作*/
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}


任务延迟

在裸机编程中,我们一般延时是让CPU空转的,这样使得资源极大的浪费,在OS中基本上决不允许这种情况的出现,所以OS提供了两个函数来供用户在OS下使用,vTaskDelayUntilvTaskDelay。两个函数有轻微的区别,详细分析见代码

/*只需要传入需要delay的tick数即可,将本task加入delaylist阻塞xTicksToDelay个tick*/
void vTaskDelay( const TickType_t xTicksToDelay ){
BaseType_t xAlreadyYielded = pdFALSE;
	/* 延迟tick为0的话,只会强制重新调度*/
	if( xTicksToDelay > ( TickType_t ) 0U ){
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll();{
			traceTASK_DELAY();
			/*从事件列表中删除的任务调度器被挂起将不会被放置在就绪状态
			列表或从阻塞列表中删除,直到调度程序已恢复。
			此任务不能在事件列表中,因为它是当前的执行任务。*/
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
		}
		xAlreadyYielded = xTaskResumeAll();
	}
	/* 如果xTaskResumeAll 没有完成,那就进行一次强制调度,来使当前任务slepp */
	if( xAlreadyYielded == pdFALSE )
		portYIELD_WITHIN_API();
}

/*传入上一次的唤醒时间pxPreviousWakeTime,和需要延时的xTimeIncrement 。达到精确延时,将在延时之前的一些函数执行时间也算进去了,所以可以很精确的延时,比如以特定频率执行某一个任务就可以使用vTaskDelayUntil */
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

	vTaskSuspendAll();{//阻塞所有任务
		const TickType_t xConstTickCount = xTickCount;

		/* 计算任务想唤醒的那个tick */
		xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

		if( xConstTickCount < *pxPreviousWakeTime )
		{//如果现在tick已经比上次唤醒的tick小了,说明肯定已经溢出过了
			/* 这部分没看懂,之后补充 */
			if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
			{
				xShouldDelay = pdTRUE;
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}else{
			/* tick还没有溢出过。这样的话,不论是否唤醒时间是否溢出、或者说tick比wake time小 ,
			我们都会进行延时delay*/
			if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ){
				xShouldDelay = pdTRUE;
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* 更新唤醒时间,方便下一次调用 */
		*pxPreviousWakeTime = xTimeToWake;

		if( xShouldDelay != pdFALSE )
			/* prvAddCurrentTaskToDelayedList() 需要的不是唤醒得到时间,而是阻塞的时间,减去当前tick */
			prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
	}
	xAlreadyYielded = xTaskResumeAll();//恢复所有任务调度

	/* 如果xTaskResumeAll 没有完成,那就进行一次强制调度,来使当前任务slepp*/
	if( xAlreadyYielded == pdFALSE )
		portYIELD_WITHIN_API();
}


任务挂起

这部分代码主要包括任务挂起,任务解挂等

void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t *pxTCB;

	taskENTER_CRITICAL();{
		/* 如果传入的是空指针,那就挂起自己*/
		pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
		/* 将此任务从ready delayed的list中移除,方便后续加入到xSuspendedTaskList中去*/
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		/* 此任务是否也在等待什么事件,如果是,就从事件等待列表中移除 */
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}
		/*将此task加入到xSuspendedTaskList中去。(按照优先级排列)*/
		vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
	}
	taskEXIT_CRITICAL();

	if( xSchedulerRunning != pdFALSE ){
	/* 重置xNextTaskUnblockTime值,因为此被挂起的任务跟可能与xNextTaskUnblockTime的值有关系(这个函数之前已经分析过)*/
		taskENTER_CRITICAL();{
			prvResetNextTaskUnblockTime();
		}
		taskEXIT_CRITICAL();
	}

	if( pxTCB == pxCurrentTCB ){//如果阻塞的当前任务自身
		if( xSchedulerRunning != pdFALSE )
		{//且调度器在运行的话,那么立刻出发一次任务切换
			/* The current task has just been suspended. */
			configASSERT( uxSchedulerSuspended == 0 );
			portYIELD_WITHIN_API();
		}else{//如果调度器没有在运行的话。
			/* The scheduler is not running, but the task that was pointed
			to by pxCurrentTCB has just been suspended and pxCurrentTCB
			must be adjusted to point to a different task. */
			if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
			{//看看挂起队列中的任务数,如果等于当前所有任务数的话,说明系统中已经没有任务可运行了
			//pxCurrentTCB置为NULL。反正调度器没有运行
				pxCurrentTCB = NULL;
			}else{//不然的话。。我也还没看懂
				vTaskSwitchContext();
			}
		}
	}
}

void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;
	/* 很容易理解为什么参数不能是NULL,pxCurrentTCB */
	if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ){
		taskENTER_CRITICAL();{
			if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
			{//检查pxTCB是不是真的是挂起态
				/* 这段代码在临界区,直接操作相关列表,从 xSuspendedTaskList上
				将此task移除,并加入到readylist中*/
				( void ) uxListRemove(  &( pxTCB->xStateListItem ) );
				prvAddTaskToReadyList( pxTCB );
				/* 有可能pxTCB的优先级比pxCurrentTCB更高,这样的话就需要强制进行任务切换 */
				if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){
				
					taskYIELD_IF_USING_PREEMPTION();
				}
				
			}
		}
		taskEXIT_CRITICAL();
	}
}
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值