FreeRTOS --(8)任务管理之空闲任务

创建完毕任务,启动调度器,任务控制,系统 SysTick 来临后判断是否需上下文切换;

如果没有其他任务执行的情况下,FreeRTOS 的 Idle 任务将被调度投入运行;

在启动调度器的时候,Idle 任务就被创建了,优先级为最低 0;


   
   
  1. void vTaskStartScheduler( void )
  2. {
  3. .....................
  4. xReturn = xTaskCreate( prvIdleTask,
  5. configIDLE_TASK_NAME,
  6. configMINIMAL_STACK_SIZE,
  7. ( void * ) NULL,
  8. portPRIVILEGE_BIT,
  9. &xIdleTaskHandle );
  10. .....................
  11. }

当某时刻所有优先级高于 Idle 任务的任务处于被阻塞或者部分被挂起的状态,此刻调度器会调度 Idle 任务运行,它的执行函数为:


   
   
  1. /*
  2. * -----------------------------------------------------------
  3. * The Idle task.
  4. * ----------------------------------------------------------
  5. *
  6. * The portTASK_FUNCTION() macro is used to allow port/compiler specific
  7. * language extensions. The equivalent prototype for this function is:
  8. *
  9. * void prvIdleTask( void *pvParameters );
  10. *
  11. */
  12. static portTASK_FUNCTION( prvIdleTask, pvParameters )
  13. {
  14. /* Stop warnings. */
  15. ( void ) pvParameters;
  16. /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
  17. SCHEDULER IS STARTED. **/
  18. /* In case a task that has a secure context deletes itself, in which case
  19. the idle task is responsible for deleting the task's secure context, if
  20. any. */
  21. portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
  22. for( ;; )
  23. {
  24. /* See if any tasks have deleted themselves - if so then the idle task
  25. is responsible for freeing the deleted task's TCB and stack. */
  26. prvCheckTasksWaitingTermination();
  27. #if ( configUSE_PREEMPTION == 0 )
  28. {
  29. /* If we are not using preemption we keep forcing a task switch to
  30. see if any other task has become available. If we are using
  31. preemption we don't need to do this as any task becoming available
  32. will automatically get the processor anyway. */
  33. taskYIELD();
  34. }
  35. #endif /* configUSE_PREEMPTION */
  36. #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
  37. {
  38. /* When using preemption tasks of equal priority will be
  39. timesliced. If a task that is sharing the idle priority is ready
  40. to run then the idle task should yield before the end of the
  41. timeslice.
  42. A critical region is not required here as we are just reading from
  43. the list, and an occasional incorrect value will not matter. If
  44. the ready list at the idle priority contains more than one task
  45. then a task other than the idle task is ready to execute. */
  46. if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
  47. {
  48. taskYIELD();
  49. }
  50. else
  51. {
  52. mtCOVERAGE_TEST_MARKER();
  53. }
  54. }
  55. #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
  56. #if ( configUSE_IDLE_HOOK == 1 )
  57. {
  58. extern void vApplicationIdleHook( void );
  59. /* Call the user defined function from within the idle task. This
  60. allows the application designer to add background functionality
  61. without the overhead of a separate task.
  62. NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
  63. CALL A FUNCTION THAT MIGHT BLOCK. */
  64. vApplicationIdleHook();
  65. }
  66. #endif /* configUSE_IDLE_HOOK */
  67. /* This conditional compilation should use inequality to 0, not equality
  68. to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
  69. user defined low power mode implementations require
  70. configUSE_TICKLESS_IDLE to be set to a value other than 1. */
  71. #if ( configUSE_TICKLESS_IDLE != 0 )
  72. {
  73. TickType_t xExpectedIdleTime;
  74. /* It is not desirable to suspend then resume the scheduler on
  75. each iteration of the idle task. Therefore, a preliminary
  76. test of the expected idle time is performed without the
  77. scheduler suspended. The result here is not necessarily
  78. valid. */
  79. xExpectedIdleTime = prvGetExpectedIdleTime();
  80. if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
  81. {
  82. vTaskSuspendAll();
  83. {
  84. /* Now the scheduler is suspended, the expected idle
  85. time can be sampled again, and this time its value can
  86. be used. */
  87. configASSERT( xNextTaskUnblockTime >= xTickCount );
  88. xExpectedIdleTime = prvGetExpectedIdleTime();
  89. /* Define the following macro to set xExpectedIdleTime to 0
  90. if the application does not want
  91. portSUPPRESS_TICKS_AND_SLEEP() to be called. */
  92. configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
  93. if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
  94. {
  95. traceLOW_POWER_IDLE_BEGIN();
  96. portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
  97. traceLOW_POWER_IDLE_END();
  98. }
  99. else
  100. {
  101. mtCOVERAGE_TEST_MARKER();
  102. }
  103. }
  104. ( void ) xTaskResumeAll();
  105. }
  106. else
  107. {
  108. mtCOVERAGE_TEST_MARKER();
  109. }
  110. }
  111. #endif /* configUSE_TICKLESS_IDLE */
  112. }
  113. }

Idle 任务也是一个无限循环:

1、调用 prvCheckTasksWaitingTermination() 判断是否有需要 Task 自己删除自己,如果有,那么在 Idle 任务中来回收这种类型的场景:


   
   
  1. static void prvCheckTasksWaitingTermination( void )
  2. {
  3. /** THIS FUNCTION IS CALLED FROM THE RTOS IDLE TASK **/
  4. #if ( INCLUDE_vTaskDelete == 1 )
  5. {
  6. TCB_t *pxTCB;
  7. /* uxDeletedTasksWaitingCleanUp is used to prevent taskENTER_CRITICAL()
  8. being called too often in the idle task. */
  9. while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
  10. {
  11. taskENTER_CRITICAL();
  12. {
  13. pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
  14. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
  15. --uxCurrentNumberOfTasks;
  16. --uxDeletedTasksWaitingCleanUp;
  17. }
  18. taskEXIT_CRITICAL();
  19. prvDeleteTCB( pxTCB );
  20. }
  21. }
  22. #endif /* INCLUDE_vTaskDelete */
  23. }

如果支持任务删除,而且有需要被删除的任务的话,进入临界区,取出要被删除的任务,更新当前任务个数和待删除任务个数,退出临界区,并调用 prvDeleteTCB() 接口来删除任务的资源,其实就是调用了 vPortFree( pxTCB->pxStack ); 和 vPortFree( pxTCB ); 来释放任务的 TCB 结构和堆栈;

2、如果定义了 configUSE_PREEMPTION 为 1(支持抢占),同时 configIDLE_SHOULD_YIELD 也为 1 (如果有与 Idle 任务相同优先级的任务,并且处于 Ready 状态,那么 Idle 任务将为其让路)的情况,Idle 任务直接调用 taskYIELD(); 引发一次调度,放弃 CPU;

3、如果使能了 configUSE_IDLE_HOOK,也就是用户的 Idle 钩子函数,则调用 vApplicationIdleHook;

4、如果使能了 configUSE_TICKLESS_IDLE,就意味着要进入低功耗场景,当然,既然都调用 Idle 任务了,进入低功耗理所应当;这里的 Tickless 的含义是:进入低功耗后,Systick 不在来中断,因为 Tick 心跳很频繁的话,处理器很快就被唤醒了,失去了低功耗的意义;

5、调用 prvGetExpectedIdleTime 获取距离下一个最近的阻塞任务的执行时间,与 当前的时间做减法,得到最大的可以进入低功耗的时间,当然这里只能判断阻塞在时间上的任务,对于事件,我们并不知道什么时候会来,也许是中断激活事件,不过这样要求中断能够唤醒处理器,否则中断无法得到及时处理,那么 RTOS 的实时任务的运行也得不到实时的保证;

6、如果获取得到的最大进入低功耗的时间 xExpectedIdleTime 大于了我们配置的期望睡眠的最小时间,也就是满足进入低功耗的条件,那么挂起调度器(因为马上要进入 Tickless),调用 portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); 进入低功耗;

7、portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) 的实现是和处理器相关,因为是带 port 前缀,每种处理器进入低功耗的方式不尽相同,即便是同一种处理器,进入低功耗也有几种模式,所以这里交给处理器相关 port.c 去实现;

8、唤醒后,一般的,原地继续执行,调用 xTaskResumeAll 恢复调度器;

针对 Cortex-M3 进入睡眠部分:


   
   
  1. #define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) \
  2. vPortSuppressTicksAndSleep( xExpectedIdleTime )

调用到了 vPortSuppressTicksAndSleep(xExpectedIdleTime)入参是期待睡眠的时间;也就是 Tick 个数:


   
   
  1. #if( configUSE_TICKLESS_IDLE == 1 )
  2. __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
  3. {
  4. uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
  5. TickType_t xModifiableIdleTime;
  6. /* Make sure the SysTick reload value does not overflow the counter. */
  7. /* 睡眠的最大值不能够超过处理器支持的最大值 */
  8. if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
  9. {
  10. xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
  11. }
  12. /* Stop the SysTick momentarily. The time the SysTick is stopped for
  13. is accounted for as best it can be, but using the tickless mode will
  14. inevitably result in some tiny drift of the time maintained by the
  15. kernel with respect to calendar time. */
  16. /* 禁止 SysTick */
  17. portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
  18. /* Calculate the reload value required to wait xExpectedIdleTime
  19. tick periods. -1 is used because this code will execute part way
  20. through one of the tick periods. */
  21. /* 计算将要配置到 SYSTICK 用于唤醒的值 */
  22. ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
  23. if( ulReloadValue > ulStoppedTimerCompensation )
  24. {
  25. ulReloadValue -= ulStoppedTimerCompensation;
  26. }
  27. /* Enter a critical section but don't use the taskENTER_CRITICAL()
  28. method as that will mask interrupts that should exit sleep mode. */
  29. /* 关闭中断 */
  30. __disable_irq();
  31. __dsb( portSY_FULL_READ_WRITE );
  32. __isb( portSY_FULL_READ_WRITE );
  33. /* If a context switch is pending or a task is waiting for the scheduler
  34. to be unsuspended then abandon the low power entry. */
  35. /* 再次 Check 有没有非 Idle 状态待执行的任务 */
  36. if( eTaskConfirmSleepModeStatus() == eAbortSleep )
  37. {
  38. /* Restart from whatever is left in the count register to complete
  39. this tick period. */
  40. /* 重新配置 SysTICK 终止睡眠流程 */
  41. portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
  42. /* Restart SysTick. */
  43. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
  44. /* Reset the reload register to the value required for normal tick
  45. periods. */
  46. portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
  47. /* Re-enable interrupts - see comments above __disable_irq() call
  48. above. */
  49. /* 打开中断 */
  50. __enable_irq();
  51. }
  52. else
  53. {
  54. /* Set the new reload value. */
  55. /* 将 SysTick 的时间配置为之前计算好的时间 */
  56. portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
  57. /* Clear the SysTick count flag and set the count value back to
  58. zero. */
  59. /* 清除当前 SysTick 的值 */
  60. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
  61. /* Restart SysTick. */
  62. /* 开启 SysTick */
  63. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
  64. /* Sleep until something happens. configPRE_SLEEP_PROCESSING() can
  65. set its parameter to 0 to indicate that its implementation contains
  66. its own wait for interrupt or wait for event instruction, and so wfi
  67. should not be executed again. However, the original expected idle
  68. time variable must remain unmodified, so a copy is taken. */
  69. xModifiableIdleTime = xExpectedIdleTime;
  70. configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
  71. /* 执行 WFI 睡眠 */
  72. if( xModifiableIdleTime > 0 )
  73. {
  74. __dsb( portSY_FULL_READ_WRITE );
  75. __wfi();
  76. __isb( portSY_FULL_READ_WRITE );
  77. }
  78. /* 此处为唤醒 */
  79. configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
  80. /* Re-enable interrupts to allow the interrupt that brought the MCU
  81. out of sleep mode to execute immediately. see comments above
  82. __disable_interrupt() call above. */
  83. /* 因为可能是其他中断唤醒的 WFI,立马开启中断,进入 ISR */
  84. __enable_irq();
  85. __dsb( portSY_FULL_READ_WRITE );
  86. __isb( portSY_FULL_READ_WRITE );
  87. /* Disable interrupts again because the clock is about to be stopped
  88. and interrupts that execute while the clock is stopped will increase
  89. any slippage between the time maintained by the RTOS and calendar
  90. time. */
  91. /* 关闭中断,做处理 */
  92. __disable_irq();
  93. __dsb( portSY_FULL_READ_WRITE );
  94. __isb( portSY_FULL_READ_WRITE );
  95. /* Disable the SysTick clock without reading the
  96. portNVIC_SYSTICK_CTRL_REG register to ensure the
  97. portNVIC_SYSTICK_COUNT_FLAG_BIT is not cleared if it is set. Again,
  98. the time the SysTick is stopped for is accounted for as best it can
  99. be, but using the tickless mode will inevitably result in some tiny
  100. drift of the time maintained by the kernel with respect to calendar
  101. time*/
  102. /* 重新配置 SysTick */
  103. portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );
  104. /* Determine if the SysTick clock has already counted to zero and
  105. been set back to the current reload value (the reload back being
  106. correct for the entire expected idle time) or if the SysTick is yet
  107. to count to zero (in which case an interrupt other than the SysTick
  108. must have brought the system out of sleep mode). */
  109. if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
  110. {
  111. uint32_t ulCalculatedLoadValue;
  112. /* The tick interrupt is already pending, and the SysTick count
  113. reloaded with ulReloadValue. Reset the
  114. portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
  115. period. */
  116. ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
  117. /* Don't allow a tiny value, or values that have somehow
  118. underflowed because the post sleep hook did something
  119. that took too long. */
  120. if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
  121. {
  122. ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
  123. }
  124. portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
  125. /* As the pending tick will be processed as soon as this
  126. function exits, the tick value maintained by the tick is stepped
  127. forward by one less than the time spent waiting. */
  128. ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
  129. }
  130. else
  131. {
  132. /* Something other than the tick interrupt ended the sleep.
  133. Work out how long the sleep lasted rounded to complete tick
  134. periods (not the ulReload value which accounted for part
  135. ticks). */
  136. ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
  137. /* How many complete tick periods passed while the processor
  138. was waiting? */
  139. ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
  140. /* The reload value is set to whatever fraction of a single tick
  141. period remains. */
  142. portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
  143. }
  144. /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
  145. again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
  146. value. */
  147. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
  148. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
  149. vTaskStepTick( ulCompleteTickPeriods );
  150. portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
  151. /* Exit with interrpts enabled. */
  152. __enable_irq();
  153. }
  154. }
  155. #endif /* #if configUSE_TICKLESS_IDLE */

内容不少,慢慢看即可:

0、入参是期望睡眠的 Tick 数目,这里不用这么抽象,比如配置的 Tick 周期为 1ms,那么这里就是 ms 数;

这里有 3 个全局变量需要说明一下:


   
   
  1. /*
  2. * The number of SysTick increments that make up one tick period.
  3. */
  4. #if( configUSE_TICKLESS_IDLE == 1 )
  5. static uint32_t ulTimerCountsForOneTick = 0;
  6. #endif /* configUSE_TICKLESS_IDLE */
  7. /*
  8. * The maximum number of tick periods that can be suppressed is limited by the
  9. * 24 bit resolution of the SysTick timer.
  10. */
  11. #if( configUSE_TICKLESS_IDLE == 1 )
  12. static uint32_t xMaximumPossibleSuppressedTicks = 0;
  13. #endif /* configUSE_TICKLESS_IDLE */
  14. /*
  15. * Compensate for the CPU cycles that pass while the SysTick is stopped (low
  16. * power functionality only.
  17. */
  18. #if( configUSE_TICKLESS_IDLE == 1 )
  19. static uint32_t ulStoppedTimerCompensation = 0;
  20. #endif /* configUSE_TICKLESS_IDLE */

在开启调度器,配置 SysTick 的时候,这 3 个全局变量被赋值初始化:


   
   
  1. #if( configUSE_TICKLESS_IDLE == 1 )
  2. {
  3. ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
  4. xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
  5. ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
  6. }
  7. #endif /* configUSE_TICKLESS_IDLE */

ulTimerCountsForOneTick :代表了一个 SysTick 配置到寄存器的 Tick 的 Count;换句话来说,就是产生 1ms 的 SysTick 中断,需要配置给寄存器的值;

xMaximumPossibleSuppressedTicks:代表了在溢出之前,硬件最大支持多少个 Tick;(因为 Cortex-M3 处理器配置给硬件的 Tick Count 最大是 24 bit 的,所以这里用 24bit 的全 1 除以 ulTimerCountsForOneTick );

ulStoppedTimerCompensation:代表了一个时钟补偿的因子,这里是固定的 45;

1、首先判断期望睡眠的值是否大于了处理器的 xMaximumPossibleSuppressedTicks,如果是,那么将睡眠的值限定在 xMaximumPossibleSuppressedTicks

2、禁止 SysTick 模块;

3、计算新的 SysTick 的 Load 值,这里的原理是,因为需要让处理器进入 WFI (Waiting For Interrupt),进入 WFI 后,处理器可以被中断唤醒,并继续执行;其实这里的 Tickless 并不是真的一直关闭了 SysTick ,而是将睡眠的时间配置到了 SysTick 中,所以这里才会限制睡眠时间;


   
   
  1. ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + \
  2. ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
  3. if( ulReloadValue > ulStoppedTimerCompensation )
  4. {
  5. ulReloadValue -= ulStoppedTimerCompensation;
  6. }

使用当前的 SysTick 的值,加上睡眠的时间乘以每个 Tick 的 Count,计算出即将配置到 SysTick 硬件寄存器的值;然后对补偿因子做减法;

4、__disable_irq,关闭中断,刷指令和数据流水线;

5、判断是否还有需要被执行的任务,如果有,那么重新配置 SysTick 还是为 1ms,并使能 SysTick,开启中断,退出睡眠逻辑;

6、如果没有要被执行的任务,将计算出来最大的睡眠时间 ulReloadValue 配置进 SysTick 计数器寄存器,开启 SysTick,此刻的 SysTick 便是睡眠的时间;

7、进入 WFI 睡眠;

8、如果有中断,则对 WFI 原地唤醒,继续执行,这里可能是 SysTick 的 IRQ 唤醒,也可能是其他中断唤醒;

9、__enable_irq,开启中断,因为可能是被其他 IRQ 唤醒,这里需要立马执行 ISR;

10、__disable_irq,关闭中断,刷指令和数据流水线,因为下面的配置不允许被打断;

11、重新配置 SysTick 成为 OS 的心跳(也就是 1ms),并使能 SysTick;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值