FreeRTOS --(5)任务管理之任务延时

目录

1、接口介绍

1.1、vTaskDelay

1.1.1、Usage

1.1.2、Implement

1.2、vTaskDelayUntil

1.2.1、Usage

1.2.2、Implement


 

在《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

这个函数是相对延时函数,它的函数原型为:


   
   
  1. /**
  2. * task. h
  3. * <pre>void vTaskDelay( const TickType_t xTicksToDelay );</pre>
  4. *
  5. * Delay a task for a given number of ticks. The actual time that the
  6. * task remains blocked depends on the tick rate. The constant
  7. * portTICK_PERIOD_MS can be used to calculate real time from the tick
  8. * rate - with the resolution of one tick period.
  9. *
  10. * INCLUDE_vTaskDelay must be defined as 1 for this function to be available.
  11. * See the configuration section for more information.
  12. *
  13. *
  14. * vTaskDelay() specifies a time at which the task wishes to unblock relative to
  15. * the time at which vTaskDelay() is called. For example, specifying a block
  16. * period of 100 ticks will cause the task to unblock 100 ticks after
  17. * vTaskDelay() is called. vTaskDelay() does not therefore provide a good method
  18. * of controlling the frequency of a periodic task as the path taken through the
  19. * code, as well as other task and interrupt activity, will effect the frequency
  20. * at which vTaskDelay() gets called and therefore the time at which the task
  21. * next executes. See vTaskDelayUntil() for an alternative API function designed
  22. * to facilitate fixed frequency execution. It does this by specifying an
  23. * absolute time (rather than a relative time) at which the calling task should
  24. * unblock.
  25. *
  26. * @param xTicksToDelay The amount of time, in tick periods, that
  27. * the calling task should block.
  28. *
  29. * Example usage:
  30. void vTaskFunction( void * pvParameters )
  31. {
  32. // Block for 500ms.
  33. const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
  34. for( ;; )
  35. {
  36. // Simply toggle the LED every 500ms, blocking between each toggle.
  37. vToggleLED();
  38. vTaskDelay( xDelay );
  39. }
  40. }
  41. * \defgroup vTaskDelay vTaskDelay
  42. * \ingroup TaskCtrl
  43. */
  44. void vTaskDelay( const TickType_t xTicksToDelay ) PRIVILEGED_FUNCTION;

官方的注释写得非常非常仔细,甚至于用法都写进了注释;

任务调用这个函数,传入一个 xTicksToDelay ,代表相对现在,对任务进行延时;这个的这个 xTicksToDelay 的单位是 Tick,比如,你 1ms 一个 Tick 的话,那么设置 50,就是延时 50ms;如果 10ms 一个 Tick 的话,设置 50,就是延时 500ms;

实际情况是,将当前任务阻塞,到时间的时候,再把它加入到就绪队列,参与调度;

简单的 Demo 如下所示:


   
   
  1. void vTaskFunction( void * pvParameters )
  2. {
  3. // Block for 250ms.
  4. const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );
  5. for( ;; )
  6. {
  7. // Simply toggle the LED every 250ms, blocking between each toggle.
  8. vToggleLED();
  9. vTaskDelay( xDelay250ms );
  10. }
  11. }

时序上比如:

 

1.1.2、Implement

知道了用法后,我们来看下 vTaskDelay 的实现,在 task.c 文件中:


   
   
  1. #if ( INCLUDE_vTaskDelay == 1 )
  2. void vTaskDelay( const TickType_t xTicksToDelay )
  3. {
  4. BaseType_t xAlreadyYielded = pdFALSE;
  5. /* 如果延时时间为0,则不会将当前任务加入延时列表 */
  6. if( xTicksToDelay > ( TickType_t ) 0U )
  7. {
  8. vTaskSuspendAll();
  9. {
  10. /* 将当前任务从就绪列表中移除,并根据当前系统节拍计数器值计算唤醒时间,然后将任务加入延时列表 */
  11. prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
  12. }
  13. xAlreadyYielded = xTaskResumeAll();
  14. }
  15. /* 强制执行一次上下文切换*/
  16. if( xAlreadyYielded == pdFALSE )
  17. {
  18. portYIELD_WITHIN_API();
  19. }
  20. }
  21. #endif /* INCLUDE_vTaskDelay */

vTaskDelay 的实现依赖于宏 INCLUDE_vTaskDelay ,必须定义这个宏,才能够使用这个函数;

先判空,如果入参,也就是需要延时的时间大于 0 才有效;

先调用 vTaskSuspendAll(); 来挂起调度器,暂时暂停调度;

然后调用 prvAddCurrentTaskToDelayedList 将当前的这个任务添加到 Delayed 链表;

这个函数稍后深入分析,这里先扩展分析一下几个链表:


   
   
  1. /* Lists for ready and blocked tasks. --------------------
  2. xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
  3. doing so breaks some kernel aware debuggers and debuggers that rely on removing
  4. the static qualifier. */
  5. PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*< Prioritised ready tasks. */
  6. PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
  7. PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
  8. PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
  9. 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. */
  10. 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 上:


   
   
  1. PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
  2. PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
  3. PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
  4. 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 的实现:


   
   
  1. static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
  2. {
  3. TickType_t xTimeToWake;
  4. const TickType_t xConstTickCount = xTickCount;
  5. #if( INCLUDE_xTaskAbortDelay == 1 )
  6. {
  7. /* About to enter a delayed list, so ensure the ucDelayAborted flag is
  8. reset to pdFALSE so it can be detected as having been set to pdTRUE
  9. when the task leaves the Blocked state. */
  10. pxCurrentTCB->ucDelayAborted = pdFALSE;
  11. }
  12. #endif
  13. /* Remove the task from the ready list before adding it to the blocked list
  14. as the same list item is used for both lists. */
  15. if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
  16. {
  17. /* The current task must be in a ready list, so there is no need to
  18. check, and the port reset macro can be called directly. */
  19. 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. */
  20. }
  21. else
  22. {
  23. mtCOVERAGE_TEST_MARKER();
  24. }
  25. #if ( INCLUDE_vTaskSuspend == 1 )
  26. {
  27. if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
  28. {
  29. /* Add the task to the suspended task list instead of a delayed task
  30. list to ensure it is not woken by a timing event. It will block
  31. indefinitely. */
  32. vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
  33. }
  34. else
  35. {
  36. /* Calculate the time at which the task should be woken if the event
  37. does not occur. This may overflow but this doesn't matter, the
  38. kernel will manage it correctly. */
  39. xTimeToWake = xConstTickCount + xTicksToWait;
  40. /* The list item will be inserted in wake time order. */
  41. listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
  42. if( xTimeToWake < xConstTickCount )
  43. {
  44. /* Wake time has overflowed. Place this item in the overflow
  45. list. */
  46. vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
  47. }
  48. else
  49. {
  50. /* The wake time has not overflowed, so the current block list
  51. is used. */
  52. vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
  53. /* If the task entering the blocked state was placed at the
  54. head of the list of blocked tasks then xNextTaskUnblockTime
  55. needs to be updated too. */
  56. if( xTimeToWake < xNextTaskUnblockTime )
  57. {
  58. xNextTaskUnblockTime = xTimeToWake;
  59. }
  60. else
  61. {
  62. mtCOVERAGE_TEST_MARKER();
  63. }
  64. }
  65. }
  66. }
  67. #else /* INCLUDE_vTaskSuspend */
  68. {
  69. /* Calculate the time at which the task should be woken if the event
  70. does not occur. This may overflow but this doesn't matter, the kernel
  71. will manage it correctly. */
  72. xTimeToWake = xConstTickCount + xTicksToWait;
  73. /* The list item will be inserted in wake time order. */
  74. listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
  75. if( xTimeToWake < xConstTickCount )
  76. {
  77. /* Wake time has overflowed. Place this item in the overflow list. */
  78. vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
  79. }
  80. else
  81. {
  82. /* The wake time has not overflowed, so the current block list is used. */
  83. vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
  84. /* If the task entering the blocked state was placed at the head of the
  85. list of blocked tasks then xNextTaskUnblockTime needs to be updated
  86. too. */
  87. if( xTimeToWake < xNextTaskUnblockTime )
  88. {
  89. xNextTaskUnblockTime = xTimeToWake;
  90. }
  91. else
  92. {
  93. mtCOVERAGE_TEST_MARKER();
  94. }
  95. }
  96. /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
  97. ( void ) xCanBlockIndefinitely;
  98. }
  99. #endif /* INCLUDE_vTaskSuspend */
  100. }

代码的逻辑是:

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

这个函数是绝对延时函数,可以用来做周期性任务(必须最高优先级才可以),它的函数原型为:


   
   
  1. /**
  2. * task. h
  3. * <pre>void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );</pre>
  4. *
  5. * INCLUDE_vTaskDelayUntil must be defined as 1 for this function to be available.
  6. * See the configuration section for more information.
  7. *
  8. * Delay a task until a specified time. This function can be used by periodic
  9. * tasks to ensure a constant execution frequency.
  10. *
  11. * This function differs from vTaskDelay () in one important aspect: vTaskDelay () will
  12. * cause a task to block for the specified number of ticks from the time vTaskDelay () is
  13. * called. It is therefore difficult to use vTaskDelay () by itself to generate a fixed
  14. * execution frequency as the time between a task starting to execute and that task
  15. * calling vTaskDelay () may not be fixed [the task may take a different path though the
  16. * code between calls, or may get interrupted or preempted a different number of times
  17. * each time it executes].
  18. *
  19. * Whereas vTaskDelay () specifies a wake time relative to the time at which the function
  20. * is called, vTaskDelayUntil () specifies the absolute (exact) time at which it wishes to
  21. * unblock.
  22. *
  23. * The constant portTICK_PERIOD_MS can be used to calculate real time from the tick
  24. * rate - with the resolution of one tick period.
  25. *
  26. * @param pxPreviousWakeTime Pointer to a variable that holds the time at which the
  27. * task was last unblocked. The variable must be initialised with the current time
  28. * prior to its first use (see the example below). Following this the variable is
  29. * automatically updated within vTaskDelayUntil ().
  30. *
  31. * @param xTimeIncrement The cycle time period. The task will be unblocked at
  32. * time *pxPreviousWakeTime + xTimeIncrement. Calling vTaskDelayUntil with the
  33. * same xTimeIncrement parameter value will cause the task to execute with
  34. * a fixed interface period.
  35. *
  36. * Example usage:
  37. <pre>
  38. // Perform an action every 10 ticks.
  39. void vTaskFunction( void * pvParameters )
  40. {
  41. TickType_t xLastWakeTime;
  42. const TickType_t xFrequency = 10;
  43. // Initialise the xLastWakeTime variable with the current time.
  44. xLastWakeTime = xTaskGetTickCount ();
  45. for( ;; )
  46. {
  47. // Wait for the next cycle.
  48. vTaskDelayUntil( &xLastWakeTime, xFrequency );
  49. // Perform action here.
  50. }
  51. }
  52. </pre>
  53. * \defgroup vTaskDelayUntil vTaskDelayUntil
  54. * \ingroup TaskCtrl
  55. */
  56. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) PRIVILEGED_FUNCTION;

两个入参:

pxPreviousWakeTime:初始化为当前的时间,后面便不用管了;

xTimeIncrement:任务的周期;

简单的 Demo 如下所示:


   
   
  1. void vTaskB( void * pvParameters )
  2. {
  3. static portTickType xLastWakeTime;
  4. const portTickType xFrequency = pdMS_TO_TICKS( 500);
  5. // 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同
  6. xLastWakeTime = xTaskGetTickCount();
  7. for( ;; )
  8. {
  9. /* 调用系统延时函数,周期性阻塞500ms */
  10. vTaskDelayUntil( &xLastWakeTime,xFrequency );
  11. // ...
  12. // 这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同
  13. // ...
  14. }
  15. }

 

1.2.2、Implement

它的实现如下所示:


   
   
  1. #if ( INCLUDE_vTaskDelayUntil == 1 )
  2. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
  3. {
  4. TickType_t xTimeToWake;
  5. BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
  6. configASSERT( pxPreviousWakeTime );
  7. configASSERT( ( xTimeIncrement > 0U ) );
  8. configASSERT( uxSchedulerSuspended == 0 );
  9. vTaskSuspendAll();
  10. {
  11. /* Minor optimisation. The tick count cannot change in this
  12. block. */
  13. const TickType_t xConstTickCount = xTickCount;
  14. /* Generate the tick time at which the task wants to wake. */
  15. xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
  16. if( xConstTickCount < *pxPreviousWakeTime )
  17. {
  18. /* The tick count has overflowed since this function was
  19. lasted called. In this case the only time we should ever
  20. actually delay is if the wake time has also overflowed,
  21. and the wake time is greater than the tick time. When this
  22. is the case it is as if neither time had overflowed. */
  23. if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
  24. {
  25. xShouldDelay = pdTRUE;
  26. }
  27. else
  28. {
  29. mtCOVERAGE_TEST_MARKER();
  30. }
  31. }
  32. else
  33. {
  34. /* The tick time has not overflowed. In this case we will
  35. delay if either the wake time has overflowed, and/or the
  36. tick time is less than the wake time. */
  37. if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
  38. {
  39. xShouldDelay = pdTRUE;
  40. }
  41. else
  42. {
  43. mtCOVERAGE_TEST_MARKER();
  44. }
  45. }
  46. /* Update the wake time ready for the next call. */
  47. *pxPreviousWakeTime = xTimeToWake;
  48. if( xShouldDelay != pdFALSE )
  49. {
  50. traceTASK_DELAY_UNTIL( xTimeToWake );
  51. /* prvAddCurrentTaskToDelayedList() needs the block time, not
  52. the time to wake, so subtract the current tick count. */
  53. prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
  54. }
  55. else
  56. {
  57. mtCOVERAGE_TEST_MARKER();
  58. }
  59. }
  60. xAlreadyYielded = xTaskResumeAll();
  61. /* Force a reschedule if xTaskResumeAll has not already done so, we may
  62. have put ourselves to sleep. */
  63. if( xAlreadyYielded == pdFALSE )
  64. {
  65. portYIELD_WITHIN_API();
  66. }
  67. else
  68. {
  69. mtCOVERAGE_TEST_MARKER();
  70. }
  71. }
  72. #endif /* INCLUDE_vTaskDelayUntil */

也是先挂起任务,最后恢复;

与 xTaskDelay 不同,它定义了几个变量:

*pxPreviousWakeTime 代表上一次解除阻塞执行任务的时间;

xConstTickCount 是当前的时间;

xTimeToWake 是下一次唤醒的时间;

首先还是判断 Tick 溢出的场景;

可以看到,在最后调用:

prvAddCurrentTaskToDelayedList

的时候,它每次都是动态的去调整 Delay 的时间:xTimeToWake - xConstTickCount,尽量做到任务执行开始的时间保持一致;

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值