FreeRTOS简单内核实现6 优先级

2024-06-16 15:20

本文主要是介绍FreeRTOS简单内核实现6 优先级,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 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、创建

将原来的就绪链表修改为就绪链表数组

/* task.c */
// 就绪链表数组
List_t pxReadyTasksLists[configMAX_PRIORITIES];

configMAX_PRIORITIES 是一个表示 RTOS 内核支持的最大优先级的宏定义,值得提醒的是目前 RTOS 支持的最大优先级数量为 32 个(这与后面使用到的记录优先级的位图有关,具体内容会在后面遇到优先级位图时做介绍)

/* FreeRTOSConfig.h */
// 设置 RTOS 支持的最大优先级
#define configMAX_PRIORITIES                    5

1.2、初始化

修改就绪链表初始化函数,即遍历整个就绪链表数组然后依次对每个就绪链表进行初始化,具体如下所示

/* task.c */
// 就绪链表始化函数
void prvInitialiseTaskLists(void)
{UBaseType_t uxPriority;// 初始化就绪任务链表for(uxPriority = (UBaseType_t)0U;uxPriority < (UBaseType_t)configMAX_PRIORITIES; uxPriority++){vListInitialise(&(pxReadyTasksLists[uxPriority]));}
}

1.3、添加任务

1.3.1、prvAddNewTaskToReadyList( )

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

  1. 当前系统中任务数量加一
  2. 如果第一次创建任务,就初始化任务相关的链表(就绪链表数组等)
  3. 如果不是第一次创建任务,就根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCB

注意:if(pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority) 判断 pxCurrentTCB 指向最高优先级任务的 TCB 时取了 = 号,也就意味着,如果系统中创建了两个相同优先级的任务,那启动调度器后第一个执行的任务将是最后创建的那个任务

任务控制块的 uxPriority 参数将在 “2.1、TCB” 小节中添加

/* task.c */
// 全局任务计数器
static volatile UBaseType_t uxCurrentNumberOfTasks = (UBaseType_t)0U;// 添加任务到就绪链表中
static void prvAddNewTaskToReadyList(TCB_t* pxNewTCB)
{// 进入临界段taskENTER_CRITICAL();{// 全局任务计数器加一操作uxCurrentNumberOfTasks++;// 如果 pxCurrentTCB 为空,则将 pxCurrentTCB 指向新创建的任务if(pxCurrentTCB == NULL){pxCurrentTCB = pxNewTCB;// 如果是第一次创建任务,则需要初始化任务相关的列表if(uxCurrentNumberOfTasks == (UBaseType_t)1){// 初始化任务相关的列表prvInitialiseTaskLists();}}else // 如果pxCurrentTCB不为空// 则根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCB {if(pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority){pxCurrentTCB = pxNewTCB;}}// 将任务添加到就绪列表prvAddTaskToReadyList(pxNewTCB);}// 退出临界段taskEXIT_CRITICAL();
}

1.3.2、prvAddTaskToReadyList( )

是怎么把任务添加到就绪链表的?

首先将要添加任务的优先级记录在优先级位图中,然后通过 vListInsertEnd() 函数将任务插入到对应优先级的就绪链表中,具体如下所示

/* task.c */
// 32位的优先级位图,默认全 0 ,记录了所有存在的优先级
static volatile UBaseType_t uxTopReadyPriority = 0;// 根据任务优先级置位优先级位图
#define taskRECORD_READY_PRIORITY(uxPriority)	portRECORD_READY_PRIORITY(uxPriority, uxTopReadyPriority)// 根据任务优先级添加任务到对应的就绪链表
#define prvAddTaskToReadyList(pxTCB) \taskRECORD_READY_PRIORITY((pxTCB)->uxPriority); \vListInsertEnd(&(pxReadyTasksLists[(pxTCB)->uxPriority]), \&((pxTCB)->xStateListItem)); \

什么是优先级位图?

优先级位图本质是一个 32 位的数,如果有对应的优先级任务,就将优先级位图这个变量的对应位标记为 1 (比如当前任务的优先级为 2 ,则将优先级位图的从右向左第二位置一)

为什么要使用优先级位图记录任务优先级?

方便快速找到当前系统中存在的最高优先级,通过计算优先级位图的前导零个数,然后让 31 减去前导零个数就可以很快找到最高优先级

/* protMacro.h */
#define portRECORD_READY_PRIORITY(uxPriority, uxReadyPriorities) (uxReadyPriorities) |= (1UL << (uxPriority))

1.4、寻找最高优先级任务

RTOS 支持任务优先级后,任务的调度策略就可以修改为始终让系统中处于就绪态的最高优先级的任务得到执行,因此我们需要寻找最高优先级任务,寻找到之后将 pxCurrentTCB 指向该任务,然后任务调度切换任务时就会切换到该最高优先级的任务

/* task.c */
// 找到就绪列表最高优先级的任务并更新到 pxCurrentTCB
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \UBaseType_t uxTopPriority; \/* 寻找最高优先级 */ \portGET_HIGHEST_PRIORITY(uxTopPriority, uxTopReadyPriority); \/* 获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB */ \listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, \&(pxReadyTasksLists[uxTopPriority])); \
}

获取系统中存在的最高优先级任务的原理正如 “1.3.2、prvAddTaskToReadyList( )” 小节中 ”为什么要使用优先级位图记录任务优先级?“ 问题所述内容

/* protMacro.h */
#define portGET_HIGHEST_PRIORITY(uxTopPriority, uxReadyPriorities) uxTopPriority = (31UL - (uint32_t) __clz((uxReadyPriorities)))

2、修改内核程序

2.1、TCB

在任务控制块中增加任务优先级参数

/* task.h */
typedef struct tskTaskControlBlock
{// 省略之前的结构体成员定义UBaseType_t           uxPriority;                           // 优先级
}tskTCB;

2.2、xTaskCreateStatic( )

修改静态创建任务函数,在参数列表中增加任务优先级参数,然后将创建好的任务直接自动添加到就绪链表中,不再需要额外手动将任务插入

/* task.c */
// 静态创建任务函数
#if (configSUPPORT_STATIC_ALLOCATION == 1)
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,const char* const pcName,const uint32_t ulStackDepth,void* const pvParameters,         UBaseType_t uxPriority,           // 优先级StackType_t* const puxTaskBuffer,TCB_t* const pxTaskBuffer)
{// 省略未改变的代码......// 真正的创建任务函数prvInitialiseNewTask(pxTaskCode,pcName,ulStackDepth,pvParameters,uxPriority,                      // 优先级&xReturn,pxNewTCB);// 创建完任务自动将任务添加到就绪链表prvAddNewTaskToReadyList(pxNewTCB);// 省略未改变的代码......
}
#endif/* task.h */
// 函数声明
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,const char* const pcName,const uint32_t ulStackDepth,void* const pvParameters,UBaseType_t uxPriority,           // 优先级StackType_t* const puxTaskBuffer,TCB_t* const pxTaskBuffer);

2.3、prvInitialiseNewTask( )

由于增加了优先级参数,因此需要在真正的创建任务函数中增加对任务优先级初始化的部分,具体如下所示

/* task.c */
// 真正的创建任务函数																 
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,const char* const pcName,const uint32_t ulStackDepth,void* const pvParameters,UBaseType_t uxPriority,TaskHandle_t* const pxCreatedTask,TCB_t* pxNewTCB)
{// 省略未改变的代码......// 初始化优先级if(uxPriority >= (UBaseType_t)configMAX_PRIORITIES){uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;}pxNewTCB->uxPriority = uxPriority;if((void*)pxCreatedTask != NULL){*pxCreatedTask = (TaskHandle_t)pxNewTCB;}
}

2.4、vTaskStartScheduler( )

由于在启动任务调度器函数中创建了空闲任务,因此还需要在创建空闲任务的参数列表中增加优先级参数,为了不抢占任何其他任务的运行,空闲任务的优先级应该保持为最低优先级,使用 taskIDLE_PRIORITY 宏定义表示,具体如下所示

/* task.c */
// 启动任务调度器
void vTaskStartScheduler(void)
{// 创建空闲任务TaskHandle_t xIdleTaskHandle = xTaskCreateStatic((TaskFunction_t)prvIdleTask,(char *)"IDLE",(uint32_t)confgiMINIMAL_STACK_SIZE,(void *)NULL,(UBaseType_t)taskIDLE_PRIORITY,(StackType_t *)IdleTasKStack,(TCB_t *)&IdleTaskTCB);if(xPortStartScheduler() != pdFALSE){}
}
/* task.h */
// 空闲任务优先级最低
#define taskIDLE_PRIORITY              ((UBaseType_t) 0U)

2.5、vTaskDelay( )

当一个任务调用阻塞延时函数时,可以将其优先级从优先级位图上清除掉,这样在寻找最高优先级任务时就不会找到阻塞状态的该任务

值得提醒的是,这种做法虽然可以简单的达到让进入阻塞状态的任务暂时脱离调度的效果,但是由于其仍然存在就绪链表中,并不是真正的从就绪链表中移除,因此在遍历就绪链表对就绪态的任务操作时会产生额外的操作

/* task.c */
// 阻塞延时函数
void vTaskDelay(const TickType_t xTicksToDelay)
{// 省略未修改程序......// 将任务从优先级位图上清除,这样调度时就不会找到该任务taskRESET_READY_PRIORITY(pxTCB->uxPriority);// 主动产生任务调度,让出 MCU taskYIELD();
}
/* task.c */
// 根据任务优先级清除优先级位图
#define taskRESET_READY_PRIORITY(uxPriority) \
{ \portRESET_READY_PRIORITY((uxPriority), (uxTopReadyPriority)); \
}

与置位优先级位图原理刚好相反,清除优先级位图则是根据任务的优先级将优先级位图上对应的位清零,具体如下所示

/* protMacro.h */
#define portRESET_READY_PRIORITY(uxPriority, uxReadyPriorities) (uxReadyPriorities) &= ~(1UL << (uxPriority))

2.6、vTaskSwitchContext( )

任务调度函数中始终选择系统中就绪状态的最高优先级任务

/* task.c */
// 任务调度函数
void vTaskSwitchContext(void)
{taskSELECT_HIGHEST_PRIORITY_TASK();
}

2.7、xTaskIncrementTick( )

更新任务阻塞延时参数,如果任务延时时间到期,将其任务优先级在优先级位图上恢复,然后产生任务调度切换任务

/* task.c */
// 更新任务延时参数
void xTaskIncrementTick(void)
{TCB_t *pxTCB = NULL;uint8_t i =0;uint8_t xSwitchRequired = pdFALSE;// 更新 xTickCount 系统时基计数器const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;// 扫描就绪列表中所有任务,如果延时时间不为 0 则减 1 for(i=0; i<configMAX_PRIORITIES; i++){pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY((&pxReadyTasksLists[i]));if(pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay--;}// 延时时间到,将任务就绪else {taskRECORD_READY_PRIORITY(pxTCB->uxPriority);xSwitchRequired = pdTRUE;}}// 如果就绪链表中有任务从阻塞状态恢复就产生任务调度if(xSwitchRequired == pdTRUE){// 产生任务调度portYIELD();}
}

3、实验

3.1、测试

测试程序与 FreeRTOS简单内核实现5 阻塞延时 几乎一致,但是已经不需要我们手动将任务插入就绪链表中了,不过创建任务时需要额外指定任务的优先级参数,另外我们添加一个模拟任务一直运行的延时函数,具体如下所示

/* main.c */
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
/* USER CODE END Includes *//* USER CODE BEGIN PV */
// 软件延时
void delay(uint32_t count)
{for(;count!=0;count--);
}TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE                    128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;
UBaseType_t Task1Priority = 1;TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE                    128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;
UBaseType_t Task2Priority = 2;// 任务 1 入口函数
void Task1_Entry(void *parg)
{for(;;){HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);vTaskDelay(100);}
}
// 任务 2 入口函数
void Task2_Entry(void *parg)
{for(;;){HAL_GPIO_TogglePin(ORANGE_LED_GPIO_Port, ORANGE_LED_Pin);// 模拟高优先级任务一直运行delay(10000000);}
}
/* USER CODE END PV *//* USER CODE BEGIN 2 */
// 创建任务 1 和 2
Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,(char *)"Task1",(uint32_t)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)Task1Priority,(StackType_t *)Task1Stack,(TCB_t *)&Task1TCB);Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry,(char *)"Task2",(uint32_t)TASK2_STACK_SIZE,(void *) NULL,(UBaseType_t)Task2Priority,(StackType_t *)Task2Stack,(TCB_t *)&Task2TCB );
// 启动任务调度器,永不返回
vTaskStartScheduler();
/* USER CODE END 2 */

使用逻辑分析仪捕获 GREEN_LED 和 ORANGE_LED 两个引脚的电平变化,具体如下图所示

![[Kernel_6.1.png]]

可以发现因为我们使用软件延时模拟高优先级 Task2 任务一直运行,导致低优先级的 Task1 任务完全没时间运行,也即 Task1 被饿死了

将 Task1 的优先级修改为 3 ,重新编译烧录程序再次观察两个引脚的电平变化,具体如下图所示

![[Kernel_6.2.png]]

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

3.2、待改进

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

  1. 静态方式创建任务
  2. 手动切换任务
  3. 临界段保护
  4. 任务阻塞延时
  5. 支持任务优先级

当前 RTOS 简单内核存在的缺点有

  1. 缺少阻塞链表
  2. 不支持时间片轮询

这篇关于FreeRTOS简单内核实现6 优先级的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066812

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

回调的简单理解

之前一直不太明白回调的用法,现在简单的理解下 就按这张slidingmenu来说,主界面为Activity界面,而旁边的菜单为fragment界面。1.现在通过主界面的slidingmenu按钮来点开旁边的菜单功能并且选中”区县“选项(到这里就可以理解为A类调用B类里面的c方法)。2.通过触发“区县”的选项使得主界面跳转到“区县”相关的新闻列表界面中(到这里就可以理解为B类调用A类中的d方法

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页:

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用。如果你看不懂,请留言。 完整代码: <!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><ti