任务延时函数

发布于 2023-08-11  353 次阅读


内容纲要

任务延时函数

函数 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();
 }
}

注意事项

  1. 使用函数 vTaskDelay()进行任务延时时,被延时的任务为调用该函数的任务,及调用该 函数时,系统中正在运行的任务,此函数无法指定将其他任务进行任务延时。
  2. 函数 vTaskDelay()传入的参数 xTicksToDelay 是任务被延时的具体延时时间,时间的单 位为系统时钟节拍,这里要特别注意,很多 FreeRTOS 的初学者可能会一会此函数延时的时间 单位为微妙、毫秒、秒等物理时间单位,当时 FreeRTOS 是以系统时钟节拍作为计量的时间单 位 的 , 而 系 统 时 钟 节 拍 对 应 的 物 理 时 间 长 短 于 FreeRTOSConfig.h 文 件 中 的 配 置 项configTICK_RATE_HZ 有关,配置项 configTICK_RATE_HZ 是用于配置系统时钟节拍的频率的, 本教程的所有配套例程,将此配置项配置成了 1000,即系统时钟节拍的频率为 1000,换算过 来,一个系统时钟节拍就是 1 毫秒。
  3. 在使用此函数进行任务延时时,如果传入的参数为 0,那表明不进行任务延时,而是强 制进行一次任务切换。
  4. 在使用此函数进行任务延时时,会调用函数 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
}
  1. 函数 prvAddCurrentTaskToDelayedList()是将任务当前任务添加到阻塞态任务列表中,其 中入参 xTicksToWait 就是要任务被阻塞的时间,入参 xCanBlockIndefinitely 为当 xTicksToWait 为最大值时,是否运行将任务无期限阻塞,即将任务挂起,当然,能够这样做的前提是,在 FreeRTOSConfig.h 文件中开启了挂起任务功能。
  2. 此函数在将任务添加到阻塞态任务列表中后,还会更新全局变量 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;
}

注意事项

  1. 函数 xTaskAbortDelay()会将阻塞任务从阻塞态任务列表中移除,并将任务添加到就绪态任务列表中。
  2. 因为有任务添加到就绪态任务列表中,因此需要的启用抢占式调度的情况下,判断刚添 加就绪态任务列表中的任务是否为系统中优先级最高的任务,如果是的话,就需要进行任务切 换,这就是抢占式调度的抢占机制。
  3. 任务被阻塞可能不仅仅因为是被延时,还有可能是在等待某个事件的发生,如果任务是 因为等待事件而被阻塞,那么中断阻塞的时候,需要将任务从所在事件列表中移除。
世界のネズミは彼らが望むものに依存し、彼らは彼ら自身から誰も求めません
最后更新于 2023-08-27