在本章之前, FreeRTOS 还没有支持多优先级,只支持两个任务互相切换,从本章开始,
任务中我们开始加入优先级的功能。在 FreeRTOS 中,数字优先级越小,逻辑优先级也越
小,这与隔壁的 RT-Thread 和 μC/OS 刚好相反。

7.1 如何支持多优先级

就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组, 数组里面存的是就绪任务的 TCB(准确来说是 TCB 里面的 xStateListItem 节点) ,数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0 的链表。 图 10-1 演示的是就绪列表中有两个任务就绪, 优先级分别为 1 和 2,其中空闲任务没有画出来,空闲任务自系统启动后会一直就绪,因为系统至少得保证有一个任务可以运行。
image.png
任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置。相同优先级的任务插入到就绪列表里面的同一条链表中。
pxCurrenTCB 是一个全局的 TCB 指针,用于指向优先级最高的就绪任务的 TCB,即当前正在运行的 TCB 。那么我 们要想 让任务支 持优先 级,即只 要解决 在任务切 换(taskYIELD) 的时候,让 pxCurrenTCB 指向最高优先级的就绪任务的 TCB 就可以,前面的章节我们是手动地让 pxCurrenTCB 在任务 1、任务 2 和空闲任务中轮转,现在我们要改成 pxCurrenTCB 在任务切换的时候指向最高优先级的就绪任务的 TCB 即可,那问题的关键就是:如果找到最高优先级的就绪任务的 TCB。FreeRTOS 提供了两套方法,一套是通用的,一套是根据特定的处理器优化过的 。

7.2 查找最高优先级的就绪任务相关代码

寻找最高优先级的就绪任务相关代码在 task.c 中定义。

  1. /* 查找最高优先级的就绪任务:通用方法 */
  2. #if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
  3. /* uxTopReadyPriority 存的是就绪任务的最高优先级 */
  4. #define taskRECORD_READY_PRIORITY( uxPriority ) \
  5. { \
  6. if( ( uxPriority ) > uxTopReadyPriority ) \
  7. { \
  8. uxTopReadyPriority = ( uxPriority ); \
  9. } \
  10. } /* taskRECORD_READY_PRIORITY */
  11. /*-----------------------------------------------------------*/
  12. #define taskSELECT_HIGHEST_PRIORITY_TASK() \
  13. { \
  14. UBaseType_t uxTopPriority = uxTopReadyPriority; \
  15. \
  16. /* 寻找包含就绪任务的最高优先级的队列 */ \
  17. while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
  18. { \
  19. --uxTopPriority; \
  20. } \
  21. \
  22. /* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
  23. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
  24. /* 更新uxTopReadyPriority */ \
  25. uxTopReadyPriority = uxTopPriority; \
  26. } /* taskSELECT_HIGHEST_PRIORITY_TASK */
  27. /*-----------------------------------------------------------*/
  28. /* 这两个宏定义只有在选择优化方法时才用,这里定义为空 */
  29. #define taskRESET_READY_PRIORITY( uxPriority )
  30. #define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
  31. /* 查找最高优先级的就绪任务:根据处理器架构优化后的方法 */
  32. #else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
  33. #define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
  34. /*-----------------------------------------------------------*/
  35. #define taskSELECT_HIGHEST_PRIORITY_TASK() \
  36. { \
  37. UBaseType_t uxTopPriority; \
  38. \
  39. /* 寻找包含就绪任务的最高优先级的队列 */ \
  40. portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
  41. /* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
  42. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
  43. } /* taskSELECT_HIGHEST_PRIORITY_TASK() */
  44. /*-----------------------------------------------------------*/
  45. #if 0
  46. #define taskRESET_READY_PRIORITY( uxPriority ) \
  47. { \
  48. if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
  49. { \
  50. portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
  51. } \
  52. }
  53. #else
  54. #define taskRESET_READY_PRIORITY( uxPriority ) \
  55. { \
  56. portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
  57. }
  58. #endif
  59. #endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
  1. 查 找 最 高 优 先 级 的 就 绪 任 务 有 两 种 方 法 , 具 体 由configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏控制, 定义为 0 选择通用方法,定义为 1 选择根据处理器优化的方法,该宏默认在 portmacro.h 中定义为 1,即使用优化过的方法

7.2.1 通用方法

1. taskRECORD_READY_PRIORITY()

taskRECORD_READY_PRIORITY()用于更新 uxTopReadyPriority的值。 uxTopReadyPriority 是一个在 task.c 中定义的静态变量, 用于表示创建的任务的最高优级,默认初始化为 0,即空闲任务的优先级

  1. /* 空闲任务优先级宏定义,在 task.h 中定义 */
  2. #define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )
  3. /* 定义 uxTopReadyPriority,在 task.c 中定义 */
  4. static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;

2.taskSELECT_HIGHEST_PRIORITY_TASK()

上面代码的一部分

  1. #define taskSELECT_HIGHEST_PRIORITY_TASK() //1 \
  2. { \
  3. UBaseType_t uxTopPriority = uxTopReadyPriority; · \
  4. \
  5. /* 寻找包含就绪任务的最高优先级的队列 */
  6. // 从最高优先级开始找 找到一条队列 队列不空,说明有任务 \
  7. while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
  8. { \
  9. --uxTopPriority; \
  10. } \
  11. \
  12. /* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
  13. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
  14. /* 更新uxTopReadyPriority */ \
  15. uxTopReadyPriority = uxTopPriority; \
  16. } /* taskSELECT_HIGHEST_PRIORITY_TASK */
  1. taskSELECT_HIGHEST_PRIORITY_TASK()用于寻找优先级最高的就绪任务, 实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值
  2. 从最高优先级对应的就绪列表数组下标开始寻找当前链表下是否有任务存在,如果没有,则 uxTopPriority 减一操作,继续寻找下一个优先级对应的链表中是否有任务存在, 如果有则跳出 while 循环,表示找到了最高优先级的就绪任务。 之所以可以采用从最高优先级往下搜索,是因为任务的优先级与就绪列表的下标是一一对应的,优先级越高,对应的就绪列表数组的下标越大。
  3. 获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB。
  4. 更新 uxTopPriority 的值到 uxTopReadyPriority。

7.2.2 优化方法

  1. 优化的方法:这得益于 Cortex-M 内核有一个计算前导零的指令CLZ,所谓前导零就是计算一个变量(Cortex-M 内核单片机的变量为 32 位)从高位开始第一次出现 1 的位的前面的零的个数。 比如: 一个 32 位的变量 uxTopReadyPriority, 其位 0、位 24 和 位 25 均 置 1 , 其 余 位 为 0 。 那 么 使 用 前 导 零 指 令 __CLZ(uxTopReadyPriority)可以很快的计算出 uxTopReadyPriority的前导零的个数为 6

image.png
如果 uxTopReadyPriority 的每个位号对应的是任务的优先级,任务就绪时,则将对应的位置 1,反之则清零。那么图 10-2 就表示优先级 0、优先级 24 和优先级 25 这三个任务就绪,其中优先级为 25 的任务优先级最高。利用前导零计算指令可以很快计算出就绪任务中的最高优先级为:
( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) = ( 31UL - ( uint32_t )6 ) = 25

1. taskRECORD_READY_PRIORITY()

这个使用了位图操作

  1. #define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )

taskRECORD_READY_PRIORITY()用于根据传进来的形参(通常形参就是任务的优先级) 将变量 uxTopReadyPriority 的某个位置 1。 uxTopReadyPriority 是一个在 task.c 中定义的静态变量,默认初始化为 0。与通用方法中用来表示创建的任务的最高优先级不一样,它在优化方法中担任的是一个优先级位图表的角色,即该变量的每个位对应任务的优先级,如果任务就绪,则将对应的位置 1,反之清零。根据这个原理,只需要计算出 uxTopReadyPriority 的前导零个数就算找到了就绪任务的最高优先级。 与taskRECORD_READY_PRIORITY() 作 用 相 反 的 是 taskRESET_READY_PRIORITY() 。taskRECORD_READY_PRIORITY()与 taskRESET_READY_PRIORITY()具体的实现见代码
其实就是简单的位或操作。

  1. #define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
  2. #define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )

2. taskRESET_READY_PRIORITY()

使用了这个

  1. #define taskRESET_READY_PRIORITY( uxPriority ) \
  2. { \
  3. portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
  4. }
  1. taskRESET_READY_PRIORITY()用于根据传进来的形参(通常形参就是任务的优先级) 将变量 uxTopReadyPriority 的某个位清零。

这个没有使用:和时间延时列表有关(下一章的内容)

  1. #define taskRESET_READY_PRIORITY( uxPriority ) \
  2. { \
  3. if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
  4. { \
  5. portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
  6. } \
  7. }

实际上根据优先级调用 taskRESET_READY_PRIORITY()函数复位 uxTopReadyPriority 变量中对应的位时, 要先确保就绪列表中对应该优先级下的链表没有任务才行。 但是我们当前实现的阻塞延时方案还是通过扫描就绪列表里面的 TCB 的延时 变量 xTicksToDelay 来实现的, 还没有单独实现延时列表(任务延时列表将在下一个章节讲解),所以任务非就绪时暂时不能将任务从就绪列表移除,而是仅仅通过将任务优先级在变量 uxTopReadyPriority 中对应的位清零。 在下一章我们实现任务延时列表之后, 任务非就绪时, 不仅会将任务优先级在变量 uxTopReadyPriority 中对应的位清零,还会将任务从就绪列表删除。

3.taskSELECT_HIGHEST_PRIORITY_TASK()

  1. #define taskSELECT_HIGHEST_PRIORITY_TASK() \
  2. { \
  3. UBaseType_t uxTopPriority; \
  4. \
  5. /* 寻找包含就绪任务的最高优先级的队列 */ \
  6. portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
  7. /* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
  8. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
  9. } /* taskSELECT_HIGHEST_PRIORITY_TASK() */
  1. taskSELECT_HIGHEST_PRIORITY_TASK()用于寻找优先级最高的就绪任务, 实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值。
  2. 根据 uxTopReadyPriority 的值, 找到最高优先级, 然后更新到uxTopPriority 这个局部变量中。 在 portmacro.h 中定义
    1. #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )\
    2. uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
    根据 uxTopPriority 的值, 从就绪列表中找到就绪的最高优先级的任务的 TCB,然后将 TCB 更新到 pxCurrentTCB。

7.3 修改代码支持多优先级

7.3.1 修改任务控制块

在任务控制块中增加与优先级相关的成员

  1. typedef struct tskTaskControlBlock
  2. {
  3. volatile StackType_t *pxTopOfStack; /* 栈顶 */
  4. ListItem_t xStateListItem; /* 任务节点 */
  5. StackType_t *pxStack; /* 任务栈起始地址 */
  6. char pcTaskName[configMAX_TASK_NAME_LEN]; /* 任务名称,字符串形式 */
  7. TickType_t xTicksToDelay; /* 用于延时 */
  8. UBaseType_t uxPriority; // 这个添加
  9. }tskTCB;
  10. typedef tskTCB TCB_t;

7.3.2 修改xTaskCreateStatic() 函数

修改任务创建 xTaskCreateStatic()函数, 27 行 、5行

  1. TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
  2. const char * const pcName,
  3. const uint32_t ulStackDepth,
  4. void * const pvParameters,
  5. UBaseType_t uxPriority, /* 任务优先级,数值越大,优先级越高 */
  6. StackType_t * const puxStackBuffer,
  7. TCB_t * const pxTaskBuffer )
  8. {
  9. TCB_t *pxNewTCB;
  10. TaskHandle_t xReturn;
  11. if( (pxTaskBuffer != NULL) && (puxStackBuffer != NULL))
  12. {
  13. pxNewTCB = (TCB_t *)pxTaskBuffer;
  14. pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;
  15. /* 创建新的任务 */
  16. prvInitialiseNewTask( pxTaskCode, /* 任务入口 */
  17. pcName, /* 任务名称,字符串形式 */
  18. ulStackDepth, /* 任务栈大小,单位为字 */
  19. pvParameters, /* 任务形参 */
  20. uxPriority, /* 优先级 */
  21. &xReturn, /* 任务句柄 */
  22. pxNewTCB ); /* 任务栈起始地址 */
  23. /* 将任务添加到就绪列表 */
  24. prvAddNewTaskToReadyList( pxNewTCB ); // 增加的部分
  25. }
  26. else
  27. {
  28. xReturn = NULL;
  29. }
  30. /* 返回任务句柄,如果任务创建成功,此时 xReturn 应该指向任务控制块 */
  31. return xReturn;
  32. }

1. prvInitialiseNewTask()函数

新修改 prvInitialiseNewTask()函数,增加优先级形参和优先级初始化相关代码

  1. static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, /* 任务入口 */
  2. const char * const pcName, /* 任务名称,字符串形式 */
  3. const uint32_t ulStackDepth, /* 任务栈大小,单位为字 */
  4. void * const pvParameters, /* 任务形参 */
  5. UBaseType_t uxPriority, /* 优先级 */
  6. TaskHandle_t * const pxCreatedTask, /* 任务句柄 */
  7. TCB_t *pxNewTCB ) /* 任务控制块指针 */
  8. {
  9. StackType_t *pxTopOfStack;
  10. UBaseType_t x;
  11. /* 获取栈顶地址 */
  12. pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - ( uint32_t )1);
  13. /* 向下做 8 字节对齐 */
  14. pxTopOfStack = (StackType_t *) (((uint32_t) pxTopOfStack) & (~((uint32_t) 0x0007)));
  15. /* 将任务的名字存储在 TCB 中 */
  16. for ( x = (UBaseType_t) 0; x < (UBaseType_t) configMAX_TASK_NAME_LEN; x++)
  17. {
  18. pxNewTCB->pcTaskName[ x ] = pcName[ x ];
  19. if( pcName[ x ] == 0x00)
  20. {
  21. break;
  22. }
  23. }
  24. /* 任务名字的长度不能超过 configMAX_TASK_NAME_LEN */
  25. pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
  26. /* 初始化 TCB 中的 xStateListItem 节点 */
  27. vListInitialiseItem( &(pxNewTCB)->xStateListItem);
  28. /* 设置 xStateListItem 节点的拥有者 */
  29. listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
  30. /* 新增 */
  31. if ( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
  32. {
  33. uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
  34. }
  35. pxNewTCB->uxPriority = uxPriority;
  36. /* 初始化任务栈 */
  37. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters);
  38. /* 让任务句柄指向任务控制块 */
  39. if ((void *) pxTopOfStack != NULL)
  40. {
  41. *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
  42. }
  43. }

2. prvAddNewTaskToReadyList()函数

新增将任务添加到就绪列表的函数 prvAddNewTaskToReadyList(),该函数在 task.c 中实现

  1. static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
  2. {
  3. /* 进入临界段 */
  4. taskENTER_CRITICAL();
  5. {
  6. /* 全局任务计时器加一操作 */
  7. uxCurrentNumberOfTasks++;
  8. /* 如果 pxCurrentTCB 为空,则将 pxCurrentTCB 指向新创建的任务 */
  9. if ( pxCurrentTCB == NULL )
  10. {
  11. pxCurrentTCB = pxNewTCB;
  12. /* 如果是第一次创建任务,则需要初始化任务相关的列表 */
  13. if ( uxCurrentNumberOfTasks == (UBaseType_t) 1)
  14. {
  15. /* 初始化任务相关的列表 */
  16. prvInitialiseTaskLists();
  17. }
  18. }
  19. else //根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCB
  20. {
  21. if ( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
  22. {
  23. pxCurrentTCB = pxNewTCB;
  24. }
  25. }
  26. /* 将任务添加到就绪列表 */
  27. prvAddTaskToReadyList( pxNewTCB );
  28. }
  29. /* 退出临界段 */
  30. taskEXIT_CRITICAL();
  31. }

prvInitialiseTaskLists()函数

  1. /* 初始化任务相关的列表 */
  2. void prvInitialiseTaskLists( void )
  3. {
  4. UBaseType_t uxPriority;
  5. /* 初始化就绪列表 */
  6. for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
  7. {
  8. vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
  9. }
  10. }

prvAddTaskToReadyList()函数

  1. /* 将任务添加到就绪列表 */
  2. // 先记录优先级,然后添加到对应的优先级列表中
  3. #define prvAddTaskToReadyList( pxTCB ) \
  4. taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
  5. vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \

7.3.3 修改vTaskStartScheduler()函数

修改开启任务调度函数 vTaskStartScheduler()。

  1. void vTaskStartScheduler( void )
  2. {
  3. TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空闲任务控制块 */
  4. StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空闲任务栈起始地址 */
  5. uint32_t ulIdleTaskStackSize;
  6. /* 获取空闲任务的内存:任务栈和任务 TCB */
  7. vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
  8. &pxIdleTaskStackBuffer,
  9. &ulIdleTaskStackSize );
  10. xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */
  11. (char *)"IDLE", /* 任务名称,字符串形式 */
  12. (uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */
  13. (void *) NULL, /* 任务形参 */
  14. (UBaseType_t) tskIDLE_PRIORITY, /* 任务优先级,数值越大,优先级越高 */
  15. (StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */
  16. (TCB_t *) &pxIdleTaskTCBBuffer ); /* 任务控制块 */
  17. // 这个任务在创建的时候就添加到列表中了
  18. //vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem) );
  19. /* 手动指定第一个运行的任务 */
  20. // 自动指定了优先级最高的任务
  21. //pxCurrentTCB = &Task1TCB;
  22. if ( xPortStartScheduler() != pdFALSE)
  23. {
  24. /* 调度器启动成功,则不会返回,即不会来到这里 */
  25. }
  26. }

7.3.4 修改vTaskDelay()函数

  1. // 阻塞延时
  2. void vTaskDelay( const TickType_t xTicksToDelay )
  3. {
  4. TCB_t *pxTCB = NULL;
  5. /* 获取当前任务的 TCB */
  6. pxTCB = pxCurrentTCB;
  7. /* 设置延时时间 */
  8. pxTCB->xTicksToDelay = xTicksToDelay;
  9. /* 将任务插入到延时列表 */
  10. //uxListRemove( &( pxTCB->xStateListItem ) ); (注意)
  11. taskRESET_READY_PRIORITY( pxTCB->uxPriority );
  12. /* 任务切换 */
  13. taskYIELD();
  14. }

(注意): 将任务从就绪列表移除本应该完成两个操作: 1 个是将任务从就绪列表移除,由函数 uxListRemove()来实现; 另一个是根据优先级将优先级位图表 uxTopReadyPriority 中对应的位清零,由函数 taskRESET_READY_PRIORITY()来实现。但是鉴于我们目前的时基更新函数 xTaskIncrementTick 还是需要通过扫描就绪列表的任务来判断任务的延时时间是否到期,所以不能将任务从就绪列表移除。当我们在接下来的“任务延时列表的实现”章节中,会专门添加一个延时列表,到时延时的时候除了根据优先级将优先级位图表 uxTopReadyPriority 中对应的位清零外,还需要将任务从就绪列表移除。

7.3.5 修改 vTaskSwitchContext()函数

在新的任务切换函数 vTaskSwitchContext()中,不再是手动的让 pxCurrentTCB 指针在任 务 1 、 任 务 2 和 空 闲 任 务 中 切 换 , 而 是 直 接 调 用 函 数taskSELECT_HIGHEST_PRIORITY_TASK()寻找到优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB

  1. #if 1
  2. /* 任务切换,即寻找优先级最高的就绪任务 */
  3. void vTaskSwitchContext( void )
  4. {
  5. /* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */
  6. taskSELECT_HIGHEST_PRIORITY_TASK();
  7. }
  8. #else
  9. void vTaskSwitchContext( void )
  10. {
  11. /* 如果当前线程是空闲线程,那么就去尝试执行线程1或者线程2,
  12. 看看他们的延时时间是否结束,如果线程的延时时间均没有到期,
  13. 那就返回继续执行空闲线程 */
  14. if( pxCurrentTCB == &IdleTaskTCB )
  15. {
  16. if(Task1TCB.xTicksToDelay == 0)
  17. {
  18. pxCurrentTCB =&Task1TCB;
  19. }
  20. else if(Task2TCB.xTicksToDelay == 0)
  21. {
  22. pxCurrentTCB =&Task2TCB;
  23. }
  24. else
  25. {
  26. return; /* 线程延时均没有到期则返回,继续执行空闲线程 */
  27. }
  28. }
  29. else
  30. {
  31. /*如果当前线程是线程1或者线程2的话,检查下另外一个线程,如果另外的线程不在延时中,就切换到该线程
  32. 否则,判断下当前线程是否应该进入延时状态,如果是的话,就切换到空闲线程。否则就不进行任何切换 */
  33. if(pxCurrentTCB == &Task1TCB)
  34. {
  35. if(Task2TCB.xTicksToDelay == 0)
  36. {
  37. pxCurrentTCB =&Task2TCB;
  38. }
  39. else if(pxCurrentTCB->xTicksToDelay != 0)
  40. {
  41. pxCurrentTCB = &IdleTaskTCB;
  42. }
  43. else
  44. {
  45. return; /* 返回,不进行切换,因为两个线程都处于延时中 */
  46. }
  47. }
  48. else if(pxCurrentTCB == &Task2TCB)
  49. {
  50. if(Task1TCB.xTicksToDelay == 0)
  51. {
  52. pxCurrentTCB =&Task1TCB;
  53. }
  54. else if(pxCurrentTCB->xTicksToDelay != 0)
  55. {
  56. pxCurrentTCB = &IdleTaskTCB;
  57. }
  58. else
  59. {
  60. return; /* 返回,不进行切换,因为两个线程都处于延时中 */
  61. }
  62. }
  63. }
  64. }
  65. #endif

7.3.6 修改 xTaskIncrementTick()函数

修改 xTaskIncrementTick()函数,即在原来的基础上增加:当任务延时时间到,将任务就绪的代码。

  1. void xTaskIncrementTick( void )
  2. {
  3. TCB_t *pxTCB = NULL;
  4. BaseType_t i = 0;
  5. const TickType_t xConstTickCount = xTickCount + 1;
  6. xTickCount = xConstTickCount;
  7. /* 扫描就绪列表中所有线程的remaining_tick,如果不为0,则减1 */
  8. for(i=0; i<configMAX_PRIORITIES; i++)
  9. {
  10. pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );
  11. if(pxTCB->xTicksToDelay > 0)
  12. {
  13. pxTCB->xTicksToDelay --;
  14. /* 延时时间到,将任务就绪 */
  15. if( pxTCB->xTicksToDelay ==0 )
  16. {
  17. taskRECORD_READY_PRIORITY( pxTCB->uxPriority );
  18. }
  19. }
  20. }
  21. /* 任务切换 */
  22. portYIELD();
  23. }

延时时间到,将任务就绪。 即根据优先级将优先级位图表uxTopReadyPriority 中对应的位置位。 在刚刚修改的上下文切换函数 vTaskSwitchContext()中, 就是通过优先级位图表 uxTopReadyPriority 来寻找就绪任务的最高优先级的。

7.4 小总结

该章实现了优先级的优先调度。

  1. 首先实现了查找最高优先级的函数并且切换pxCurrentTCB(taskSELECT_HIGHEST_PRIORITY_TASK),和标记任务就绪的函数(taskRECORD_READY_PRIORITY)以及取消标记的函数(taskRESET_READY_PRIORITY)

  2. 以优化方法为例:优化方法通过位图操作来标记就绪的任务,然后通过下面的公式来计算哪个任务OK了。然后就找到最高优先级的任务了。

注:该优化方法最多支持32个优先级。一般一个RTOS的任务不会超过15个。
( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) = ( 31UL - ( uint32_t )6 ) = 25

  1. 修改代码支持了多优先级。

修改任务控制块:xTaskCreateStatic
然后上面函数调用的:prvInitialiseNewTask
上面都增加了优先级的操作

xTaskCreateStatic:在创建完成调用了prvAddNewTaskToReadyList函数。
prvAddNewTaskToReadyList:

  1. 1. 将任务自动添加到了就绪任务pxReadyTasksLists[ ( pxTCB )->uxPriority ]列表中,且设置为就绪
  2. 1. 除此创建任务的时候初始化了pxReadyTasksLists[]所有就绪列表
  3. 1. 将当前的pxCurrentTCB ——> 指向了优先级最高的任务

prvInitialiseNewTask:初始化了优先级,并且不能让优先级大于定下的最大优先级。

  1. 修改了调度函数vTaskStartScheduler():删除了初始化列表,因为在创建第一个任务的时候就初始化了。

删除了手动添加任务:自动在创建完成后就进行了添加,添加到了对应的就绪列表中。

  1. vTaskDelay:添加了将任务设置为非就绪态。也就是在位图中取消就绪态。
  2. vTaskSwitchContext :现在增加了优先级,只需要在位图中找到优先级最高的任务并且切换。
  3. xTaskIncrementTick() :增加了任务到,标记任务就绪的代码。

    7.5 main 函数

    ```c

  • 包含的头文件

*/

include “FreeRTOS.h”

include “task.h”

/*


  • 全局变量

*/ portCHAR flag1; portCHAR flag2;

extern List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

/*


  • 任务控制块 & STACK

*/ TaskHandle_t Task1_Handle;

define TASK1_STACK_SIZE 128

StackType_t Task1Stack[TASK1_STACK_SIZE]; TCB_t Task1TCB;

TaskHandle_t Task2_Handle;

define TASK2_STACK_SIZE 128

StackType_t Task2Stack[TASK2_STACK_SIZE]; TCB_t Task2TCB;

/*


  • 函数声明

/ void delay (uint32_t count); void Task1_Entry( void p_arg ); void Task2_Entry( void *p_arg );

/*


  • main函数

/ int main(void) {
/
硬件初始化 / / 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */

  1. /* 创建任务 */
  2. Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
  3. (char *)"Task1", /* 任务名称,字符串形式 */
  4. (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
  5. (void *) NULL, /* 任务形参 */
  6. (UBaseType_t) 1, /* 任务优先级,数值越大,优先级越高 */
  7. (StackType_t *)Task1Stack, /* 任务栈起始地址 */
  8. (TCB_t *)&Task1TCB ); /* 任务控制块 */
  9. /* 将任务添加到就绪列表 */
  10. //vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
  11. Task2_Handle = xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */
  12. (char *)"Task2", /* 任务名称,字符串形式 */
  13. (uint32_t)TASK2_STACK_SIZE , /* 任务栈大小,单位为字 */
  14. (void *) NULL, /* 任务形参 */
  15. (UBaseType_t) 2, /* 任务优先级,数值越大,优先级越高 */
  16. (StackType_t *)Task2Stack, /* 任务栈起始地址 */
  17. (TCB_t *)&Task2TCB ); /* 任务控制块 */
  18. /* 将任务添加到就绪列表 */
  19. //vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
  20. /* 启动调度器,开始多任务调度,启动成功则不返回 */
  21. vTaskStartScheduler();
  22. for(;;)
  23. {
  24. /* 系统启动成功不会到达这里 */
  25. }

}

/*


  • 函数实现

/ / 软件延时 / void delay (uint32_t count) { for(; count!=0; count—); } / 任务1 / void Task1_Entry( void p_arg ) { for( ;; ) { flag1 = 1; vTaskDelay( 2 );
flag1 = 0; vTaskDelay( 2 );
} }

/ 任务2 / void Task2_Entry( void *p_arg ) { for( ;; ) { flag2 = 1; vTaskDelay( 2 );
flag2 = 0; vTaskDelay( 2 );
} }

/ 获取空闲任务的内存 / StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; TCB_t IdleTaskTCB; void vApplicationGetIdleTaskMemory( TCB_t ppxIdleTaskTCBBuffer, StackType_t ppxIdleTaskStackBuffer, uint32_t pulIdleTaskStackSize ) { ppxIdleTaskTCBBuffer=&IdleTaskTCB; ppxIdleTaskStackBuffer=IdleTaskStack; pulIdleTaskStackSize=configMINIMAL_STACK_SIZE; } ```

7.6 实验现象

和上一节类似