找回密码
 立即注册
首页 业界区 安全 FreeRTOS简单内核实现6 优先级

FreeRTOS简单内核实现6 优先级

钱艷芳 2025-6-8 13:22:35
@
目录

  • 0、思考与回答

    • 0.1、思考一

  • 1、就绪链表

    • 1.1、创建
    • 1.2、初始化
    • 1.3、添加任务

      • 1.3.1、prvAddNewTaskToReadyList( )
      • 1.3.2、prvAddTaskToReadyList( )

    • 1.4、寻找最高优先级任务

  • 2、修改内核程序

    • 2.1、TCB
    • 2.2、xTaskCreateStatic( )
    • 2.3、prvInitialiseNewTask( )
    • 2.4、vTaskStartScheduler( )
    • 2.5、vTaskDelay( )
    • 2.6、vTaskSwitchContext( )
    • 2.7、xTaskIncrementTick( )

  • 3、实验

    • 3.1、测试
    • 3.2、待改进


0、思考与回答

0.1、思考一

如何实现 RTOS 内核支持多优先级?
因为不支持优先级,所以所有的任务都插入了一个名为 pxReadyTasksLists 的就绪链表中,相当于所有任务的优先级都是一致的,那如果我们创建一个就绪链表数组,数组下标代表优先级,优先级为 x 的任务就插入到 pxReadyTasksLists[x] 中,这样通过一个就绪链表数组就实现了将不同优先级的任务放在不同的就绪链表中,方便在进行任务调度时支持任务优先级
1、就绪链表

1.1、创建

将原来的就绪链表修改为就绪链表数组
  1. /* task.c */
  2. // 就绪链表数组
  3. List_t pxReadyTasksLists[configMAX_PRIORITIES];
复制代码
configMAX_PRIORITIES 是一个表示 RTOS 内核支持的最大优先级的宏定义,值得提醒的是目前 RTOS 支持的最大优先级数量为 32 个(这与后面使用到的记录优先级的位图有关,具体内容会在后面遇到优先级位图时做介绍)
  1. /* FreeRTOSConfig.h */
  2. // 设置 RTOS 支持的最大优先级
  3. #define configMAX_PRIORITIES                    5
复制代码
1.2、初始化

修改就绪链表初始化函数,即遍历整个就绪链表数组然后依次对每个就绪链表进行初始化,具体如下所示
  1. /* task.c */
  2. // 就绪链表始化函数
  3. void prvInitialiseTaskLists(void)
  4. {
  5.         UBaseType_t uxPriority;
  6.         // 初始化就绪任务链表
  7.         for(uxPriority = (UBaseType_t)0U;
  8.             uxPriority < (UBaseType_t)configMAX_PRIORITIES; uxPriority++)
  9.         {
  10.                 vListInitialise(&(pxReadyTasksLists[uxPriority]));
  11.         }
  12. }
复制代码
1.3、添加任务

1.3.1、prvAddNewTaskToReadyList( )

已完成的内核中添加任务到就绪链表是对每个任务手动调用 vListInsertEnd() 函数实现的,现在创建一个函数用于在任务创建后自动将其添加到就绪链表中,具体函数流程如下所示

  • 当前系统中任务数量加一
  • 如果第一次创建任务,就初始化任务相关的链表(就绪链表数组等)
  • 如果不是第一次创建任务,就根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCB
注意:if(pxCurrentTCB->uxPriority uxPriority) 判断 pxCurrentTCB 指向最高优先级任务的 TCB 时取了 = 号,也就意味着,如果系统中创建了两个相同优先级的任务,那启动调度器后第一个执行的任务将是最后创建的那个任务
任务控制块的 uxPriority 参数将在 "2.1、TCB" 小节中添加
  1. /* task.c */
  2. // 全局任务计数器
  3. static volatile UBaseType_t uxCurrentNumberOfTasks = (UBaseType_t)0U;
  4. // 添加任务到就绪链表中
  5. static void prvAddNewTaskToReadyList(TCB_t* pxNewTCB)
  6. {
  7.         // 进入临界段
  8.         taskENTER_CRITICAL();
  9.         {
  10.                 // 全局任务计数器加一操作
  11.                 uxCurrentNumberOfTasks++;
  12.                        
  13.                 // 如果 pxCurrentTCB 为空,则将 pxCurrentTCB 指向新创建的任务
  14.                 if(pxCurrentTCB == NULL)
  15.                 {
  16.                         pxCurrentTCB = pxNewTCB;
  17.                         // 如果是第一次创建任务,则需要初始化任务相关的列表
  18.                         if(uxCurrentNumberOfTasks == (UBaseType_t)1)
  19.                         {
  20.                                 // 初始化任务相关的列表
  21.                                 prvInitialiseTaskLists();
  22.                         }
  23.                 }
  24.                 else
  25.                 // 如果pxCurrentTCB不为空
  26.                 // 则根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCB
  27.                 {
  28.                         if(pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority)
  29.                         {
  30.                                 pxCurrentTCB = pxNewTCB;
  31.                         }
  32.                 }
  33.                
  34.                 // 将任务添加到就绪列表
  35.                 prvAddTaskToReadyList(pxNewTCB);
  36.         }
  37.         // 退出临界段
  38.         taskEXIT_CRITICAL();
  39. }
复制代码
1.3.2、prvAddTaskToReadyList( )

是怎么把任务添加到就绪链表的?
首先将要添加任务的优先级记录在优先级位图中,然后通过 vListInsertEnd() 函数将任务插入到对应优先级的就绪链表中,具体如下所示
  1. /* task.c */
  2. // 32位的优先级位图,默认全 0 ,记录了所有存在的优先级
  3. static volatile UBaseType_t uxTopReadyPriority = 0;
  4. // 根据任务优先级置位优先级位图
  5. #define taskRECORD_READY_PRIORITY(uxPriority)        portRECORD_READY_PRIORITY(uxPriority, uxTopReadyPriority)
  6. // 根据任务优先级添加任务到对应的就绪链表
  7. #define prvAddTaskToReadyList(pxTCB) \
  8.         taskRECORD_READY_PRIORITY((pxTCB)->uxPriority); \
  9.         vListInsertEnd(&(pxReadyTasksLists[(pxTCB)->uxPriority]), \
  10.                        &((pxTCB)->xStateListItem)); \
复制代码
什么是优先级位图?
优先级位图本质是一个 32 位的数,如果有对应的优先级任务,就将优先级位图这个变量的对应位标记为 1 (比如当前任务的优先级为 2 ,则将优先级位图的从右向左第二位置一)
为什么要使用优先级位图记录任务优先级?
方便快速找到当前系统中存在的最高优先级,通过计算优先级位图的前导零个数,然后让 31 减去前导零个数就可以很快找到最高优先级
  1. /* protMacro.h */
  2. #define portRECORD_READY_PRIORITY(uxPriority, uxReadyPriorities) (uxReadyPriorities) |= (1UL << (uxPriority))
复制代码
2.4、vTaskStartScheduler( )

由于在启动任务调度器函数中创建了空闲任务,因此还需要在创建空闲任务的参数列表中增加优先级参数,为了不抢占任何其他任务的运行,空闲任务的优先级应该保持为最低优先级,使用 taskIDLE_PRIORITY 宏定义表示,具体如下所示
  1. /* task.c */
  2. // 找到就绪列表最高优先级的任务并更新到 pxCurrentTCB
  3. #define taskSELECT_HIGHEST_PRIORITY_TASK() \
  4. { \
  5.         UBaseType_t uxTopPriority; \
  6.         /* 寻找最高优先级 */ \
  7.         portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority); \
  8.         /* 获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB */ \
  9.         listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, \
  10.                                     &(pxReadyTasksLists[uxTopPriority])); \
  11. }
复制代码
  1. /* protMacro.h */
  2. #define portGET_HIGHEST_PRIORITY(uxTopPriority, uxReadyPriorities) uxTopPriority = (31UL - (uint32_t) __clz((uxReadyPriorities)))
复制代码
2.5、vTaskDelay( )

当一个任务调用阻塞延时函数时,可以将其优先级从优先级位图上清除掉,这样在寻找最高优先级任务时就不会找到阻塞状态的该任务
值得提醒的是,这种做法虽然可以简单的达到让进入阻塞状态的任务暂时脱离调度的效果,但是由于其仍然存在就绪链表中,并不是真正的从就绪链表中移除,因此在遍历就绪链表对就绪态的任务操作时会产生额外的操作
  1. /* task.h */
  2. typedef struct tskTaskControlBlock
  3. {
  4.         // 省略之前的结构体成员定义
  5.     UBaseType_t           uxPriority;                           // 优先级
  6. }tskTCB;
复制代码
  1. /* task.c */
  2. // 静态创建任务函数
  3. #if (configSUPPORT_STATIC_ALLOCATION == 1)
  4. TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
  5.                             const char* const pcName,
  6.                             const uint32_t ulStackDepth,
  7.                             void* const pvParameters,         
  8.                             UBaseType_t uxPriority,           // 优先级
  9.                             StackType_t* const puxTaskBuffer,
  10.                             TCB_t* const pxTaskBuffer)
  11. {
  12.         // 省略未改变的代码
  13.         ......
  14.                 // 真正的创建任务函数
  15.                 prvInitialiseNewTask(pxTaskCode,
  16.                                                          pcName,
  17.                                                          ulStackDepth,
  18.                                                          pvParameters,
  19.                                                          uxPriority,                      // 优先级
  20.                                                          &xReturn,
  21.                                                          pxNewTCB);
  22.        
  23.                 // 创建完任务自动将任务添加到就绪链表
  24.                 prvAddNewTaskToReadyList(pxNewTCB);
  25.         // 省略未改变的代码
  26.         ......
  27. }
  28. #endif
  29. /* task.h */
  30. // 函数声明
  31. TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,
  32.                             const char* const pcName,
  33.                             const uint32_t ulStackDepth,
  34.                             void* const pvParameters,
  35.                             UBaseType_t uxPriority,           // 优先级
  36.                             StackType_t* const puxTaskBuffer,
  37.                             TCB_t* const pxTaskBuffer);
复制代码
与置位优先级位图原理刚好相反,清除优先级位图则是根据任务的优先级将优先级位图上对应的位清零,具体如下所示
  1. /* task.c */
  2. // 真正的创建任务函数                                                                                                                                 
  3. static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,
  4.                             const char* const pcName,
  5.                             const uint32_t ulStackDepth,
  6.                             void* const pvParameters,
  7.                             UBaseType_t uxPriority,
  8.                             TaskHandle_t* const pxCreatedTask,
  9.                             TCB_t* pxNewTCB)
  10. {
  11.         // 省略未改变的代码
  12.         ......
  13.         // 初始化优先级
  14.         if(uxPriority >= (UBaseType_t)configMAX_PRIORITIES)
  15.         {
  16.                 uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;
  17.         }
  18.         pxNewTCB->uxPriority = uxPriority;
  19.        
  20.         if((void*)pxCreatedTask != NULL)
  21.         {
  22.             *pxCreatedTask = (TaskHandle_t)pxNewTCB;
  23.         }
  24. }
复制代码
3、实验

3.1、测试

测试程序与 FreeRTOS简单内核实现5 阻塞延时 几乎一致,但是已经不需要我们手动将任务插入就绪链表中了,不过创建任务时需要额外指定任务的优先级参数,另外我们添加一个模拟任务一直运行的延时函数,具体如下所示
  1. /* task.c */
  2. // 启动任务调度器
  3. void vTaskStartScheduler(void)
  4. {
  5.     // 创建空闲任务
  6.         TaskHandle_t xIdleTaskHandle = xTaskCreateStatic((TaskFunction_t)prvIdleTask,
  7.                                       (char *)"IDLE",
  8.                                       (uint32_t)confgiMINIMAL_STACK_SIZE,
  9.                                       (void *)NULL,
  10.                                       (UBaseType_t)taskIDLE_PRIORITY,
  11.                                       (StackType_t *)IdleTasKStack,
  12.                                       (TCB_t *)&IdleTaskTCB);
  13.        
  14.         if(xPortStartScheduler() != pdFALSE){}
  15. }
复制代码
使用逻辑分析仪捕获 GREEN_LED 和 ORANGE_LED 两个引脚的电平变化,具体如下图所示
1.png

可以发现因为我们使用软件延时模拟高优先级 Task2 任务一直运行,导致低优先级的 Task1 任务完全没时间运行,也即 Task1 被饿死了
将 Task1 的优先级修改为 3 ,重新编译烧录程序再次观察两个引脚的电平变化,具体如下图所示
2.png

可以发现 Task1 不再被饿死,通过上述测试可以证明目前的 RTOS 已经支持多任务优先级
3.2、待改进

当前 RTOS 简单内核已实现的功能有

  • 静态方式创建任务
  • 手动切换任务
  • 临界段保护
  • 任务阻塞延时
  • 支持任务优先级
当前 RTOS 简单内核存在的缺点有

  • 缺少阻塞链表
  • 不支持时间片轮询

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册