FreeRTOS_16章_任务管理_延时阻塞部分单独整理

简介:

本文是 [野火®]《FreeRTOS 内核实现与应用开发实战—基于STM32》 这本书第16章任务管理中延时阻塞部分单独整理。

延时阻塞主要变量:

滴答中断的类型定义(portmacro.h):

configUSE_16_BIT_TICKS 用于判断编译器位数

TickType_t 用于滴答中断计数器的类型

portMAX_DELAY 用于记录滴答中断计数的最大值

//阻塞延时的全局定义
//滴答中断计数的类型
#if( configUSE_16_BIT_TICKS == 1 )
	typedef uint16_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffff
#else
	typedef uint32_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif

任务延时阻塞实现的主要变量(tasks.c):

//阻塞延时的大表和一些操作的主要变量
滴答中断计数
PRIVILEGED_DATA static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;
???
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
存放任务阻塞表其中正常时间阻塞的表1和配合超时溢出的表2
PRIVILEGED_DATA static List_t xDelayedTaskList1;					
PRIVILEGED_DATA static List_t xDelayedTaskList2;		
///使用指针,来固定住名字,然后通过下面的变量互换来完成超时溢出
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;				
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;		

延时阻塞的主要逻辑:

​ freeRTOS的阻塞存了两个表xDelayedTaskList1和xDelayedTaskList2,表里面的TCB结点的xDelayedTaskList2是存放解锁的时间值。假如正常情况下任务TCB是挂载在xDelayedTaskList1表上,但是解锁时间【xTickCount(时基)+(延时时间)】总归有溢出的时候,那么溢出的挂载在xDelayedTaskList2上。那么当计数器xTickCount溢出的话,就把正常表切到xDelayedTaskList2上,溢出表切到xDelayedTaskList1上。

​ 然后用正常指针pxDelayedTaskList指向在计数范围内的正常表,pxOverflowDelayedTaskList指向溢出计数范围的溢出表。

​ 用两个表来解决溢出问题,用指针来解决内部变量切换和表示问题。

在这里插入图片描述

交换正常延时表和溢出延时表taskSWITCH_DELAYED_LISTS():

​ 因为是#define定义的函数,所以调用taskSWITCH_DELAYED_LISTS的时候,等于把整段话内嵌和内联函数类似。其中每行还需要加 ‘ \ ’ 否者会报错。

/**************************************************************************************************
* 定义函数:                                   taskSWITCH_DELAYED_LISTS
* 说明:  		交换正常延时表和溢出延时表
* 输入:  
*		   
* 关联变量: 	pxDelayedTaskList			当前计数(计时)指针
*			pxOverflowDelayedTaskList	溢出计数(计时)指针
*			xNumOfOverflows				记录溢出次数
* 返回:  
* 注释作者:  日南方  11/30/2020
**************************************************************************************************/
/* pxDelayedTaskList and pxOverflowDelayedTaskList are switched when the tick count overflows. */
#define taskSWITCH_DELAYED_LISTS()																	\
{																									\
	List_t *pxTemp;																					\
																									\
	/* 判断当前阻塞列表中已经没有任务了,因为要切到超时的列表中,本列表应该都已经运行完才是*/							\
	configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );										\
																									\
	/* 交换*/																							\
	pxTemp = pxDelayedTaskList;																		\
	pxDelayedTaskList = pxOverflowDelayedTaskList;													\
	pxOverflowDelayedTaskList = pxTemp;																\
	xNumOfOverflows++;																				\
																									\
	/* 更新阻塞解锁值	*/																		\	
	prvResetNextTaskUnblockTime();																	\
}

更新阻塞解锁值prvResetNextTaskUnblockTime():

/**************************************************************************************************
* 函数:                                   prvResetNextTaskUnblockTime
* 说明:    更新解锁时间
* 输入:  
*		   
* 关联变量: pxDelayedTaskList			正常阻塞时间表
*			xNextTaskUnblockTime	下一个解锁时间
* 返回:  
* 注释作者:  日南方  11/30/2020
**************************************************************************************************/
static void prvResetNextTaskUnblockTime( void )
{
	TCB_t *pxTCB;

	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
	{
		//当前阻塞正常表已经没有延时阻塞任务了,直接解锁延时拉满
		xNextTaskUnblockTime = portMAX_DELAY;
	}
	else
	{
		//当前阻塞正常表已经有延时阻塞任务,查到第一个任务的TCB,读取他的解锁时间
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}

阻塞延时任务解锁时机:

​ 一个是滴答计数器xTickCount溢出,切换一次。

​ 一个是滴答计数器xTickCount已经到了下一个任务解锁时间xNextTaskUnblockTime,切换一次。

/**************************************************************************************************
* 函数:                                   xTaskIncrementTick
* 说明:    任务(定时)(计数)
* 输入:  
*		   
* 关联变量: xTickCount 					滴答计数器
*			uxSchedulerSuspended	调度器是否被悬挂
*			xNextTaskUnblockTime 	下一个任务的解锁时间
*			*** ***
* 返回:  
* 注释作者:  日南方  11/30/2020
**************************************************************************************************/
BaseType_t xTaskIncrementTick( void )
{
	TCB_t * pxTCB;
	TickType_t xItemValue;
	BaseType_t xSwitchRequired = pdFALSE;
    
	//调度器没有被悬挂
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{
		//镜像优化,在这个函数中xConstTickCount的值都不会被改变
		//滴答时钟+1
		const TickType_t xConstTickCount = xTickCount + 1;
		xTickCount = xConstTickCount;

		if( xConstTickCount == ( TickType_t ) 0U )
		{
			//计数器溢出,交换阻塞延时的正常表和溢出表
			taskSWITCH_DELAYED_LISTS();
		}
		else
		{
			//代码覆盖测试
			mtCOVERAGE_TEST_MARKER();
		}

		//如果滴答计数器的时间大于下一个任务解锁时间
		if( xConstTickCount >= xNextTaskUnblockTime )
		{
			for( ;; )
			{
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
				{
					//如果正常表没有任务,直接阻塞时间拉满
					xNextTaskUnblockTime = portMAX_DELAY; 
					break;
				}
				else
				{
					//如果正常表有任务,获取第一个任务的TCB和解锁时间
               		//这个支路会循环两次,因为外层的for
					//第一次循环是解锁任务,添加的就绪表中
					//第二次循环是更新下一个任务的解锁时间
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
					if( xConstTickCount < xItemValue )
					{
                        //第二次循环
						//检测是否合法,并赋值
						xNextTaskUnblockTime = xItemValue;
						break;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
                    //第一次循环
					//任务脱离阻塞表
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					//任务脱离事假表
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
					{
						( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
					//任务添加就绪表
					prvAddTaskToReadyList( pxTCB );
					//省略一些代码
				}
			}
		}
	//省略一些代码
	return xSwitchRequired;
}


阻塞任务的相对延时函数vTaskDelay( ··· xTicksToDelay):

​ INCLUDE_vTaskDelay宏要置1才可以使用这个阻塞延时函数。

​ 阻塞任务的延时函数有个好处就是“让权等待”,可以节省CPU时间。将任务挂载到阻塞延时的列表上去后,任务就不会运行了,要等到时间才会被重新激活。比那种实时查询标志位的忙等方法节省了很多CPU的时间。

代码详解:

/**************************************************************************************************
* 函数:                                   vTaskDelay
* 说明:  
* 输入:  
*		   const TickType_t xTicksToDelay	输入的延时时间		
* 关联变量: 
* 返回:  
* 注释作者:  日南方  11/30/2020
**************************************************************************************************/
#if ( INCLUDE_vTaskDelay == 1 )

	void vTaskDelay( const TickType_t xTicksToDelay )
	{
		BaseType_t xAlreadyYielded = pdFALSE;

		// 延时时间要大于 0 个 tick,否则会进行强制切换任务 
		if( xTicksToDelay > ( TickType_t ) 0U )
		{
			//调度器没有被悬挂
			configASSERT( uxSchedulerSuspended == 0 );
			//调度器悬挂
			vTaskSuspendAll();
			{
				//调试追踪函数
				traceTASK_DELAY();
				//将任务添加到延时列表中
				prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
			}
			//调度器恢复
			xAlreadyYielded = xTaskResumeAll();
		}
		else
		{
			//代码覆盖函数
			mtCOVERAGE_TEST_MARKER();
		}

		if( xAlreadyYielded == pdFALSE )
		{
			//强制任务调度
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
#endif /* INCLUDE_vTaskDelay */

将任务添加到延时表中prvAddCurrentTaskToDelayedList(…xTicksToWait, …xCanBlockIndefinitely);

/**************************************************************************************************
* 函数:                                   prvAddCurrentTaskToDelayedList
* 说明:  	将任务添加到延时阻塞表中
* 输入:  
*		   TickType_t 		xTicksToWait					延时等待时间 = xTicksToWait*滴答单位时间
*		const BaseType_t 	xCanBlockIndefinitely			是否阻塞,一般是pdFALSE
* 关联变量: 
* 返回:  
* 注释作者:  日南方  11/30/2020
**************************************************************************************************/
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
	TickType_t xTimeToWake;
	const TickType_t xConstTickCount = xTickCount;

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
        //INCLUDE_xTaskAbortDelay的定义是为了BaseType_t xTaskAbortDelay( TaskHandle_t xTask );这个函数
        //这个函数是强制离开阻塞态,进入就绪态
		/* About to enter a delayed list, so ensure the ucDelayAborted flag is
		reset to pdFALSE so it can be detected as having been set to pdTRUE
		when the task leaves the Blocked state. */
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif


	//在将任务添加到阻止列表之前,从就绪列表中删除任务.
	//因为两个列表都使用相同的列表项,都使用xStateListltem节点。
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* The current task must be in a ready list, so there is no need to
		check, and the port reset macro can be called directly. */
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			//阻塞,挂起到悬挂表中
			/* Add the task to the suspended task list instead of a delayed task
			list to ensure it is not woken by a timing event.  It will block
			indefinitely. */
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			//不阻塞,挂起到延时表中
			/* Calculate the time at which the task should be woken if the event
			does not occur.  This may overflow but this doesn't matter, the
			kernel will manage it correctly. */
			//任务解锁时间
			xTimeToWake = xConstTickCount + xTicksToWait;
			/* The list item will be inserted in wake time order. */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			//溢出?
			if( xTimeToWake < xConstTickCount )
			{
				//溢出,添加到溢出延时表
				/* Wake time has overflowed.  Place this item in the overflow
				list. */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				//没溢出,添加到正常延时表
				/* The wake time has not overflowed, so the current block list
				is used. */
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				/* If the task entering the blocked state was placed at the
				head of the list of blocked tasks then xNextTaskUnblockTime
				needs to be updated too. */
				if( xTimeToWake < xNextTaskUnblockTime )
				{
					//如果任务解锁时间,比当前解锁小,更新解锁时间
					xNextTaskUnblockTime = xTimeToWake;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
    //不允许任务悬挂
	#else /* INCLUDE_vTaskSuspend */
	{
		/* Calculate the time at which the task should be woken if the event
		does not occur.  This may overflow but this doesn't matter, the kernel
		will manage it correctly. */
		xTimeToWake = xConstTickCount + xTicksToWait;

		/* The list item will be inserted in wake time order. */
		listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

		if( xTimeToWake < xConstTickCount )
		{
			/* Wake time has overflowed.  Place this item in the overflow list. */
			vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* The wake time has not overflowed, so the current block list is used. */
			vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

			/* If the task entering the blocked state was placed at the head of the
			list of blocked tasks then xNextTaskUnblockTime needs to be updated
			too. */
			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
		( void ) xCanBlockIndefinitely;
	}
	#endif /* INCLUDE_vTaskSuspend */
}
/**************************************************************************************************
* 函数:                                   xTaskIncrementTick
* 说明:    任务(定时)(计数)
* 输入:  
*		   
* 关联变量: xTickCount 					滴答计数器
*			uxSchedulerSuspended	调度器是否被悬挂
*			xNextTaskUnblockTime 	下一个任务的解锁时间
*			*** ***
* 返回:  
* 注释作者:  日南方  11/30/2020
**************************************************************************************************/
BaseType_t xTaskIncrementTick( void )
{
	TCB_t * pxTCB;
	TickType_t xItemValue;
	BaseType_t xSwitchRequired = pdFALSE;
    
	//调度器没有被悬挂
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{
		//镜像优化,在这个函数中xConstTickCount的值都不会被改变
		//滴答时钟+1
		const TickType_t xConstTickCount = xTickCount + 1;
		xTickCount = xConstTickCount;

		if( xConstTickCount == ( TickType_t ) 0U )
		{
			//计数器溢出,交换阻塞延时的正常表和溢出表
			taskSWITCH_DELAYED_LISTS();
		}
		else
		{
			//代码覆盖测试
			mtCOVERAGE_TEST_MARKER();
		}

		//如果滴答计数器的时间大于下一个任务解锁时间
		if( xConstTickCount >= xNextTaskUnblockTime )
		{
			for( ;; )
			{
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
				{
					//如果正常表没有任务,直接阻塞时间拉满
					xNextTaskUnblockTime = portMAX_DELAY; 
					break;
				}
				else
				{
					//如果正常表有任务,获取第一个任务的TCB和解锁时间
               		//这个支路会循环两次,因为外层的for
					//第一次循环是解锁任务,添加的就绪表中
					//第二次循环是更新下一个任务的解锁时间
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
					if( xConstTickCount < xItemValue )
					{
                        //第二次循环
						//检测是否合法,并赋值
						xNextTaskUnblockTime = xItemValue;
						break;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
                    //第一次循环
					//任务脱离阻塞表
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					//任务脱离事假表
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
					{
						( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
					//任务添加就绪表
					prvAddTaskToReadyList( pxTCB );
					//省略一些代码
				}
			}
		}
	//省略一些代码
	return xSwitchRequired;
}


阻塞任务的绝对延时函数vTaskDelayUntil()

在这里插入图片描述

代码详解:

/**************************************************************************************************
* 函数:                                   vTaskDelayUntil
* 说明:   绝对延时函数
* 输入:  
*		   TickType_t * 	const pxPreviousWakeTime		上次唤醒时间
*		const TickType_t 	xTimeIncrement					任务周期时间
* 关联变量: 
* 返回:  
* 注释作者:  日南方  12/1/2020
**************************************************************************************************/

#if ( INCLUDE_vTaskDelayUntil == 1 )
	void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
	{
		//本次要唤醒任务的时间点
		TickType_t xTimeToWake;
		BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

		configASSERT( pxPreviousWakeTime );
		configASSERT( ( xTimeIncrement > 0U ) );
		configASSERT( uxSchedulerSuspended == 0 );

		//悬挂任务
		vTaskSuspendAll();
		{
			/* Minor optimisation.  The tick count cannot change in this
			block. */
			//保障这个函数,的当前滴答时间不被改变
			const TickType_t xConstTickCount = xTickCount;
			//计算任务延时解锁时间
			/* Generate the tick time at which the task wants to wake. */
			xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

			//pxPreviousWakeTime 中保存的是上次唤醒时间,
			//唤醒后需要一定时间执行任务主体代码,xConstTickCount是执行这些代码后的世界
			//如果上次唤醒时间大于当前时间,说明节拍计数器溢出了
			if( xConstTickCount < *pxPreviousWakeTime )
			{
				/* The tick count has overflowed since this function was
				lasted called.  In this case the only time we should ever
				actually delay is if the wake time has also	overflowed,
				and the wake time is greater than the tick time.  When this
				is the case it is as if neither time had overflowed. */
				//如果唤醒的时间小于上次唤醒时间,并且唤醒时间大于开始计时的时间,
				//这样子就是相当于没有溢出,也就是保了证周期性延时时间大于任务主体代码的执行时间
				//看图16-2
				if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* The tick time has not overflowed.  In this case we will
				delay if either the wake time has overflowed, and/or the
				tick time is less than the wake time. */
				//只是唤醒时间溢出的情况或者都没溢出,保证了延时时间大于任务主体代码的执行时间
				//图16-3,图16-4
				if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			//更新上一次的唤醒时间
			/* Update the wake time ready for the next call. */
			*pxPreviousWakeTime = xTimeToWake;

			if( xShouldDelay != pdFALSE )
			{
				traceTASK_DELAY_UNTIL( xTimeToWake );

				/* prvAddCurrentTaskToDelayedList() needs the block time, not
				the time to wake, so subtract the current tick count. */
				//prvAddCurrentTaskToDelayedList()函数需要的是阻塞时间而不是唤醒时间,
				//因此减去当前的滴答计数。
				prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		xAlreadyYielded = xTaskResumeAll();

		/* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
		if( xAlreadyYielded == pdFALSE )
		{
			//上下文切换
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelayUntil */

溢出情况示意图:

​ 这里一共有三个时间:

​ 1)任务开始的头时间,也是延时开始的头时间,pxPreviousWakeTime

​ 2) 任务结束进入,进入延时函数的时间,xConstTickCount

​ 3) 最后一个算出来包括任务在内,下一个固定延时唤醒的时间,xTimeToWake

在这里插入图片描述

​ 这三个关键点如果在各个关键点中间发生溢出会产生三种情况,如下三图所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

任务中调用

void vTaskA( void * pvParameters )
{
	/* 用于保存上次时间。调用后系统自动更新 */ 
	static portTickType PreviousWakeTime; 
	/* 设置延时时间,将时间转为节拍数 */ 
	const portTickType TimeIncrement = pdMS_TO_TICKS(1000); 
	/* 获取当前系统时间 */ 
	PreviousWakeTime = xTaskGetTickCount(); 
 
	 while (1)
	{
		/* 调用绝对延时函数,任务时间间隔为 1000 个 tick */ 
		vTaskDelayUntil( &PreviousWakeTime,TimeIncrement ); 
		// ...
		// 这里为任务主体代码 
		// ...
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值