FreeRTOS time management


1. FreeRTOS delay function

1. Function vTaskDelay()

The delay function in FreeRTOS also has relative mode and absolute mode, but different functions are used in different modes in FreeRTOS. The function vTaskDelay() is a relative mode (relative delay function), and the function vTaskDelayUntil() is an absolute mode (absolute delay function). time function). The function vTaskDelay() is defined in the file tasks.c. To use this function, the macro INCLUDE_vTaskDelay must be 1. The function code is as follows:

void vTaskDelay( const TickType_t xTicksToDelay )
{
    
    
	BaseType_t xAlreadyYielded = pdFALSE;
	//延时时间要大于 0。
	if( xTicksToDelay > ( TickType_t ) 0U ) (1)
	{
    
    
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll(); (2)
		{
    
    
			traceTASK_DELAY();
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); (3)
		}
		xAlreadyYielded = xTaskResumeAll(); (4)
	}
	else
	{
    
    
		mtCOVERAGE_TEST_MARKER();
	}
	if( xAlreadyYielded == pdFALSE ) (5)
	{
    
    
		portYIELD_WITHIN_API(); (6)
	}
	else
	{
    
    
		mtCOVERAGE_TEST_MARKER();
	}
}

(1) The delay time is determined by the parameter xTicksToDelay, which is the number of ticks to be delayed, and the delay time must be greater
than 0. Otherwise, it is equivalent to directly calling the function portYIELD() to switch tasks.

(2) Call the function vTaskSuspendAll() to suspend the task scheduler.

(3) Call the function prvAddCurrentTaskToDelayedList() to add the task to be delayed to the delay list pxDelayedTaskList or pxOverflowDelayedTaskList(). The function prvAddCurrentTaskToDelayedList() will be analyzed in detail later
.

(4) Call the function xTaskResumeAll() to restore the task scheduler.

(5) If the function xTaskResumeAll() does not perform task scheduling, then task scheduling must be performed here.

(6) Call the function portYIELD_WITHIN_API() to schedule a task.

2. Function prvAddCurrentTaskToDelayedList()

The function prvAddCurrentTaskToDelayedList() is used to add the current task to the waiting list. The function
is defined in the file tasks.c. The reduced function is as follows:

static void prvAddCurrentTaskToDelayedList( TickType_t x TicksToWait, const BaseType_t xCanBlockIndefinitely )
{
    
    
	TickType_t xTimeToWake;
	const TickType_t xConstTickCount = xTickCount; (1)
	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
    
    
		//如果使能函数 xTaskAbortDelay()的话复位任务控制块的 ucDelayAborted 字段为
		//pdFALSE。
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif
	
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (2)
	{
    
    
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); (3)
	}
	else
	{
    
    
		mtCOVERAGE_TEST_MARKER();
	}
	
	#if ( INCLUDE_vTaskSuspend == 1 )
	{
    
    
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )(4)
		{
    
    
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); (5)
		}
		else
		{
    
    
			xTimeToWake = xConstTickCount + xTicksToWait; (6)
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), \ (7)
			xTimeToWake );
			if( xTimeToWake < xConstTickCount ) (8)
			{
    
    
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->\ (9)
				xStateListItem ) );
			}
		   else
			{
    
    
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); (10)
				if( xTimeToWake < xNextTaskUnblockTime ) (11)
				{
    
    
					xNextTaskUnblockTime = xTimeToWake; (12)
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	/***************************************************************************/
	/****************************其他条件编译语句*******************************/
	/***************************************************************************/
}

}
(1). Read the time point of entering the function prvAddCurrentTaskToDelayedList() and save it in xConstTickCount, which will be used when calculating the wake-up time point of the task later. xTickCount is the clock tick counter, each tick timer interrupt xTickCount will increase by one.

(2) To add the currently running task to the delay list, the current task must be removed from the ready list first.

(3) After removing the current task from the ready list, cancel the ready mark of the task in uxTopReadyPriority.
That is to clear the corresponding bit in uxTopReadyPriority.

(4) If the delay time is the maximum value portMAX_DELAY, and xCanBlockIndefinitely is not pdFALSE (if xCanBlockIndefinitely is not pdFALSE, it means that blocking tasks are allowed), then the current task is directly added to the pending list, and the task does not need to be added to the delay list.

(5). Add the current task to the end of the suspended list xSuspendedTaskList.

(6) Calculate the wake-up time point of the task, that is, the time value xConstTickCount of the entry function prvAddCurrentTaskToDelayedList() obtained in (1) plus the delay time value xTicksToWait.

(7) Write the calculated task wake-up time value xTimeToWake into the corresponding field of the state list item in the task list
.

(8) The calculated task wake-up time point is less than xConstTickCount, indicating that an overflow has occurred. The global variable
xTickCount is of TickType_t type, which is a 32-bit data type, so overflow will definitely occur when xTimeToWake is used to calculate the wake-up time of the task. FreeRTOS has specially dealt with this phenomenon. In FreeROTS, two delay lists xDelayedTaskList1 and xDelayedTaskList2 are defined, and two pointers pxDelayedTaskList and pxOverflowDelayedTaskList are defined to access these two lists. In the initialization list function prvInitialiseTaskLists(), the pointer pxDelayedTaskList It points to the list xDelayedTaskList1, and the pointer pxOverflowDelayedTaskList points to the list xDelayedTaskList2. In this way, if an overflow occurs, the task will be added to the list pointed to by pxOverflowDelayedTaskList, and if there is no overflow, it will be added to the list pointed to by pxDelayedTaskList.

(9). If an overflow occurs, add the current task to the list pointed to by pxOverflowDelayedTaskList.

(10). If no overflow occurs, add the current task to the list pointed to by pxDelayedTaskList.


(11), xNextTaskUnblockTime is a global variable, which saves the minimum time point value from the next task to be unblocked . When xTimeToWake is less than xNextTaskUnblockTime, it means that a smaller time point is coming.

(12). Update xNextTaskUnblockTime to xTimeToWake.

3. Function vTaskDelayUntil()


The function vTaskDelayUntil() will block tasks, and the blocking time is an absolute time. Those tasks that need to run at a certain frequency can use the function vTaskDelayUntil(). This function has the following definition in the file tasks.c:

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(); (1)
	{
    
    
		const TickType_t xConstTickCount = xTickCount; (2)
		xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; (3)
		if( xConstTickCount < *pxPreviousWakeTime ) (4)
		{
    
    
			if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake >\ (5)
				xConstTickCount ) )
			{
    
    
				xShouldDelay = pdTRUE; (6)
			}
		   else
		   {
    
    
				mtCOVERAGE_TEST_MARKER();
		   }
  	   }
	   else
		{
    
    
			if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > \ (7)
			xConstTickCount ) )
			{
    
    
				xShouldDelay = pdTRUE; (8)
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		*pxPreviousWakeTime = xTimeToWake; (9)
		if( xShouldDelay != pdFALSE ) (10)
		{
    
    
			traceTASK_DELAY_UNTIL( xTimeToWake );
			prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );(11)
		}
		else
		{
    
    
			mtCOVERAGE_TEST_MARKER();
		}
	}
	xAlreadyYielded = xTaskResumeAll(); (12)
	if( xAlreadyYielded == pdFALSE )
	{
    
    
		ortYIELD_WITHIN_API();
	}
	else
	{
    
    
		mtCOVERAGE_TEST_MARKER();
	}
}

Parameters:
pxPreviousWakeTime: The time when the task was woken up after the delay ended. If the function vTaskDelayUntil is called for the first time in the task,
pxPreviousWakeTime needs to be initialized to enter the time point value of the while() loop body of the task. The function vTaskDelayUntil() will automatically update pxPreviousWakeTime in future runs.
xTimeIncrement: The number of time beats that the task needs to delay (relative to the number of beats of pxPreviousWakeTime this delay).

(1) Suspend the task scheduler.

(2). Record the time point value entering the function vTaskDelayUntil() and save it in xConstTickCount.

(3) Calculate the next wake-up time of the task according to the delay time xTimeIncrement, and save it in
xTimeToWake. It can be seen that this delay time is relative to pxPreviousWakeTime, which is the time when the last task was woken up. The relationship between pxPreviousWakeTime, xTimeToWake, xTimeIncrement and xConstTickCount is shown in the figure below.
insert image description hereAs shown in the figure above, (1) is the main body of the task, that is, the real work to be done by the task; (2) is calling vTaskDelayUntil() in the task function to delay the task; (3) is running other tasks. The delay time of the task is xTimeIncrement, which is relative to pxPreviousWakeTime. It can be seen that the total execution time of the task must be less than the task delay time xTimeIncrement! That is to say, if vTaskDelayUntil() is used, the execution period of the task is equivalent to xTimeIncrement, and the task must be executed within this time. This ensures that the task will always run at a certain frequency. This delay value is the absolute delay time, so the function vTaskDelayUntil() is also called the absolute delay function.

(4) According to Figure 12.3.1, it can be seen that theoretically xConstTickCount is greater than pxPreviousWakeTime, but there is also a situation that causes xConstTickCount to be less than pxPreviousWakeTime, that is, xConstTickCount overflows!

(5) Since xConstTickCount has overflowed, the calculated task wake-up time must also overflow,
and xTimeToWake must be greater than xConstTickCount. This situation is shown in the figure below:
insert image description here
(6). If the condition (5) is satisfied, assign pdTRUE to xShouldDelay, and mark the time delay as allowed.

(7) There are two other cases, one: only xTimeToWake overflows, and two: neither overflows. If only xTimeToWake overflows,
insert image description here
it is as shown in the figure below; if it does not overflow, it is as shown in Figure 12.3.1. In both cases, delay is allowed.

(8) Assign pdTRUE to xShouldDelay to mark the time delay allowed.

(9). Update the value of pxPreviousWakeTime to xTimeToWake to prepare for the next execution of this function
.

(10) After the previous judgment, it is allowed to delay the task.

(11) Call the function prvAddCurrentTaskToDelayedList() to delay the time. The first parameter of the function is to set
the blocking time of the task. We have calculated the next wake-up time of the task before, so the time that the task still needs to block is the next wake-up time xTimeToWake minus the current time xConstTickCount. In the function vTaskDelay(), this parameter is simply set to xTicksToDelay.

(12) Call the function xTaskResumeAll() to restore the task scheduler.
The function vTaskDelayUntil() is used as follows:

void TestTask( void * pvParameters )
{
    
    
	TickType_t PreviousWakeTime;
	//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
	//设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
	const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
	PreviousWakeTime = xTaskGetTickCount(); //获取当前的系统节拍值
	for( ;; )
	{
    
    
		/******************************************************************/
		/*************************任务主体*********************************/
		/******************************************************************/
		//调用函数 vTaskDelayUntil 进行延时
		vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
	}
}

In fact, the task delayed by using the function vTaskDelayUntil() may not necessarily be able to run periodically. Using the function vTaskDelayUntil() can only ensure that you unblock according to a certain period and enter the ready state. If there is a higher priority or interrupt, you still have to wait for other high priority tasks or interrupt service functions to complete before it is your turn. This absolute delay is only relative to the simple delay function vTaskDelay().

2. FreeRTOS system clock beat

No matter what the system is, it needs a system clock tick to run. It has been mentioned many times before. xTickCount is the
system clock tick counter of FreeRTOS. xTickCount will add one xTickCount in each tick timer interrupt. The specific operation process is carried out in the function xTaskIncrementTick(), which is defined in the file tasks.c, as follows:

BaseType_t xTaskIncrementTick( void )
{
    
    
	TCB_t * pxTCB;
	TickType_t xItemValue;
	BaseType_t xSwitchRequired = pdFALSE;
	//每个时钟节拍中断(滴答定时器中断)调用一次本函数,增加时钟节拍计数器 xTickCount 的
	//值,并且检查是否有任务需要取消阻塞。
	traceTASK_INCREMENT_TICK( xTickCount );
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) (1)
	{
    
    
		const TickType_t xConstTickCount = xTickCount + 1; (2)
		//增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列
		//表指针值。
		xTickCount = xConstTickCount;
		if( xConstTickCount == ( TickType_t ) 0U ) (3)
		{
    
    
			taskSWITCH_DELAYED_LISTS(); (4)
		}
		else
		{
    
    
			mtCOVERAGE_TEST_MARKER();
		}
		//判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升
		//序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的
		//延时时间都没有到的话后面的任务就不用看了,肯定也没有到。
		if( xConstTickCount >= xNextTaskUnblockTime ) (5)
		{
    
    
			for( ;; )
			{
    
    
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) (6)
				{
    
    
					//延时列表为空,设置 xNextTaskUnblockTime 为最大值。
					xNextTaskUnblockTime = portMAX_DELAY; (7)
					break;
				}
				else
				{
    
    
					//延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值
					//判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。
					pxTCB = ( TCB_t * )\ (8)
					 listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
					xItemValue =\ (9)
					listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
					if( xConstTickCount < xItemValue ) (10)
					{
    
    
						//任务延时时间还没到,但是 xItemValue 保存着下一个即将解除
						//阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新
						//变量 xNextTaskUnblockTime
						xNextTaskUnblockTime = xItemValue; (11)
						break;
					}
					else
					{
    
    
						mtCOVERAGE_TEST_MARKER();
					}
					//将任务从延时列表中移除
					( void ) uxListRemove( &( pxTCB->xStateListItem ) ); (12)
					//任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些
					//任务从相应的事件列表中移除。相当于等待事件超时退出!
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ (13)
					= NULL )
					{
    
    
						( void ) uxListRemove( &( pxTCB->xEventListItem ) ); (14)
					}
					else
					{
    
    
						mtCOVERAGE_TEST_MARKER();
					}
					//将任务添加到就绪列表中
					prvAddTaskToReadyList( pxTCB ); (15)
					#if ( configUSE_PREEMPTION == 1 )
					{
    
    
						//使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
						//运行的任务优先级,如果是的话就需要进行一次任务切换!
						if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) (16)
						{
    
    
							xSwitchRequired = pdTRUE;
						}
						else
						{
    
    
							mtCOVERAGE_TEST_MARKER();
						}
					}
					#endif /* configUSE_PREEMPTION */
				}
			}
		}
		//如果使能了时间片的话还需要处理同优先级下任务之间的调度
		#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )(17)
		{
    
    
			if( listCURRENT_LIST_LENGTH( &( \
				pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
			{
    
    
				xSwitchRequired = pdTRUE;
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif 
		
		//使用时钟节拍钩子函数
		#if ( configUSE_TICK_HOOK == 1 )
		{
    
    
			if( uxPendedTicks == ( UBaseType_t ) 0U )
			{
    
    
				vApplicationTickHook(); (18)
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configUSE_TICK_HOOK */
	
	}
	else //任务调度器挂起 (19)
	{
    
    
		++uxPendedTicks; (20)
		#if ( configUSE_TICK_HOOK == 1 )
		{
    
    
			vApplicationTickHook();
		}
		#endif
	}
	#if ( configUSE_PREEMPTION == 1 )
	{
    
    
		if( xYieldPending != pdFALSE ) (21)
		{
    
    
			xSwitchRequired = pdTRUE;
		}
		else
		{
    
    
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_PREEMPTION */
	return xSwitchRequired; (22)
}

}
(1). Determine whether the task scheduler is suspended.

(2) Add one to the clock tick counter xTickCount and save the result in xConstTickCount. The next program
will assign xConstTickCount to xTickCount, which is equivalent to adding one to xTickCount.

(3), xConstTickCount is 0, indicating that an overflow has occurred!

(4) If an overflow occurs, use the function taskSWITCH_DELAYED_LISTS to
exchange the list pointed to by the delayed list pointer pxDelayedTaskList and the overflow list pointer pxOverflowDelayedTaskList. The function taskSWITCH_DELAYED_LISTS() is essentially a macro, which is defined in the file tasks.c. The value of xNextTaskUnblockTime needs to be updated after the lists pointed by the two pointers are exchanged.

(5). The variable xNextTaskUnblockTime stores the time point value of the next task to be unblocked. If
xConstTickCount is greater than xNextTaskUnblockTime, it means that there is a task that needs to be unblocked.

(6). Determine whether the delay list is empty.

(7) If the delay list is empty, set xNextTaskUnblockTime to portMAX_DELAY.

(8) The delay list is not empty, and the task control block corresponding to the first item in the delay list is obtained.

(9). Obtain the value of the strong state list item in the task control block obtained in (8).

(10), the value of the strong state list item in the task control block has preserved the wake-up time point of the task, if the wake-up time point value is greater than the
current system clock (clock tick counter value), the delay time of the task has not yet arrived.

(11) The task delay time has not yet arrived, and xItemValue has saved the wake-up time
point of the next task to be woken up, so it is necessary to update xNextTaskUnblockTime with xItemValue.

(12) The task delay time is up, so remove the task from the delay list first.

(13) Check whether the task is still waiting for an event, such as waiting for a semaphore, queue, etc. If still waiting, the task is
removed from the corresponding event list. Because the timeout is up!

(14). The task is removed from the corresponding event list.

(15) The task delay time is up, and the task has been removed from the delay list or event list. So here you need
to add the task to the ready list.

(16). The priority of the task whose delay time has expired is higher than that of the running task, so task switching is required. Mark
xSwitchRequired as pdTRUE, indicating that task switching is required.

(17) If time slice scheduling is enabled, work related to time slice scheduling must also be processed. Refer to
Section 9.6 for the specific process.

(18) If the time slice hook function is enabled, execute the time slice hook function vApplicationTickHook(), and the
specific content of the function is written by the user.

(19). If the function vTaskSuspendAll() is called to suspend the task scheduler,
xTickCount will not be updated every tick timer interrupt. Instead, uxPendedTicks is used to record the number of ticks during which the scheduler was suspended. In this way, when the function xTaskResumeAll() is called to restore the task scheduler, the function xTaskIncrementTick() will be called uxPendedTicks times, so that xTickCount will resume, and those tasks that should be unblocked will be unblocked.

The corresponding processing code in the function xTaskResumeAll() is as follows:

BaseType_t xTaskResumeAll( void )
{
    
    
	TCB_t *pxTCB = NULL;
	BaseType_t xAlreadyYielded = pdFALSE;
	configASSERT( uxSchedulerSuspended );
	taskENTER_CRITICAL();
	/************************************************************************/
	/****************************省略部分代码********************************/
	/************************************************************************/
	UBaseType_t uxPendedCounts = uxPendedTicks; 
	if( uxPendedCounts > ( UBaseType_t ) 0U )
	{
    
    
		//do-while()循环体,循环次数为 uxPendedTicks
		do
		{
    
    
			if( xTaskIncrementTick() != pdFALSE ) //调用函数 xTaskIncrementTick
			{
    
    
				xYieldPending = pdTRUE; //标记需要进行任务调度。
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
			--uxPendedCounts; //变量减一
		} while( uxPendedCounts > ( UBaseType_t ) 0U );
		uxPendedTicks = 0; //循环执行完毕,uxPendedTicks 清零
	}
	else
	{
    
    
		mtCOVERAGE_TEST_MARKER();
	}
	/************************************************************************/
	/****************************省略部分代码********************************/
	/************************************************************************/
	taskEXIT_CRITICAL();
	return xAlreadyYielded;
}

(20) uxPendedTicks is a global variable, which is defined in the file tasks.c. After the task scheduler is suspended, this variable is
used to record the number of clock ticks.

(21). Sometimes when calling other API functions, the variable xYieldPending will be used to mark whether context switching is required
, and specific analysis will be encountered later.

(22) Return the value of xSwitchRequired, xSwitchRequired saves the information whether to perform task switching, if it is pdTRUE, it needs to perform task switching, and if pdFALSE, it does not need to perform task switching.
When calling xTaskIncrementTick() in the function xPortSysTickHandler(), it will judge the return value, and decide whether to switch tasks according to the return value.

Guess you like

Origin blog.csdn.net/Dustinthewine/article/details/130207126