基本概念

FreeRTOS 是一个支持多任务的操作系统。

在 FreeRTOS 中,任务可以使用或等待 CPU、使用内存空间等系统资源,并独立于其 它 任 务 运 行 , 任 何 数 量 的 任 务 可 以 共 享 同 一 个 优 先 级 ,

如果 宏 configUSE_TIME_SLICING 定义为 1,处于就绪态的多个相同优先级任务将会以时间片切 换的方式共享处理器。

任务切换

在任务切换的时候,保存上下文的环境(寄存器值、栈堆内容),所以每个任务都要有自己的任务栈,来保存这些信息。

FreeRTOS使用的是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。

任务调度器

不同优先级的调度

调度器是基于抢占式调度,在系统中除了中断处理函数,调度器上锁部分的代码和禁止中断代码是不可抢占的之外,系统的其他部分都是可以抢占的。

系统理论上可以支持无数个优先级,但一般会根据mcu的性能限制优先级数目,一般是8或32个。

0为最低优先级,等级依次上涨。

寻找优先级的方法
通用方法 在就绪链表中查找从高优先级往低查找 uxTopPriority,因为在创建任务的时候已经将优先级进行排序,查找到的第一个 uxTopPriority 就是我们需要的任务,然后通过 uxTopPriority 获取对 应的任务控制块。
计算前导零

前导零

计算一个变量从高位开始第一次出现1的位前面零的个数。

步骤:

  1. 设置一个32位的变量
    1. 该变量的每个位号对应的是任务的优先级
    2. 担任是一个优先级位图表的角色
  2. 任务就绪时,则将对应的位置置1,反之清零
  3. 利用前导计算指令可以很快算出就绪任务的最高优先级

相同优先级的调度

相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。

任务调度的原则

任务调度的原则是一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态)。

任务状态迁移

就绪 该任务在就绪列表中,就绪的任务已经具备执行的能力,只等 待调度器进行调度,新创建的任务会初始化为就绪态。
运行 该状态表明任务正在执行,此时它占用处理器,FreeRTOS 调 度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
阻塞 如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、任务 正在等待信号量、读写队列或者等待读写事件等。
挂起态 处于挂起态的任务对调度器而言是不可见的

任务进入挂起状态:唯一办法就是调用 vTaskSuspend()函数;

挂起状态的任务恢复:的 唯 一 途 径 就 是 调 用 vTaskResume() 或 vTaskResumeFromISR()函数,

挂起与阻塞态的区别

当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到我们调用恢复任务的 API 函数;任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。

常用任务函数

任务挂起函数_vTaskSuspend()

挂起指定任务。被挂起的任务绝不会得到 CPU 的使用权,不管该任务具有什么优先级。

任务可以通过调用 vTaskSuspend()函数都可以将处于任何状态的任务挂起,被挂起的 任务得不到 CPU 的使用权,也不会参与调度,它相对于调度器而言是不可见的,除非它从 挂起态中解除。

  1. #if ( INCLUDE\_vTaskSuspend == 1 )
  2. void vTaskSuspend( TaskHandle\_t xTaskToSuspend )
  3. {
  4. TCB\_t \*pxTCB;
  5. taskENTER\_CRITICAL();
  6. {
  7. /\* 如果在此处传递 null,那么它正在被挂起的正在运行的任务。 \*/
  8. pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
  9. traceTASK\_SUSPEND( pxTCB );
  10. /\* 从就绪/阻塞列表中删除任务并放入挂起列表中。 \*/
  11. if ( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType\_t ) 0 ) {
  12. taskRESET\_READY\_PRIORITY( pxTCB->uxPriority );
  13. } else {
  14. mtCOVERAGE\_TEST\_MARKER();
  15. }
  16. /\* 如果任务在等待事件,也从等待事件列表中移除 \*/
  17. if ( listLIST\_ITEM\_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) {
  18. ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
  19. } else {
  20. mtCOVERAGE\_TEST\_MARKER();
  21. }
  22. /\* 将任务状态添加到挂起列表中 \*/
  23. vListInsertEnd( &xSuspendedTaskList,&(pxTCB->xStateListItem));
  24. }
  25. taskEXIT\_CRITICAL();
  26. if ( xSchedulerRunning != pdFALSE ) {
  27. /\* 重置下一个任务的解除阻塞时间。
  28. 重新计算一下还要多长时间执行下一个任务。
  29. 如果下个任务的解锁,刚好是被挂起的那个任务,
  30. 那么变量 NextTaskUnblockTime 就不对了,
  31. 所以要重新从延时列表中获取一下。\*/
  32. taskENTER\_CRITICAL();
  33. {
  34. prvResetNextTaskUnblockTime();
  35. }
  36. taskEXIT\_CRITICAL();
  37. } else {
  38. mtCOVERAGE\_TEST\_MARKER();
  39. }
  40. if ( pxTCB == pxCurrentTCB ) {
  41. if ( xSchedulerRunning != pdFALSE ) { (8)
  42. /\* 当前的任务已经被挂起。 \*/
  43. configASSERT( uxSchedulerSuspended == 0 );
  44. /\* 调度器在运行时,如果这个挂起的任务是当前任务,立即切换任务。 \*/
  45. portYIELD\_WITHIN\_API();
  46. } else { (9)
  47. /\* 调度器未运行(xSchedulerRunning == pdFALSE ), pxCurrentTCB 指向的任务刚刚被暂停,
  48. 所以必须调整 pxCurrentTCB 以指向其他任务。 首先调用函数 listCURRENT\_LIST\_LENGTH()
  49. 判断一下系统中所有的任务是不是都被挂起了, 也就是查看列表 xSuspendedTaskList
  50. 的长度是不是等于 uxCurrentNumberOfTasks 事实上并不会发生这种情况,
  51. 因为空闲任务是不允许被挂起和阻塞的,必须保证系统中无论如何都有一个任务可以运行\*/
  52. if ( listCURRENT\_LIST\_LENGTH( &xSuspendedTaskList )
  53. == uxCurrentNumberOfTasks ) { (10)
  54. /\* 没有其他任务准备就绪,因此将 pxCurrentTCB 设置回 NULL
  55. 以便在创建下一个任务时 pxCurrentTCB 将被设置为指向它,
  56. 实际上并不会执行到这里 \*/
  57. pxCurrentTCB = NULL; (11)
  58. } else {
  59. /\* 有其他任务,则切换到其他任务 \*/
  60. vTaskSwitchContext(); (12)
  61. }
  62. }
  63. } else {
  64. mtCOVERAGE\_TEST\_MARKER();
  65. }
  66. }
  67. #endif /\* INCLUDE\_vTaskSuspend \*/
  68. /\*---------------------------------

任务挂起函数_vTaskSuspendAll()

挂起所有任务(相当于挂起任务调度器)

调度器被挂起后则不能进行上下文切换,但是中断还是使能的。

当调 度器被挂起的时候,如果有中断需要进行上下文切换, 那么这个中断将会被挂起,在调度器恢复之后才响应这个中断。

  1. void vTaskSuspendAll( void )
  2. {
  3. ++uxSchedulerSuspended;
  4. }

任务恢复函数_vTaskResume()

任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行。

如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一 位,那么系统将进行任务上下文的切换。

  1. #if ( INCLUDE\_vTaskSuspend == 1 )
  2. void vTaskResume( TaskHandle\_t xTaskToResume ) (2)
  3. {
  4. /\* 根据 xTaskToResume 获取对应的任务控制块 \*/
  5. TCB\_t \* const pxTCB = ( TCB\_t \* ) xTaskToResume; (3)
  6. /\* 检查要恢复的任务是否被挂起,
  7. 如果没被挂起,恢复调用任务没有意义 \*/
  8. configASSERT( xTaskToResume ); (4)
  9. /\* 该参数不能为 NULL
  10. 同时也无法恢复当前正在执行的任务,
  11. 因为当前正在运行的任务不需要恢复,
  12. 只能恢复处于挂起态的任务
  13. \*/
  14. if ( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ) { (5)
  15. /\* 进入临界区 \*/
  16. taskENTER\_CRITICAL(); (6)
  17. {
  18. if ( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) { (7)
  19. traceTASK\_RESUME( pxTCB );
  20. /\* 由于我们处于临界区,
  21. 即使任务被挂起,我们也可以访问任务的状态列表。
  22. 将要恢复的任务从挂起列表中删除 \*/
  23. ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); (8)
  24. /\* 将要恢复的任务添加到就绪列表中去 \*/
  25. prvAddTaskToReadyList( pxTCB ); (9)
  26. /\* 如果刚刚恢复的任务优先级比当前任务优先级更高
  27. 则需要进行任务的切换 \*/
  28. if ( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){
  29. /\* 因为恢复的任务在当前情况下的优先级最高
  30. 调用 taskYIELD\_IF\_USING\_PREEMPTION()进行一次任务切换\*/
  31. taskYIELD\_IF\_USING\_PREEMPTION(); (10)
  32. } else {
  33. mtCOVERAGE\_TEST\_MARKER();
  34. }
  35. } else {
  36. mtCOVERAGE\_TEST\_MARKER();
  37. }
  38. }
  39. taskEXIT\_CRITICAL(); (11)
  40. /\* 退出临界区 \*/
  41. } else {
  42. mtCOVERAGE\_TEST\_MARKER();
  43. }
  44. }
  45. #endif /\* INCLUDE\_vTaskSuspend \*/
  46. /\*-----------------------------------------------------------\*/

任务恢复函数_xTaskResumeFromISR()

xTaskResumeFromISR()与 vTaskResume()一样都是用于恢复被挂起的任务,不一样的是 xTaskResumeFromISR() 专 门 用 在 中 断 服 务 程 序 中 。

无 论 通 过 调 用 一 次 或 多 次 vTaskSuspend()函数而被挂起的任务,也只需调用一次 xTaskResumeFromISR()函数即可解挂 。

boundary-start —-启用条件—-

把 INCLUDE_vTaskSuspend 和 INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。

boundary-end ———

  1. #if ( ( INCLUDE\_xTaskResumeFromISR == 1 ) && ( INCLUDE\_vTaskSuspend == 1 ) )
  2. BaseType\_t xTaskResumeFromISR( TaskHandle\_t xTaskToResume ) (1)
  3. {
  4. BaseType\_t xYieldRequired = pdFALSE; (2)
  5. TCB\_t \* const pxTCB = ( TCB\_t \* ) xTaskToResume; (3)
  6. UBaseType\_t uxSavedInterruptStatus; (4)
  7. configASSERT( xTaskToResume ); (5)
  8. portASSERT\_IF\_INTERRUPT\_PRIORITY\_INVALID();
  9. uxSavedInterruptStatus = portSET\_INTERRUPT\_MASK\_FROM\_ISR(); (6)
  10. {
  11. if ( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) { (7)
  12. traceTASK\_RESUME\_FROM\_ISR( pxTCB );
  13. /\* 检查可以访问的就绪列表,检查调度器是否被挂起 \*/
  14. if ( uxSchedulerSuspended == ( UBaseType\_t ) pdFALSE ) {(8)
  15. /\* 如果刚刚恢复的任务优先级比当前任务优先级更高
  16. 需要进行一次任务的切换
  17. xYieldRequired = pdTRUE 表示需要进行任务切换\*/
  18. if ( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) {(9)
  19. xYieldRequired = pdTRUE;
  20. } else {
  21. mtCOVERAGE\_TEST\_MARKER();
  22. }
  23. /\* 可以访问就绪列表,
  24. 因此可以将任务从挂起列表删除
  25. 然后添加到就绪列表中。\*/
  26. ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); (10)
  27. prvAddTaskToReadyList( pxTCB );
  28. } else {
  29. /\* 无法访问就绪列表,
  30. 因此任务将被添加到待处理的就绪列表中,
  31. 直到调度器被恢复再进行任务的处理。\*/
  32. vListInsertEnd( &( xPendingReadyList ),
  33. &( pxTCB->xEventListItem ) ); (11)
  34. }
  35. } else {
  36. mtCOVERAGE\_TEST\_MARKER();
  37. }
  38. }
  39. portCLEAR\_INTERRUPT\_MASK\_FROM\_ISR( uxSavedInterruptStatus ); (12)
  40. return xYieldRequired; (13)
  41. }
  42. #endif
  43. /\*-----------------------------------------------------------\*/

任务恢复函数_xTaskResumeAll()

调用了 vTaskSuspendAll()函数将调度 器挂起,想要恢复调度器的时候我们就需要调用 xTaskResumeAll()函数,

  1. 1 /\*----------------------------------------------------------\*/
  2. 2
  3. 3 BaseType\_t xTaskResumeAll( void )
  4. 4 {
  5. 5 TCB\_t \*pxTCB = NULL;
  6. 6 BaseType\_t xAlreadyYielded = pdFALSE;
  7. 7
  8. 8 /\* 如果 uxSchedulerSuspended 0
  9. 9 则此函数与先前对 vTaskSuspendAll()的调用不匹配,
  10. 10 不需要调用 xTaskResumeAll()恢复调度器。 \*/
  11. 11 configASSERT( uxSchedulerSuspended ); (1)
  12. 12
  13. 13
  14. 14
  15. 15 /\* 屏蔽中断 \*/
  16. taskENTER\_CRITICAL(); (2)
  17. 18 {
  18. 19 --uxSchedulerSuspended; (3)
  19. 20
  20. 21 if ( uxSchedulerSuspended == ( UBaseType\_t ) pdFALSE ) { (4)
  21. 22 if ( uxCurrentNumberOfTasks > ( UBaseType\_t ) 0U ) {
  22. 23 /\* 将任何准备好的任务从待处理就绪列表
  23. 24 移动到相应的就绪列表中。 \*/
  24. 25 while ( listLIST\_IS\_EMPTY( &xPendingReadyList ) == pdFALSE ) {(5)
  25. 26 pxTCB = ( TCB\_t \* ) listGET\_OWNER\_OF\_HEAD\_ENTRY
  26. 27 ( ( &xPendingReadyList ) );
  27. 28 ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
  28. 29 ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
  29. 30 prvAddTaskToReadyList( pxTCB );
  30. 31
  31. 32 /\* 如果移动的任务的优先级高于当前任务,
  32. 33 需要进行一次任务的切换
  33. 34 xYieldPending = pdTRUE 表示需要进行任务切换 \*/
  34. 35 if ( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { (6)
  35. 36 xYieldPending = pdTRUE;
  36. 37 } else {
  37. 38 mtCOVERAGE\_TEST\_MARKER();
  38. 39 }
  39. 40 }
  40. 41
  41. 42 if ( pxTCB != NULL ) {
  42. 43 /\* 在调度器被挂起时,任务被解除阻塞,
  43. 44 这可能阻止了重新计算下一个解除阻塞时间,
  44. 45 在这种情况下,重置下一个任务的解除阻塞时间 \*/
  45. 46
  46. 47 prvResetNextTaskUnblockTime(); (7)
  47. 48 }
  48. 49
  49. 50 /\* 如果在调度器挂起这段时间产生滴答定时器的计时
  50. 51 并且在这段时间有任务解除阻塞,由于调度器的挂起导致
  51. 52 没法切换任务,当恢复调度器的时候应立即处理这些任务。
  52. 53 这样确保了滴答定时器的计数不会滑动,
  53. 54 并且任何在延时的任务都会在正确的时间恢复。 \*/
  54. 55 {
  55. 56 UBaseType\_t uxPendedCounts = uxPendedTicks;
  56. 57
  57. 58 if ( uxPendedCounts > ( UBaseType\_t ) 0U ) { (8)
  58. 59 do {
  59. 60 if ( xTaskIncrementTick() != pdFALSE ) {(9)
  60. 61 xYieldPending = pdTRUE;
  61. 62 } else {
  62. 63 mtCOVERAGE\_TEST\_MARKER();
  63. 64 }
  64. 65 --uxPendedCounts;
  65. 66 } while ( uxPendedCounts > ( UBaseType\_t ) 0U );
  66. 67
  67. 68 uxPendedTicks = 0;
  68. 69 } else {
  69. 70 mtCOVERAGE\_TEST\_MARKER();
  70. 71 }
  71. 72 }
  72. 73
  73. 74 if ( xYieldPending != pdFALSE ) {
  74. 75 #if( configUSE\_PREEMPTION != 0 )
  75. 76 {
  76. 77 xAlreadyYielded = pdTRUE;
  77. 78 }
  78. 79 #endif
  79. 80 taskYIELD\_IF\_USING\_PREEMPTION(); (10)
  80. 81 } else {
  81. 82 mtCOVERAGE\_TEST\_MARKER();
  82. 83 }
  83. 84 }
  84. 85 } else {
  85. 86 mtCOVERAGE\_TEST\_MARKER();
  86. 87 }
  87. 88 }
  88. 89 taskEXIT\_CRITICAL(); (11)
  89. 90
  90. 91 return xAlreadyYielded;
  91. 92 }

任务删除函数 _vTaskDelete()

vTaskDelete()用于删除一个任务。

当一个任务删除另外一个任务时,形参为要删除任务创建时返回的任务句柄,

如果是删除自身, 则形参为 NULL。

  1. 1 /\*-----------------------------------------------------------\*/
  2. 2
  3. 3 #if ( INCLUDE\_vTaskDelete == 1 )
  4. 4
  5. 5 void vTaskDelete( TaskHandle\_t xTaskToDelete ) (1)
  6. 6 {
  7. 7 TCB\_t \*pxTCB;
  8. 8
  9. 9 taskENTER\_CRITICAL();
  10. 10 {
  11. 11 /\* 获取任务控制块,如果 xTaskToDelete null
  12. 12 则删除任务自身 \*/
  13. 13 pxTCB = prvGetTCBFromHandle( xTaskToDelete ); (2)
  14. 14
  15. 15 /\* 将任务从就绪列表中移除 \*/
  16. 16 if ( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType\_t ) 0 ) {
  17. 17 /\* 清除任务的就绪优先级变量中的标志位 \*/
  18. 18 taskRESET\_READY\_PRIORITY( pxTCB->uxPriority ); (3)
  19. 19 } else {
  20. 20 mtCOVERAGE\_TEST\_MARKER();
  21. 21 }
  22. 22
  23. 23 /\* 如果当前任务在等待事件,那么将任务从事件列表中移除 \*/
  24. 24 if ( listLIST\_ITEM\_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) {
  25. 25 ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); (4)
  26. 26 } else {
  27. 27 mtCOVERAGE\_TEST\_MARKER();
  28. 28 }
  29. 29
  30. 30 uxTaskNumber++;
  31. 31
  32. 32 if ( pxTCB == pxCurrentTCB ) {
  33. 33 /\*
  34. 34 任务正在删除自己。 这不能在任务本身内完成,
  35. 35 因为需要上下文切换到另一个任务。
  36. 36 将任务放在结束列表中。空闲任务会检查结束
  37. 37 列表并释放掉删除的任务控制块
  38. 38 和已删除任务的堆栈的任何内存。\*/
  39. 39 vListInsertEnd( &xTasksWaitingTermination, (5)
  40. 40 &( pxTCB->xStateListItem ) );
  41. 41
  42. 42 /\* 增加 uxDeletedTasksWaitingCleanUp 变量,
  43. 43 记录有多少个任务需要释放内存,
  44. 44 以便空闲任务知道有一个已删除的任务,然后进行内存释放
  45. 45 空闲任务会检查结束列表 xTasksWaitingTermination \*/
  46. 46 ++uxDeletedTasksWaitingCleanUp; (6)
  47. 47
  48. 48 /\* 任务删除钩子函数 \*/
  49. 49 portPRE\_TASK\_DELETE\_HOOK( pxTCB, &xYieldPending );
  50. 50 } else {
  51. 51 /\* 当前任务数减一,uxCurrentNumberOfTasks 是全局变量
  52. 52 用于记录当前的任务数量 \*/
  53. 53 --uxCurrentNumberOfTasks; (7)
  54. 54 /\* 删除任务控制块 \*/
  55. 55 prvDeleteTCB( pxTCB ); (8)
  56. 56
  57. 57 /\* 重置下一个任务的解除阻塞时间。重新计算一下
  58. 58 还要多长时间执行下一个任务,如果下个任务的解锁,
  59. 59 刚好是被删除的任务,那么这就是不正确的,
  60. 60 因为删除的任务对调度器而言是不可见的,
  61. 61 所以调度器是无法对删除的任务进行调度,
  62. 62 所以要重新从延时列表中获取下一个要解除阻塞的任务。
  63. 63 它是从延时列表的头部来获取的任务 TCB,延时列表是按延时时间排序的\*/
  64. 64 prvResetNextTaskUnblockTime(); (9)
  65. 65 }
  66. 66
  67. 67 traceTASK\_DELETE( pxTCB );
  68. 68 }
  69. 69 taskEXIT\_CRITICAL(); (10)
  70. 70
  71. 71 /\* 如删除的是当前的任务,则需要发起一次任务切换 \*/
  72. 72 if ( xSchedulerRunning != pdFALSE ) {
  73. 73 if ( pxTCB == pxCurrentTCB ) {
  74. 74 configASSERT( uxSchedulerSuspended == 0 );
  75. 75 portYIELD\_WITHIN\_API(); (11)
  76. 76 } else {
  77. 77 mtCOVERAGE\_TEST\_MARKER();
  78. 78 }
  79. 79 }
  80. 80 }
  81. 81
  82. 82 #endif /\* INCLUDE\_vTaskDelete \*/
  83. 83 /\*-----------------------------------------------------------\*/

任务延时函数 _vTaskDelay()

vTaskDelay()用于阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻塞态的任务将让出 CPU 资源。

延时的时长由形参 xTicksToDelay 决定,单位为系统节拍周期, 比如系统的时钟节拍周期为 1ms,那么调用 vTaskDelay(1)的延时时间则为 1ms。

  1. #if ( INCLUDE\_vTaskDelete == 1 )
  2. 4
  3. 5 void vTaskDelete( TaskHandle\_t xTaskToDelete ) (1)
  4. 6 {
  5. 7 TCB\_t \*pxTCB;
  6. 8
  7. 9 taskENTER\_CRITICAL();
  8. 10 {
  9. 11 /\* 获取任务控制块,如果 xTaskToDelete null
  10. 12 则删除任务自身 \*/
  11. 13 pxTCB = prvGetTCBFromHandle( xTaskToDelete ); (2)
  12. 14
  13. 15 /\* 将任务从就绪列表中移除 \*/
  14. 16 if ( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType\_t ) 0 ) {
  15. 17 /\* 清除任务的就绪优先级变量中的标志位 \*/
  16. 18 taskRESET\_READY\_PRIORITY( pxTCB->uxPriority ); (3)
  17. 19 } else {
  18. 20 mtCOVERAGE\_TEST\_MARKER();
  19. 21 }
  20. 22
  21. 23 /\* 如果当前任务在等待事件,那么将任务从事件列表中移除 \*/
  22. 24 if ( listLIST\_ITEM\_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) {
  23. 25 ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); (4)
  24. 26 } else {
  25. 27 mtCOVERAGE\_TEST\_MARKER();
  26. 28 }
  27. 29
  28. 30 uxTaskNumber++;
  29. 31
  30. 32 if ( pxTCB == pxCurrentTCB ) {
  31. 33 /\*
  32. 34 任务正在删除自己。 这不能在任务本身内完成,
  33. 35 因为需要上下文切换到另一个任务。
  34. 36 将任务放在结束列表中。空闲任务会检查结束
  35. 37 列表并释放掉删除的任务控制块
  36. 38 和已删除任务的堆栈的任何内存。\*/
  37. 39 vListInsertEnd( &xTasksWaitingTermination, (5)
  38. 40 &( pxTCB->xStateListItem ) );
  39. 41
  40. 42 /\* 增加 uxDeletedTasksWaitingCleanUp 变量,
  41. 43 记录有多少个任务需要释放内存,
  42. 44 以便空闲任务知道有一个已删除的任务,然后进行内存释放
  43. 45 空闲任务会检查结束列表 xTasksWaitingTermination \*/
  44. 46 ++uxDeletedTasksWaitingCleanUp; (6)
  45. 47
  46. 48 /\* 任务删除钩子函数 \*/
  47. 49 portPRE\_TASK\_DELETE\_HOOK( pxTCB, &xYieldPending );
  48. 50 } else {
  49. 51 /\* 当前任务数减一,uxCurrentNumberOfTasks 是全局变量
  50. 52 用于记录当前的任务数量 \*/
  51. 53 --uxCurrentNumberOfTasks; (7)
  52. 54 /\* 删除任务控制块 \*/
  53. 55 prvDeleteTCB( pxTCB ); (8)
  54. 56
  55. 57 /\* 重置下一个任务的解除阻塞时间。重新计算一下
  56. 58 还要多长时间执行下一个任务,如果下个任务的解锁,
  57. 59 刚好是被删除的任务,那么这就是不正确的,
  58. 60 因为删除的任务对调度器而言是不可见的,
  59. 61 所以调度器是无法对删除的任务进行调度,
  60. 62 所以要重新从延时列表中获取下一个要解除阻塞的任务。
  61. 63 它是从延时列表的头部来获取的任务 TCB,延时列表是按延时时间排序的\*/
  62. 64 prvResetNextTaskUnblockTime(); (9)
  63. 65 }
  64. 66
  65. 67 traceTASK\_DELETE( pxTCB );
  66. 68 }
  67. 69 taskEXIT\_CRITICAL(); (10)
  68. 70
  69. 71 /\* 如删除的是当前的任务,则需要发起一次任务切换 \*/
  70. 72 if ( xSchedulerRunning != pdFALSE ) {
  71. 73 if ( pxTCB == pxCurrentTCB ) {
  72. 74 configASSERT( uxSchedulerSuspended == 0 );
  73. 75 portYIELD\_WITHIN\_API(); (11)
  74. 76 } else {
  75. 77 mtCOVERAGE\_TEST\_MARKER();
  76. 78 }
  77. 79 }
  78. 80 }
  79. 81
  80. 82 #endif /\* INCLUDE\_vTaskDelete \*/
  81. 83 /\*-----------------------------------------------------------\*/

任务延时函数 _vTaskDelayUntil()

绝对延时常用于较精确的周期运行任务,

比如我有一个任务,希望它以固定频率定期执行, 而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的,

  1. 1 #if ( INCLUDE\_vTaskDelayUntil == 1 )
  2. 2
  3. 3 void vTaskDelayUntil( TickType\_t \* const pxPreviousWakeTime, (1)
  4. 4 const TickType\_t xTimeIncrement ) (2) 5 {
  5. 6 TickType\_t xTimeToWake;
  6. 7 BaseType\_t xAlreadyYielded, xShouldDelay = pdFALSE;
  7. configASSERT( pxPreviousWakeTime );
  8. 10 configASSERT( ( xTimeIncrement > 0U ) );
  9. 11 configASSERT( uxSchedulerSuspended == 0 );
  10. 12
  11. 13 vTaskSuspendAll();
  12. 14 {
  13. 15 /\* 获取开始进行延时的时间点 \*/
  14. 16 const TickType\_t xConstTickCount = xTickCount; (3)
  15. 17
  16. 18 /\* 计算延时到达的时间,也就是唤醒任务的时间 \*/
  17. 19 xTimeToWake = \*pxPreviousWakeTime + xTimeIncrement; (4)
  18. 20
  19. 21 /\* pxPreviousWakeTime 中保存的是上次唤醒时间,
  20. 22 唤醒后需要一定时间执行任务主体代码,
  21. 23 如果上次唤醒时间大于当前时间,说明节拍计数器溢出了\*/
  22. 24 if ( xConstTickCount < \*pxPreviousWakeTime ) { (5)
  23. 25 /\* 如果唤醒的时间小于上次唤醒时间,
  24. 26 并且唤醒时间大于开始计时的时间,
  25. 27 这样子就是相当于没有溢出,
  26. 28 也就是保了证周期性延时时间大于任务主体代码的执行时间\*/
  27. 29 if ( ( xTimeToWake < \*pxPreviousWakeTime )
  28. 30 && ( xTimeToWake > xConstTickCount ) ) { (6)
  29. 31 xShouldDelay = pdTRUE;
  30. 32 } else {
  31. 33 mtCOVERAGE\_TEST\_MARKER();
  32. 34 } 35 } else {
  33. 36 /\* 只是唤醒时间溢出的情况
  34. 37 或者都没溢出,
  35. 38 保证了延时时间大于任务主体代码的执行时间\*/
  36. 39 if ( ( xTimeToWake < \*pxPreviousWakeTime )
  37. 40 || ( xTimeToWake > xConstTickCount ) ) { (7)
  38. 41 xShouldDelay = pdTRUE;
  39. 42 } else {
  40. 43 mtCOVERAGE\_TEST\_MARKER();
  41. 44 }
  42. 45 }
  43. 46
  44. 47 /\* 更新上一次的唤醒时间 \*/
  45. 48 \*pxPreviousWakeTime = xTimeToWake; (8)
  46. 49
  47. 50 if ( xShouldDelay != pdFALSE ) {
  48. 51 traceTASK\_DELAY\_UNTIL( xTimeToWake );
  49. 52
  50. 53 /\* prvAddCurrentTaskToDelayedList()函数需要的是阻塞时间
  51. 54 而不是唤醒时间,因此减去当前的滴答计数。 \*/
  52. 55 prvAddCurrentTaskToDelayedList(
  53. 56 xTimeToWake - xConstTickCount, pdFALSE ); (9)
  54. 57 } else {
  55. 58 mtCOVERAGE\_TEST\_MARKER();
  56. 59 }
  57. 60 }
  58. 61 xAlreadyYielded = xTaskResumeAll();
  59. 62
  60. 63 /\* 强制执行一次上下文切换 \*/
  61. 64 if ( xAlreadyYielded == pdFALSE ) { (10)
  62. 65 portYIELD\_WITHIN\_API();
  63. 66 } else {
  64. 67 mtCOVERAGE\_TEST\_MARKER();
  65. 68 }
  66. 69 }

任务的设计要点

中断服务函数

中断服务函数是一种需要特别注意的上下文环境,它运行在非任务的执行环境下(一 般为芯片的一种特殊运行模式(也被称作特权模式))

中断服务函数的限制
在这个上下文环境中不能使用挂起当前任务的操作不允许调用任何会阻塞运行的 API 函数接口

中断服务程序最好保持精简短小,快进快出,一般在中断服务函数中只做标记事件的发生, 然后通知任务,让对应任务去执行相关处理,因为中断服务函数的优先级高于任何优先级的任务,

如果中断处理时间过长,将会导致整个系统的任务无法正常运行。

所以在设计的 时候必须考虑中断的频率、中断的处理时间等重要因素,以便配合对应中断处理任务的工作。

任务

任务中要设置阻塞机制,阻塞机制就是可以跳出该任务,调度器去调用其他任务。

空闲任务

空闲任务(idle 任务)是 FreeRTOS 系统中没有其他工作进行时自动进入的系统任务。

因为处理器总是需要代码来执行——所以至少要有一个任务处于运行态。

FreeRTOS 为了保证这一点,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环。

用户可以通过空闲任务钩子方式,在空闲任务上钩入自己的功能函数。

通常这个空闲任务钩子能够完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等。

除了空闲任务钩子,FreeRTOS 系统还把空闲任务用于一些其他的功能,比如当系统删除一个任务或一个动态任务运行结束时,在执行删除任务的时候,并不会释放任务的内存空间,只会将任务添加到结束列表中,真正的系统资源回收工作在空闲任务完成,空闲任务是唯一一个不允许出现阻塞情况的任务,因为 FreeRTOS 需要保证系统永远都有一个可运行的任务。

对于空闲任务钩子上挂接的空闲钩子函数,它应该满足以下的条件:

  • 永远不会挂起空闲任务;
  • 不应该陷入死循环,需要留出部分时间用于系统处理系统资源回收。

任务的执行时间

任务的执行时间一般是指两个方面,一是任务从开始到结束的时间,二是任务的周期。

任务的响应指标

例如,对于事件 A 对应的服务任务 Ta,系统要求的实时响应指标是 10ms,而 Ta 的最大运行时间是 1ms,那么 10ms 就是任务 Ta 的周期了,1ms 则是任务的运行时间,简单来说任务 Ta 在 10ms 内完成对事件 A 的响应即可。

此时,系统中还存在着以 50ms 为周期的另一任务Tb,它每次运行的最大时间长度是 100us。在这种情况下,即使把任务 Tb 的优先级抬到比 Ta 更高的位置,对系统的实时 性指标也没什么影响,因为即使在 Ta 的运行过程中,Tb 抢占了 Ta 的资源,等到 Tb 执行 完毕,消耗的时间也只不过是 100us,还是在事件 A 规定的响应时间内(10ms),Ta 能够安 全完成对事件 A 的响应。

但是假如系统中还存在任务 Tc,其运行时间为 20ms,假如将 Tc 的优先级设置比 Ta 更高,那么在 Ta 运行的时候,突然间被 Tc 打断,等到 Tc 执行完毕, 那 Ta 已经错过对事件 A(10ms)的响应了,这是不允许的。所以在我们设计的时候,必 须考虑任务的时间,一般来说处理时间更短的任务优先级应设置更高一些。