目录
在《FreeRTOS --(7)任务管理之入门篇》中讲过,如果有几个任务同时跑,但是又都不阻塞的话,那么最高优先级的任务将会占领整个 CPU,因为每次都会调度到它,一直处于 Ready 状态,所以呢,调度器每次都要选择优先级最高的任务来让它执行;所以,不管怎么样,任务做完自己该做的事情,就应该进入阻塞状态,等待下次该自己做任务的时候,在占领 CPU,这样既可以让 Idle 线程,在系统空闲的时候跑,也可以让让任务在合理的时间占领 CPU;
之前也说过,让任务进入阻塞状态的方式有两种:
1、让任务延时:因为任务是一个 While 1 的无限循环,所以执行完自己的事情后,可以调用接口进行延时,进入阻塞,让出 CPU;
2、让任务等待某个事件:当任务需要的资源满足不了的时候,可以让任务阻塞的等待所需的资源,这样也是合理的;
这章就是要讲让任务进入延时进入阻塞的方法以及相关的原理;
1、接口介绍
任务执行完自己的事情后,可以调用如下接口,让任务进入阻塞态,delay 一段时间:
1、vTaskDelay()
2、vTaskDelayUntil()
下面分别来介绍这两个函数的用法和官方的解释;
1.1、vTaskDelay
1.1.1、Usage
这个函数是相对延时函数,它的函数原型为:
-
/**
-
* task. h
-
* <pre>void vTaskDelay( const TickType_t xTicksToDelay );</pre>
-
*
-
* Delay a task for a given number of ticks. The actual time that the
-
* task remains blocked depends on the tick rate. The constant
-
* portTICK_PERIOD_MS can be used to calculate real time from the tick
-
* rate - with the resolution of one tick period.
-
*
-
* INCLUDE_vTaskDelay must be defined as 1 for this function to be available.
-
* See the configuration section for more information.
-
*
-
*
-
* vTaskDelay() specifies a time at which the task wishes to unblock relative to
-
* the time at which vTaskDelay() is called. For example, specifying a block
-
* period of 100 ticks will cause the task to unblock 100 ticks after
-
* vTaskDelay() is called. vTaskDelay() does not therefore provide a good method
-
* of controlling the frequency of a periodic task as the path taken through the
-
* code, as well as other task and interrupt activity, will effect the frequency
-
* at which vTaskDelay() gets called and therefore the time at which the task
-
* next executes. See vTaskDelayUntil() for an alternative API function designed
-
* to facilitate fixed frequency execution. It does this by specifying an
-
* absolute time (rather than a relative time) at which the calling task should
-
* unblock.
-
*
-
* @param xTicksToDelay The amount of time, in tick periods, that
-
* the calling task should block.
-
*
-
* Example usage:
-
-
void vTaskFunction( void * pvParameters )
-
{
-
// Block for 500ms.
-
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
-
-
for( ;; )
-
{
-
// Simply toggle the LED every 500ms, blocking between each toggle.
-
vToggleLED();
-
vTaskDelay( xDelay );
-
}
-
}
-
-
* \defgroup vTaskDelay vTaskDelay
-
* \ingroup TaskCtrl
-
*/
-
void vTaskDelay( const TickType_t xTicksToDelay ) PRIVILEGED_FUNCTION;
官方的注释写得非常非常仔细,甚至于用法都写进了注释;
任务调用这个函数,传入一个 xTicksToDelay ,代表相对现在,对任务进行延时;这个的这个 xTicksToDelay 的单位是 Tick,比如,你 1ms 一个 Tick 的话,那么设置 50,就是延时 50ms;如果 10ms 一个 Tick 的话,设置 50,就是延时 500ms;
实际情况是,将当前任务阻塞,到时间的时候,再把它加入到就绪队列,参与调度;
简单的 Demo 如下所示:
-
void vTaskFunction( void * pvParameters )
-
{
-
// Block for 250ms.
-
const TickType_t xDelay250ms =
pdMS_TO_TICKS(
250 );
-
-
for( ;; )
-
{
-
// Simply toggle the LED every 250ms, blocking between each toggle.
-
vToggleLED();
-
-
vTaskDelay( xDelay250ms );
-
}
-
}
时序上比如:
1.1.2、Implement
知道了用法后,我们来看下 vTaskDelay 的实现,在 task.c 文件中:
-
#if ( INCLUDE_vTaskDelay == 1 )
-
-
void vTaskDelay( const TickType_t xTicksToDelay )
-
{
-
BaseType_t xAlreadyYielded = pdFALSE;
-
-
-
/* 如果延时时间为0,则不会将当前任务加入延时列表 */
-
if( xTicksToDelay > ( TickType_t )
0U )
-
{
-
vTaskSuspendAll();
-
{
-
/* 将当前任务从就绪列表中移除,并根据当前系统节拍计数器值计算唤醒时间,然后将任务加入延时列表 */
-
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
-
}
-
xAlreadyYielded =
xTaskResumeAll();
-
}
-
-
-
/* 强制执行一次上下文切换*/
-
if( xAlreadyYielded == pdFALSE )
-
{
-
portYIELD_WITHIN_API();
-
}
-
}
-
-
#endif /* INCLUDE_vTaskDelay */
vTaskDelay 的实现依赖于宏 INCLUDE_vTaskDelay ,必须定义这个宏,才能够使用这个函数;
先判空,如果入参,也就是需要延时的时间大于 0 才有效;
先调用 vTaskSuspendAll(); 来挂起调度器,暂时暂停调度;
然后调用 prvAddCurrentTaskToDelayedList 将当前的这个任务添加到 Delayed 链表;
这个函数稍后深入分析,这里先扩展分析一下几个链表:
-
/* Lists for ready and blocked tasks. --------------------
-
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
-
doing so breaks some kernel aware debuggers and debuggers that rely on removing
-
the static qualifier. */
-
PRIVILEGED_DATA
static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
/*< Prioritised ready tasks. */
-
PRIVILEGED_DATA
static List_t xDelayedTaskList1;
/*< Delayed tasks. */
-
PRIVILEGED_DATA
static List_t xDelayedTaskList2;
/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
-
PRIVILEGED_DATA
static List_t *
volatile pxDelayedTaskList;
/*< Points to the delayed task list currently being used. */
-
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;
/*< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */
-
对于一个任务,有很多种状态,可能是 Running、Ready、Blocked、Suspend,那对于不一样的的状态,FreeRTOS 中将其挂接到不同的链表,进行管理;
单核情况下,同一时间,只有一个任务处于 Running 状态,所以用 pxCurrentTCB 便可以代表了 Running 状态;
Blocked 阻塞态的任务,阻塞在时间上的任务,被挂接到名为 xxxDelayedTaskListx 上:
-
PRIVILEGED_DATA
static List_t xDelayedTaskList1;
/*< Delayed tasks. */
-
PRIVILEGED_DATA
static List_t xDelayedTaskList2;
/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
-
PRIVILEGED_DATA
static List_t *
volatile pxDelayedTaskList;
/*< Points to the delayed task list currently being used. */
-
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. */
用了两个链表,主要是为了处理时间回绕的场景(时间回绕,的含义为,用 U32 来记录运行时间,每次 SysTick 的时候增加1个计数,由于 SysTick 的周期我们是知道的,比如 1ms,所以我们就知道当前的时间,但是如果系统长时间运行,记录时间的 U32 势必会溢出,就导致了时间回绕);具体的处理方式我们细细来品;
最后调用 xTaskResumeAll 查看是否有需要调度的任务,有的话,强制触发一次调度;
下面来看看 prvAddCurrentTaskToDelayedList 的实现:
-
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
-
{
-
TickType_t xTimeToWake;
-
const TickType_t xConstTickCount = xTickCount;
-
-
#if( INCLUDE_xTaskAbortDelay == 1 )
-
{
-
/* 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
-
-
/* Remove the task from the ready list before adding it to the blocked list
-
as the same list item is used for both lists. */
-
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 );
/*lint !e931 pxCurrentTCB cannot change as it is the calling task. pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called with scheduler suspended or in a critical section. */
-
}
-
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 */
-
}
代码的逻辑是:
1、首先,当前的这个任务,肯定是处于 Ready 链表,所以将它从 Ready 链表移除;
2、获取当前的绝对时间 xTickCount,也就是 SysTick 会累积增加的那个,然后将延时的时间加上这个基准时间,配置成为唤醒该任务的时间,并赋值给这个 Item 的 Value 字段,并将其挂接到 Delay 链表;
虽然代码逻辑如上所示,不过在链表挂接的时候,需要处理一些临界状态,比如,将当前任务从 Ready 链表中拿去的时候,需要判断当前 Ready 链表中,拿去这个任务后,是否已为空,如果是这样的话,就要清除记录优先级对应的 uxTopReadyPriority (Bitmap);
配置唤醒时间的时候,就要通过比对时间基准来判断 U32 的回绕,如果时间回绕,那么将其挂接到 pxOverflowDelayedTaskList 这个链表,否则挂接到 pxDelayedTaskList 链表;
唤醒的时间如果比最近的唤醒时间还早,那么需要更新唤醒时间到全局变量 xNextTaskUnblockTime 中,在 SysTick 来的时候进行判断比对唤醒时间;
总的来说,vTaskDelay 接口实现基于当前时间的一个增量延时,并 Block 了当前任务;
1.2、vTaskDelayUntil
我们再来看看这个 vTaskDelayUntil ;
1.2.1、Usage
这个函数是绝对延时函数,可以用来做周期性任务(必须最高优先级才可以),它的函数原型为:
-
/**
-
* task. h
-
* <pre>void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );</pre>
-
*
-
* INCLUDE_vTaskDelayUntil must be defined as 1 for this function to be available.
-
* See the configuration section for more information.
-
*
-
* Delay a task until a specified time. This function can be used by periodic
-
* tasks to ensure a constant execution frequency.
-
*
-
* This function differs from vTaskDelay () in one important aspect: vTaskDelay () will
-
* cause a task to block for the specified number of ticks from the time vTaskDelay () is
-
* called. It is therefore difficult to use vTaskDelay () by itself to generate a fixed
-
* execution frequency as the time between a task starting to execute and that task
-
* calling vTaskDelay () may not be fixed [the task may take a different path though the
-
* code between calls, or may get interrupted or preempted a different number of times
-
* each time it executes].
-
*
-
* Whereas vTaskDelay () specifies a wake time relative to the time at which the function
-
* is called, vTaskDelayUntil () specifies the absolute (exact) time at which it wishes to
-
* unblock.
-
*
-
* The constant portTICK_PERIOD_MS can be used to calculate real time from the tick
-
* rate - with the resolution of one tick period.
-
*
-
* @param pxPreviousWakeTime Pointer to a variable that holds the time at which the
-
* task was last unblocked. The variable must be initialised with the current time
-
* prior to its first use (see the example below). Following this the variable is
-
* automatically updated within vTaskDelayUntil ().
-
*
-
* @param xTimeIncrement The cycle time period. The task will be unblocked at
-
* time *pxPreviousWakeTime + xTimeIncrement. Calling vTaskDelayUntil with the
-
* same xTimeIncrement parameter value will cause the task to execute with
-
* a fixed interface period.
-
*
-
* Example usage:
-
<pre>
-
// Perform an action every 10 ticks.
-
void vTaskFunction( void * pvParameters )
-
{
-
TickType_t xLastWakeTime;
-
const TickType_t xFrequency = 10;
-
-
// Initialise the xLastWakeTime variable with the current time.
-
xLastWakeTime = xTaskGetTickCount ();
-
for( ;; )
-
{
-
// Wait for the next cycle.
-
vTaskDelayUntil( &xLastWakeTime, xFrequency );
-
-
// Perform action here.
-
}
-
}
-
</pre>
-
* \defgroup vTaskDelayUntil vTaskDelayUntil
-
* \ingroup TaskCtrl
-
*/
-
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) PRIVILEGED_FUNCTION;
两个入参:
pxPreviousWakeTime:初始化为当前的时间,后面便不用管了;
xTimeIncrement:任务的周期;
简单的 Demo 如下所示:
-
void vTaskB( void * pvParameters )
-
{
-
static portTickType xLastWakeTime;
-
const portTickType xFrequency =
pdMS_TO_TICKS(
500);
-
-
// 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同
-
xLastWakeTime =
xTaskGetTickCount();
-
-
for( ;; )
-
{
-
/* 调用系统延时函数,周期性阻塞500ms */
-
vTaskDelayUntil( &xLastWakeTime,xFrequency );
-
-
// ...
-
// 这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同
-
// ...
-
-
}
-
}
1.2.2、Implement
它的实现如下所示:
-
#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;
-
-
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. */
-
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. */
-
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( 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 */
也是先挂起任务,最后恢复;
与 xTaskDelay 不同,它定义了几个变量:
*pxPreviousWakeTime 代表上一次解除阻塞执行任务的时间;
xConstTickCount 是当前的时间;
xTimeToWake 是下一次唤醒的时间;
首先还是判断 Tick 溢出的场景;
可以看到,在最后调用:
prvAddCurrentTaskToDelayedList
的时候,它每次都是动态的去调整 Delay 的时间:xTimeToWake - xConstTickCount,尽量做到任务执行开始的时间保持一致;