内容纲要
任务延时函数
函数 vTaskDelay()
函数 vTaskDelay()用于对任务进行延时,延时的时间单位为系统时钟节拍,如需使用子函 数,需要的 FreeRTOSConfig.h 文件中将配置项 INCLUDE_vTaskDelay 配置为 1。此函数在 task.c 文件中有定义,具体的代码如下所示:
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* 只有在延时时间大于 0 的时候,
* 才需要进行任务阻塞,
* 否则相当于强制进行任务切换,而不阻塞任务
*/
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();
}
}
注意事项
- 使用函数 vTaskDelay()进行任务延时时,被延时的任务为调用该函数的任务,及调用该 函数时,系统中正在运行的任务,此函数无法指定将其他任务进行任务延时。
- 函数 vTaskDelay()传入的参数 xTicksToDelay 是任务被延时的具体延时时间,时间的单 位为系统时钟节拍,这里要特别注意,很多 FreeRTOS 的初学者可能会一会此函数延时的时间 单位为微妙、毫秒、秒等物理时间单位,当时 FreeRTOS 是以系统时钟节拍作为计量的时间单 位 的 , 而 系 统 时 钟 节 拍 对 应 的 物 理 时 间 长 短 于 FreeRTOSConfig.h 文 件 中 的 配 置 项configTICK_RATE_HZ 有关,配置项 configTICK_RATE_HZ 是用于配置系统时钟节拍的频率的, 本教程的所有配套例程,将此配置项配置成了 1000,即系统时钟节拍的频率为 1000,换算过 来,一个系统时钟节拍就是 1 毫秒。
- 在使用此函数进行任务延时时,如果传入的参数为 0,那表明不进行任务延时,而是强 制进行一次任务切换。
- 在使用此函数进行任务延时时,会调用函数 prvAddCurrentTaskToDelayedList()将被延时 的任务添加到阻塞态任务列表中进行延时,系统会在每一次 SysTick 中断发生时,处理阻塞态 任务列表,更详细地请参考第 12.1.3 小节《FreeRTOS 系统时钟节拍处理》。其中函数 prvAddCurrentTaskToDelayedList()在 task.c 文件中有定义,具体的代码如下所示:
static void prvAddCurrentTaskToDelayedList(
TickType_t xTicksToWait, /* 阻塞时间 */
const BaseType_t xCanBlockIndefinitely)/* 是否无期限阻塞(阻塞时间为最大值) */
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;
/* 此宏用于开启任务延时中断功能 */
#if ( INCLUDE_xTaskAbortDelay == 1 )
{
/* 如果开启了任务延时中断功能
* 那么将任务的延时中断标志复位设置为假
* 当任务延时被中断时,再将其设置为真
*/
pxCurrentTCB->ucDelayAborted = pdFALSE;
}
#endif
/* 将当前任务从所在任务列表中移除
* 当前任务为调用了会触发阻塞的 API 函数的任务
* 当前任务的状态一定时就绪态
*/
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) ==
( UBaseType_t ) 0 )
{
/* 如果将当前任务从所在就绪态任务列表中移除后,
* 原本所在就绪态任务列表中每有其他任务
* 那么将任务优先级记录中该任务的优先级清除
*/
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority,
uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 此宏用于启用任务挂起功能 */
#if ( INCLUDE_vTaskSuspend == 1 )
{
/* 如果阻塞的时间为最大值并且允许任务被无期限阻塞
* 入参 xCanBlockIndefinitely 用于定义,
* 在入参 xTicksToWait 为最大值的情况下,
* 是否将任务无期限阻塞,即将任务挂起
*/
if( ( xTicksToWait == portMAX_DELAY ) &&
( xCanBlockIndefinitely != pdFALSE ) )
{
/* 将任务添加到挂起态任务列表中挂起,
* 即将任务无期限阻塞
*/
listINSERT_END( &xSuspendedTaskList,
&( pxCurrentTCB->xStateListItem ) );
}
else
{
/* 计算任务在未来被取消阻塞时,
* 系统时钟节拍计数器的值,
* 计算出来的值可能会出现溢出的情况,
* 但系统会有相应的处理机制,即两个阻塞态任务列表
*/
xTimeToWake = xConstTickCount + xTicksToWait;
/* 设置任务的状态列表项的值为计算出来的值
* 那么在 SysTick 中断服务函数中处理阻塞态任务列表时,
* 就可以通过这个值,判断任务时候阻塞超时
*/
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ),
xTimeToWake );
/* 如果计算出来的值溢出 */
if( xTimeToWake < xConstTickCount )
{
/* 将任务添加到阻塞超时时间溢出列表 */
vListInsert( pxOverflowDelayedTaskList,
&( pxCurrentTCB->xStateListItem ) );
}
else
{
/* 将任务添加到阻塞态任务列表 */
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* 全局变量 xNextTaskUnblockTime 用于保存
* 系统中最近要发生超时的系统节拍计数器的值
*/
if( xTimeToWake < xNextTaskUnblockTime )
{
/* 有新的任务阻塞,因此要更新 xNextTaskUnblockTime */
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
#else
{
/* 计算任务在未来被取消阻塞时,
* 系统时钟节拍计数器的值,
* 计算出来的值可能会出现溢出的情况,
* 但系统会有相应的处理机制,即两个阻塞态任务列表
*/
xTimeToWake = xConstTickCount + xTicksToWait;
/* 设置任务的状态列表项的值为计算出来的值
* 那么在 SysTick 中断服务函数中处理阻塞态任务列表时,
* 就可以通过这个值,判断任务时候阻塞超时
*/
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
/* 如果计算出来的值溢出 */
if( xTimeToWake < xConstTickCount )
{
/* 将任务添加到阻塞超时时间溢出列表 */
vListInsert( pxOverflowDelayedTaskList,
&( pxCurrentTCB->xStateListItem ) );
}
else
{
/* 将任务添加到阻塞态任务列表 */
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* 全局变量 xNextTaskUnblockTime 用于保存
* 系统中最近要发生超时的系统节拍计数器的值
*/
if( xTimeToWake < xNextTaskUnblockTime )
{
/* 有新的任务阻塞,因此要更新 xNextTaskUnblockTime */
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 不使用任务挂起功能,就不使用这个入参 */
( void ) xCanBlockIndefinitely;
}
#endif
}
- 函数 prvAddCurrentTaskToDelayedList()是将任务当前任务添加到阻塞态任务列表中,其 中入参 xTicksToWait 就是要任务被阻塞的时间,入参 xCanBlockIndefinitely 为当 xTicksToWait 为最大值时,是否运行将任务无期限阻塞,即将任务挂起,当然,能够这样做的前提是,在 FreeRTOSConfig.h 文件中开启了挂起任务功能。
- 此函数在将任务添加到阻塞态任务列表中后,还会更新全局变量 xNextTaskUnblockTime, 全局变量 xNextTaskUnblockTime 用于记录系统中的所有阻塞态任务中未来最近一个阻塞超时 任务的阻塞超时时系统时钟节拍计数器的值,因此,在往阻塞态任务列表添加任务后,就需要 更新这个全局变量,因为,新添加的阻塞态任务可能是未来系统中最早阻塞超时的阻塞任务。
函数 vTaskDelayUntil()
函数 vTaskDelayUntil()用于以一个绝对的时间阻塞任务,适用于需要按照一定频率运行的 任务,函数 vTaskDelayUntil()实际上是一个宏,在 task.h 文件中有定义,具体的代码如下所示:
#define vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement ) \
{ \
( void ) xTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement ); \
}
从上面的代码可以看出,宏 vTaskDelayUntil()实际上就是函数 xTaskDelayUntil(),函数 xTaskDelayUntil()在 task.c 文件中有定义,具体的代码如下所示:
BaseType_t xTaskDelayUntil(
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();
{
const TickType_t xConstTickCount = xTickCount;
/* 计算任务下一次阻塞超时的时间,
* 这个阻塞超时时间是相对于上一次阻塞超时的时间的
*/
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
/* 如果在上一次阻塞超时后,
* 系统时钟节拍计数器溢出过
*/
if( xConstTickCount < *pxPreviousWakeTime )
{
/* 只有在下一次阻塞超时时间也溢出,
* 并且下一次阻塞超时时间大于系统时钟节拍计数器的值时,
* 需要做相应的溢出处理,否则就好像没有溢出
*/
if( ( xTimeToWake < *pxPreviousWakeTime ) &&
( xTimeToWake > xConstTickCount ) )
{
/* 标记因为溢出,需要做相应的处理 */
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 系统时钟节拍计数器没有溢出,
* 但是下一次阻塞超时时间溢出了,
* 并且下一次阻塞超时时间大于系统时钟节拍计数器的值时,
* 需要做相应的溢出处理
*/
if( ( xTimeToWake < *pxPreviousWakeTime ) ||
( xTimeToWake > xConstTickCount ) )
{
/* 标记因为溢出,需要做相应的溢出处理 */
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 更新上一次阻塞超时时间为下一次阻塞超时时间 */
*pxPreviousWakeTime = xTimeToWake;
/* 根据标记,做相应的溢出处理 */
if( xShouldDelay != pdFALSE )
{
/* 用于调试,不用理会 */
traceTASK_DELAY_UNTIL( xTimeToWake );
/* 将任务添加到阻塞态任务列表中 */
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount,
pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 恢复任务调度器运行,
* 调用此函数会返回是否需要进行任务切换
*/
xAlreadyYielded = xTaskResumeAll();
/* 根据标志进行任务切换 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xShouldDelay;
}
从上面的代码可以看出,函数 xTaskDelayUntil()对任务进行延时的操作,是相对于任务上 一次阻塞超时的时间,而不是相对于系统当前的时钟节拍计数器的值,因此,函数能够更准确 地以一定的频率进行任务延时,更加适用于需要按照一定频率运行的任务。
函数 xTaskAbortDelay()
函数 xTaskAbortDelay()用于终止处于阻塞态任务的阻塞,此函数在 task.c 文件中有定于, 具体的代码如下所示:
BaseType_t xTaskAbortDelay( TaskHandle_t xTask )
{
TCB_t * pxTCB = xTask;
BaseType_t xReturn;
configASSERT( pxTCB );
/* 挂起任务调度器 */
vTaskSuspendAll();
{
/* 被中断阻塞时的任务一定处于阻塞状态 */
if( eTaskGetState( xTask ) == eBlocked )
{
xReturn = pdPASS;
/* 将任务从所在任务列表(阻塞态任务列表)中移除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 阻塞任务因为等待时间而被阻塞
* 因为要中断任务阻塞,
* 因此将任务从所在事件列表中移除
*/
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !=
NULL )
{
/* 将任务从所在事件列表中移除 */
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
/* 标记任务阻塞被中断 */
pxTCB->ucDelayAborted = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 将任务添加到就绪态任务列表中 */
prvAddTaskToReadyList( pxTCB );
/* 此宏用于启用抢占式调度 */
#if ( configUSE_PREEMPTION == 1 )
{
/* 如果启用了抢占式调度,
* 就需要判断刚添加到就绪态任务列表中的任务
* 是否为系统中优先级最高的就绪态任务,
* 如果是,就绪要进行任务切换
*/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* 标记需要进行任务切换 */
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
else
{
/* 待取消阻塞的任务不处于阻塞态
* 返回错误
*/
xReturn = pdFAIL;
}
}
/* 恢复任务调度器 */
( void ) xTaskResumeAll();
return xReturn;
}
注意事项
- 函数 xTaskAbortDelay()会将阻塞任务从阻塞态任务列表中移除,并将任务添加到就绪态任务列表中。
- 因为有任务添加到就绪态任务列表中,因此需要的启用抢占式调度的情况下,判断刚添 加就绪态任务列表中的任务是否为系统中优先级最高的任务,如果是的话,就需要进行任务切 换,这就是抢占式调度的抢占机制。
- 任务被阻塞可能不仅仅因为是被延时,还有可能是在等待某个事件的发生,如果任务是 因为等待事件而被阻塞,那么中断阻塞的时候,需要将任务从所在事件列表中移除。
Comments NOTHING