目录

1. 支持多优先级的方法

1.1 任务优先级

1.2 基于优先级的就绪列表

1.3 实现基于优先级的调度

2. 查找最高优先级就绪任务的方法

2.1 通用方法

2.2 体系结构优化方法

3. 修改代码支持多优先级

3.1 创建任务相关

3.1.1 修改任务控制块

3.1.2 增加prvAddNewTaskToReadyList函数

3.2 修改vTaskSwitchContext函数

3.3 修改xTaskIncrementTick函数

3.4 修改vTaskDelay函数

4. 实验现象


1. 支持多优先级的方法

1.1 任务优先级

在FreeRTOS中,使用数字表示任务优先级,数字越小优先级越小(这点和uC/OS & RT-Thread正好相反)

说明:后文将看到,FreeRTOS中这种优先级设置方式是和查找优先级最高的就绪任务的方式相匹配的

1.2 基于优先级的就绪列表

/* 任务就绪列表 */
List_t pxReadyTasksLists[configMAX_PRIORITIES];

① 就绪列表是一个存储就绪任务TCB的列表数组,元素个数就是系统支持的优先级个数

② 数组的下标对应任务的优先级,优先级越低,对应的数组下标越小。其中空闲任务优先级最低,对应下标为0的列表

③ 任务在创建时,会根据任务的优先级将任务插入到就绪列表不同的位置;相同优先级的任务插入到就绪列表的同一个列表中

1.3 实现基于优先级的调度

pxCurrentTCB是一个全局的TCB指针,用于指向当前正在运行任务的TCB。要想让任务支持优先级,只要解决在任务切换时让pxCurrentTCB指向最高优先级的就绪任务的TCB即可

下面就说明查找最高优先级就绪任务的方法

2. 查找最高优先级就绪任务的方法

在FreeRTOS中提供2种查找最高优先级就绪任务的方法,

① 通用方法

② 根据特定处理器优化的方法

在编译时由configUSE_PORT_OPTIMISED_TASK_SELECTION宏加以控制

2.1 通用方法

/*
* 系统当前就绪任务最高优先级
* 初始值为0,对应最低优先级的空闲任务
*/
// file: tasks.c
static volatile BaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;// file: tasks.c
/* 在系统中注册就绪任务优先级 */
#define taskRECORD_READY_PRIORITY(uxPriority) \
{ \if ((uxPriority > uxTopReadyPriority)) \{ \uxTopReadyPriority = (uxPriority); \} \
}/* 查找优先级最高的就绪任务 */
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \UBaseType_t uxTopPriority = uxTopReadyPriority; \while (listLIST_IS_EMPTY(&(pxReadyTasksLists[uxTopPriority]))) \{ \--uxTopPriority; \} \listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, \&(pxReadyTasksLists[uxTopPriority])); \uxTopReadyPriority = uxTopPriority; \
}/* 在系统中注销任务优先级(通用方法为空) */
#define taskRESET_READY_PRIORITY(uxPriority)
#define portRESET_READY_PRIORITY(uxPriority, uxTopReadyPriority)

说明1:uxTopReadyPriority的含义

在通用方法中,uxTopReadyPriority用于记录当前系统中就绪任务的最高优先级,可见该方法无法体现当前系统中哪些优先级是有任务的,这种缺点就限制了通用方法的实现

说明2:注册就绪任务优先级

在通用方法中,注册就绪任务优先级就是比较就绪任务优先级与uxTopReadyPriority的大小,使得uxTopReadyPriority中始终记录当前系统中就绪任务的最高优先级

说明3:无法注销优先级

在某些情况下,任务是需要退出就绪状态的(e.g. 比如任务进入睡眠或等待资源),此时应该注销该优先级

但是在通用模式下,退出就绪状态的任务并不知道自己是否是最高优先级,所以无法更新uxTopReadyPriority变量

如果要实现更新,就必须在有任务退出就绪状态时从尾部遍历就绪列表,以便更新uxTopReadyPriority变量,但是这个代价是很大的

补充:无法注销优先级还因为要考虑支持同优先级多任务的场景,当从就绪列表注销一个任务时,可能有与该任务同优先级的任务,所以仍需要遍历就绪列表才能正确更新uxTopReadyPriority变量

说明4:遍历查找最高优先级任务

正是由于上面提到的不能实时更新uxTopReadyPriority变量,所以在调用taskSELECT_HIGHEST_PRIORITY_TASK时,可能uxTopReadyPriority对应的优先级并没有任务,所以需要从尾部遍历

2.2 体系结构优化方法

/*
* 系统当前就绪任务最高优先级位图
* 初始值为0,对应没有任务
*/
static volatile BaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;/* 在系统中注册就绪任务优先级 */
// file: portmacro.h
#define portRECORD_READY_PRIORITY(uxPriority, uxTopReadyPriority) \(uxTopReadyPriority) |= (1UL << (uxPriority));// file: tasks.c
#define taskRECORD_READY_PRIORITY(uxPriority) \portRECORD_READY_PRIORITY(uxPriority, uxTopReadyPriority)/* 查找优先级最高的就绪任务 */
// file: portmacro.h
#define portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority) \uxTopPriority = (31UL - (uint32_t)__clz((uxTopReadyPriority)));// file: tasks.c
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \UBaseType_t uxTopPriority; \portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority); \listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, \&(pxReadyTasksLists[uxTopPriority])); \
}/* 在系统中注销任务优先级 */
// file: portmacro.h
#define portRESET_READY_PRIORITY(uxPriority, uxTopReadyPriority) \(uxTopReadyPriority) &= ~(1UL << (uxPriority));// file: tasks.c
#define taskRESET_READY_PRIORITY(uxPriority) \
{ \portRESET_READY_PRIORITY(uxPriority, uxTopReadyPriority); \
}

说明1:uxTopReadyPriority的含义

在体系结构优化方法中uxTopReadyPriority作为任务就绪位图使用,当某一位为1时,表示对应的优先级有任务就绪

此时很容易通过位操作来注册和注销任务优先级,也就解决了通用方法中的问题

说明2:基于clz指令的优化

优化方法的核心是使用clz指令计算uxTopReadyPriority中的先导零个数,进而计算出当前的最高优先级

基于这种优化方式,就很容易理解为什么在FreeRTOS中数字越大优先级越高,因为任务优先级在此处被映射为就绪位图中的位数

说明3:再看listGET_OWNER_OF_NEXT_ENTRY宏的使用

#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB, pxList) \
{ \List_t *const pxConstList = (pxList); \/* 节点索引指向链表第1个节点 */ \(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \if ((void *)(pxConstList)->pxIndex == \(void *)&((pxConstList)->xListEnd)) \{ \(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \} \/* 获取节点owner */ \(pxTCB) = (pxConstList)->pxIndex->pvOwner; \
}

在列表章节的笔记中曾经谈到过对该宏的使用,对该宏的正确理解应该是用于遍历列表,每调用一次,均会按序返回下一个列表项

当列表非空时,该宏会越过尾节点;如果列表为空,则会出错,因为尾节点中没有pvOwner选项,所以该宏只能在确知列表非空的情况下调用

说明4:使用体系结构优化方法的代价就是任务优先级一般不能超过32个,否则无法映射到32位的位图之上

补充:FreeRTOS中任务就绪列表视图如下图所示,可见如何将就绪列表状态映射到就绪位图中

3. 修改代码支持多优先级

3.1 创建任务相关

3.1.1 修改任务控制块

typedef struct tskTaskControlBlock
{// 其他字段UBaseType_t uxPriority; // 任务优先级
} tskTCB;

说明:由于TCB中增加了任务优先级字段,所以用于创建任务的xTaskCreateStatic & prvInitialiseNewTask函数均要增加任务优先级参数

3.1.2 增加prvAddNewTaskToReadyList函数

// file: tasks.c
/* 将pxTCB指向的任务加入就绪列表 */
#define prvAddTaskToReadyList(pxTCB) \taskRECORD_READY_PRIORITY((pxTCB)->uxPriority); \vListInsertEnd(&(pxReadyTasksLists[(pxTCB)->uxPriority]), \&((pxTCB)->xStateListItem));static void prvAddNewTaskToReadyList(TCB_t *pxNewTCB)
{/* 进入临界段 */taskENTER_CRITICAL();{/* 全局任务计数器加1 */uxCurrentNumberOfTasks++;if (pxCurrentTCB == NULL){/* 如果pxCurrentTCB为空,将其指向新创建的任务 */pxCurrentTCB = pxNewTCB;/* 如果是第一次创建任务,需要初始化任务相关列表 */if (uxCurrentNumberOfTasks == (UBaseType_t)1){prvInitialiseTaskLists();}}else{/* 如果pxCurrentTCB不为空,则根据任务优先级更新 */if (pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority){pxCurrentTCB = pxNewTCB;}}/* 将任务加入就绪列表 */prvAddTaskToReadyList(pxNewTCB);}/* 退出临界段 */taskEXIT_CRITICAL();
}

说明1:在将任务加入就绪列表的同时,修改任务就绪位图,表示该优先级已有任务就绪

说明2:pxCurrentTCB的更新

在创建任务的过程中,根据任务优先级更新pxCurrentTCB指针,这样在启动调度器时就可以自动运行当前优先级最高的任务,而不需要手动指定

这里需要注意的是,只有在调度器尚未开始运行时,才可以在prvAddNewTaskToReadyList函数中切换pxCurrentTCB指针的指向。如果调度器已经运行,则只能等待任务调度

3.2 修改vTaskSwitchContext函数

现在的调度变得非常简单,就是查找优先级最高的就绪任务

// file: tasks.cvoid vTaskSwitchContext(void)
{/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */taskSELECT_HIGHEST_PRIORITY_TASK();
}

3.3 修改xTaskIncrementTick函数

// file: tasks.cvoid xTaskIncrementTick(void)
{TCB_t *pxTCB = NULL;BaseType_t i = 0;const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 更新任务延时计数 */for (i = 0; i < configMAX_PRIORITIES; i++){pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(&(pxReadyTasksLists[i]));if (pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay--;/* 延时到达,将任务就绪 */if (pxTCB->xTicksToDelay == 0){taskRECORD_READY_PRIORITY(pxTCB->uxPriority);}}/* 触发任务切换 */portYIELD();}
}

说明:现在递减任务延时的方式仍然是遍历任务就绪列表,这样效率太低,后续将实现任务延时列表,专门用于维护处于延时状态的任务

3.4 修改vTaskDelay函数

void vTaskDelay(const TickType_t xTicksToDelay)
{TCB_t *pxTCB = NULL;/* 获取当前任务TCB */pxTCB = pxCurrentTCB;/* 设置延时Tick值 */pxTCB->xTicksToDelay = xTicksToDelay;/* 将任务从就绪列表中删除 */taskRESET_READY_PRIORITY(pxTCB->uxPriority);/* 主动放弃CPU */taskYIELD();
}

说明:此处并未真的将任务从就绪列表中删除,因为xTaskIncrementTick函数还需要遍历就绪列表维护延时tick值,此处只是注销了任务优先级

设想一下,如果此时同优先级有多个任务,简单注销任务优先级也是不合理的。这就促使我们一定要实现延时列表,将需要延时的任务移出就绪列表

4. 实验现象

说明1:实验现象和上一章是相同的,差别在于现在是根据任务优先级调度,而之前是人为在Task1和Task2之间切换

说明2:在当前代码环境下,通用模式是不能工作的,因为我们没有将退出就绪状态的任务移出就绪列表,在调用taskSELECT_HIGHEST_PRIORITY_TASK宏从尾部遍历就绪列表时,优先级永远不会改变,导致只有一个高优先级任务被调度

在下个章节完成任务延时列表后将再次验证

FreeRTOS内核实现05:支持多优先级相关推荐

  1. 从0到1写RT-Thread内核——支持多优先级

    在本章之前,RT-Thread还没有支持多优先级,我们手动指定了第一个运行的线程,并在此之后三个线程(包括空闲线程)互相切换,在本章中我们加入优先级的功能,第一个运行的程序是就绪列表里优先级最高的程线 ...

  2. 分离内核和虚拟机支持安全的关键任务边缘计算

    分离内核和虚拟机支持安全的关键任务边缘计算 Separation kernels and VMs enable secure mission critical edge computing Lynx软 ...

  3. freertos内核 任务定义与切换 原理分析

    freertos内核 任务定义与切换 原理分析 主程序 任务控制块 任务创建函数 任务栈初始化 就绪列表 调度器 总结任务切换 主程序 这个程序目的就是,使用freertos让两个任务不断切换.看两个 ...

  4. harmonyos资料整理:HarmonyOS采用多内核设计,支持针对不同资源受限设备选用适合的OS内核(linux是一个内核,不是一个完整的操作系统,例如ubuntu 是操作系统,内核是Linux)

    文章目录 前言 技术特性 硬件互助,资源共享 一次开发,多端部署 统一OS,弹性部署 技术架构 系统安全 see also 前言 HarmonyOS是一款"面向未来".面向全场景( ...

  5. RTL9300 修改内核和busybox支持 vlan if接口注册到内核

    //修改内核+busybox,支持vlan if虚接口//Note:开启8021q-vlan功能.|| make menuconfig赞未支持8021q kconfig也未配置其依赖.0.make 9 ...

  6. FreeRTOS内核笔记(一):基本知识和命名规则

    FreeRTOS内核笔记(一):基本知识和命名规则 FreeRTOS内核笔记 命名规则: 常用宏定义 Thread运行状态: RTOS Tick Context切换: 实时调度器Scheduler F ...

  7. X5浏览器支持HTML5吗,腾讯X5 Blink内核 加强H5支持和渲染性能支撑

    原标题:腾讯X5 Blink内核 加强H5支持和渲染性能支撑 腾讯浏览服务TBS2.0即日全量发布,X5内核升级到Blink版本,H5能力和渲染性能全面提升.随着TBS2.0的发布,使用TBS浏览服务 ...

  8. FreeRTOS内核实现07(完):支持时间片

    目录 1. 时间片概念与测试 1.1 时间片概念 1.2 时间片实现原理 1.2.1 taskSELECT_HIGHEST_PRIORITY_TASK宏 1.2.2 taskRESET_READY_P ...

  9. FreeRTOS内核详解(1) —— 临界段保护原理

    什么是临界段 临界段用一句话概括就是一段在执行的时候不能被中断的代码段. 在 FreeRTOS 里面,这个临界段最常出现的就是对全局变量的操作,由于不同任务间可以切换运行,当一个任务在访问某个全局变量 ...

最新文章

  1. linux设置逻辑卷进不了图形界面,LVM逻辑卷管理器图形界面操作
  2. 37个JavaScript基本面试问题和解答
  3. 利用 Angular Directive 和 @HostBinding 实现输入文本框随着键盘输入自动变色效果
  4. IntelliJ IDEA安装后需要必须做的一件事
  5. 键盘上ALT键的妙用
  6. Redis实现计数器---接口防刷---升级版(Redis+Lua)
  7. excel表自动向下填充
  8. 2018第九届蓝桥杯JavaB组省赛真题详解
  9. 全球与中国塑料树脂市场深度分析及发展研究预测报告
  10. private111
  11. java gui容器_中国大学MOOC: (GUI容器)容器类java.awt.container的父类是_______。
  12. pymysql无法访问本地计算机,使用Python和odo模块在mysql上加载csv时出错
  13. 漂亮的非主流图片(38p)
  14. [转]重装系统时要备份的东西
  15. 阿里云对腾讯企业邮箱设置域名解析
  16. java连接MQTT服务器(Springboot整合MQTT)
  17. 2-10、秋招年6月晋升期——《硬件架构的艺术-数字电路的设计方法与技术》
  18. 计算机考研难度2017,2017大专生考研难度分析
  19. HTTP与HTTPS知识点
  20. java毕业设计网上宠物商城管理系统源码+lw文档+mybatis+系统+mysql数据库+调试

热门文章

  1. frame中src怎么设置成一个变量_OpenCV图像人脸检测及视频中的人脸检测(附源码)...
  2. render java_render 实现v-model
  3. linux+tux游戏,Linux吉祥物游戏SuperTux 0.5.0版发布 类《超级马里奥兄弟》
  4. python tkinter linux,用于Python和Tkinter的Linux上的字体管理
  5. java 多线程数据分发_多线程分发处理List集合数据
  6. #6281. 数列分块入门 5
  7. 从此,懂一点CDMA
  8. Java中正则表达式提取字符串
  9. IDEA隐藏不需要的文件
  10. 批处理脚本手动双击可以执行,但计划任务中执行失败