【学习日记】【FreeRTOS】延时列表的实现

前言

本文在前面文章的基础上实现了延时列表,取消了 TCB 中的延时参数。
本文是对野火 RTOS 教程的笔记,融入了笔者的理解,代码大部分来自野火。

一、如何更高效地查找延时到期的任务

1. 朴素方式

  • 在本文之前,我们使用了一种朴素的思想进行延时任务的查找:
    • 在 TCB 中设置一个延时参数,需要延时的时候进行初始化
    • 将延时任务挂起(清除就绪优先级位 uxTopReadyPriority)
    • 当 SysTick 中断时,扫描就绪列表中每个 TCB,如果延时参数不为 0 就减 1
    • 如果延时参数被减到 0,就置对应的就绪优先级位 uxTopReadyPriority,然后进行任务切换
      可以看到,上面这种想法非常朴素,但是每次 SysTick 中断的时候都需要扫描一遍就绪列表中的所有任务,当任务多的时候,耗时将会很多。

2. 更高效的方式

  • 设置除就绪列表外的另一个列表——延时列表
  • 当任务要进入延时的时候,将延时到期的值设置为节点的排序值,根据排序值按升序插入延时列表中,然后将该任务从就绪列表中删除
  • 同时更新下一个任务的解锁时刻的变量 xNextTaskUnblockTime,这个变量的意思是,当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪
    可以看到,FreeRTOS 用这种方式避免了扫描所有任务的延时,这点是优于 RT-Thread 和 μC/OS 的。

实际上,有两个延时列表,这是为了解决延时时间溢出的问题。

如图:
在这里插入图片描述

二、代码详解

我们添加或修改以下的代码:

  • 两条延时列表的定义和初始化
  • 下一任务到期时间点变量的定义及初始化
  • 修改延时函数,延时时将任务从就绪列表中删除并添加到延时列表
  • 修改时基计数器中断,每次计时时查看是否有任务到期

还有一些辅助的函数,主要是解决当计时溢出或者延时溢出时两条延时列表的切换:

  • 切换当前延时列表指针和溢出延时列表指针函数
  • 更新任务到期时间点变量的函数

1. 延时列表的定义及初始化

① 定义

  • 定义了两个任务延时列表,当系统时基计数器xTickCount 没有溢出时,用一条列表,当 xTickCount 溢出后,用另外一条列表
  • pxDelayedTaskList 指向 xTickCount 没有溢出时使用的那条列表
  • pxOverflowDelayedTaskList 指向 xTickCount 溢出时使用的那条列表
//延时列表
static List_t xDelayedTaskList1;
static List_t xDelayedTaskList2;
//延时列表指针(用于切换)
static List_t * volatile pxDelayedTaskList;
static List_t * volatile pxOverflowDelayedTaskList;

② 初始化

/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{UBaseType_t uxPriority;//就绪列表初始化for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ){vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );	//初始化每个就绪列表}//延时列表初始化vListInitialise( &xDelayedTaskList1 );vListInitialise( &xDelayedTaskList2 );pxDelayedTaskList = &xDelayedTaskList1;pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

2. 下一任务到期时间点变量的定义和初始化

  • xNextTaskUnblockTime 用于表示下一个任务的解锁时刻
  • xNextTaskUnblockTime = xTickCount + xTicksToDelay(当前时间 + 延时时间)
  • 当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪

① 定义

//下一个延时任务到期的时间
static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U;

② 初始化

  • 在 vTaskStartScheduler 任务调度器函数中初始化为 portMAX_DELAY
  • portMAX_DELAY 是一个 portmacro.h 中定义的宏,默认为 0xffffffffUL
#define portMAX_DELAY ( TickType_t ) 0xffffffffULvoid vTaskStartScheduler( void )
{
/*======================================创建空闲任务start==============================================*/     TCB_t *pxIdleTaskTCBBuffer = NULL;StackType_t *pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize;/* 获取空闲任务的内存:任务栈和任务TCB */vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );    xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask,              /* 任务入口 */(char *)"IDLE",                           /* 任务名称,字符串形式 */(uint32_t)ulIdleTaskStackSize ,           /* 任务栈大小,单位为字 */(void *) NULL,                            /* 任务形参 */(UBaseType_t) tskIDLE_PRIORITY,           /* 任务优先级,数值越大,优先级越高 */(StackType_t *)pxIdleTaskStackBuffer,     /* 任务栈起始地址 */(TCB_t *)pxIdleTaskTCBBuffer );           /* 任务控制块 */
/*======================================创建空闲任务end================================================*/ xNextTaskUnblockTime = portMAX_DELAY;xTickCount = ( TickType_t ) 0U;/* 启动调度器 */if( xPortStartScheduler() != pdFALSE ){/* 调度器启动成功,则不会返回,即不会来到这里 */}
}

3. 延时函数的修改

任务调用延时函数时,将任务从就绪列表中转移到延时列表中:

void vTaskDelay( const TickType_t xTicksToDelay )
{TCB_t *pxTCB = NULL;/* 获取当前任务的TCB */pxTCB = pxCurrentTCB;/* 将任务插入到延时列表 */prvAddCurrentTaskToDelayedList( xTicksToDelay );/* 任务切换 */taskYIELD();
}
  • 任务插入延时列表使用 prvAddCurrentTaskToDelayedList()
  • 这个函数除了插入操作还处理了延时溢出的情况
  • 笔者画了个流程图方便大家理解:
    在这里插入图片描述
//将任务插入到延时列表
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait )
{TickType_t xTimeToWake;/* 获取系统时基计数器xTickCount的值 */const TickType_t xConstTickCount = xTickCount;/* 将任务从就绪列表中移除 */if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){/* 将任务在优先级位图中对应的位清除 */portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );}/* 计算延时到期时,系统时基计数器xTickCount的值是多少 */xTimeToWake = xConstTickCount + xTicksToWait;/* 将延时到期的值设置为节点的排序值 */listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );/* 溢出 */if( xTimeToWake < xConstTickCount ){vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );}else /* 没有溢出 */{vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );/* 更新下一个任务解锁时刻变量xNextTaskUnblockTime的值 */if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}}	
}

4. 修改时基计数器中断

  • 修改时基计数器中断,每次计时时查看是否有任务到期
  • 并且处理时基计数器溢出情况
  • 函数流程图如下
    在这里插入图片描述
  • 函数代码如下:
//系统时基计数
void xTaskIncrementTick( void )
{TCB_t * pxTCB;TickType_t xItemValue;//系统时基计数 + 1const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 如果xConstTickCount溢出,则切换延时列表 */if( xConstTickCount == ( TickType_t ) 0U ){taskSWITCH_DELAYED_LISTS();}/* 最近的延时任务延时到期 */if( xConstTickCount >= xNextTaskUnblockTime ){for( ;; ){if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ){/* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */xNextTaskUnblockTime = portMAX_DELAY;break;}else /* 延时列表不为空 */{pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );/* 直到将延时列表中所有延时到期的任务移除才跳出for循环 */if( xConstTickCount < xItemValue ){xNextTaskUnblockTime = xItemValue;break;}/* 将任务从延时列表移除,消除等待状态 */( void ) uxListRemove( &( pxTCB->xStateListItem ) );/* 将解除等待的任务添加到就绪列表 */prvAddTaskToReadyList( pxTCB );}}}/* xConstTickCount >= xNextTaskUnblockTime *//* 任务切换 */portYIELD();
}

5. 交换非溢出延时指针和溢出延时指针

  • 使用一个中间变量进行交换
  • 切换延时列表后记得更新 xNextTaskUnblockTime 的值
/* * 当系统时基计数器溢出的时候,延时列表pxDelayedTaskList 和* pxOverflowDelayedTaskList要互相切换*/
#define taskSWITCH_DELAYED_LISTS()\
{\List_t *pxTemp;\pxTemp = pxDelayedTaskList;\pxDelayedTaskList = pxOverflowDelayedTaskList;\pxOverflowDelayedTaskList = pxTemp;\xNumOfOverflows++;\prvResetNextTaskUnblockTime();\
}

6. 更新任务到期时间点变量的函数

  • 函数流程图:
    在这里插入图片描述

  • 函数代码:

//重新设置变量xNextTaskUnblockTime的值
static void prvResetNextTaskUnblockTime( void )
{TCB_t *pxTCB; // 定义一个指向TCB_t类型的指针变量pxTCBif( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ){/* 新的延时任务列表为空。将xNextTaskUnblockTime设置为最大可能值,以确保在延时列表中有任务项之前,if( xTickCount >= xNextTaskUnblockTime )的测试不会通过。 */xNextTaskUnblockTime = portMAX_DELAY;}else{/* 新的延时任务列表不为空,获取延时列表头部任务的值。这个值表示了延时列表头部任务应该从阻塞状态中解除的时间。 */( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );}
}

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/96138.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yolov5封装进ros系统

一&#xff0c;要具备ROS环境 ROS环境搭建可以参考我之前的文章 ROS参考文章1 ROS参考文章2   建立ROS工作空间 ROS系统由自己的编译空间规则。 cd 你自己想要的文件夹&#xff08;我一般是home目录&#xff09; mkdir -p (你自己的文件夹名字&#xff0c;比如我是yolov5…

Stable Diffusion 告别复制关键词,高质量提示词自动生成插件

在使用SD时,我们经常会遇到心中无想法,或不知如何描述心中所想的图像。有时由于提示词的选择不当,生成的图片质量也不尽如人意。为此,我今天为大家推荐一个高质量的提示词自动生成插件——One Button Prompt。 下面是他生成的一些样图。 文章目录 插件安装插件说明主菜单工…

python爬虫数据解析xpath、jsonpath,bs4

数据的解析 解析数据的方式大概有三种 xpathJsonPathBeautifulSoup xpath 安装xpath插件 打开谷歌浏览器扩展程序&#xff0c;打开开发者模式&#xff0c;拖入插件&#xff0c;重启浏览器&#xff0c;ctrlshiftx&#xff0c;打开插件页面 安装lxml库 安装在python环境中的Scri…

js fetch请求中断的几种方式

1、通过AbortController 这是官方标准手段&#xff0c;真正意义的阻止请求&#xff08;不支持ie&#xff09; 后端接口设置的两秒返回数据 function myFetch() {const controller new AbortController();const signal controller.signal;fetch(http://localhost:3000/aaa/…

Nginx 配置文件的完整指南 (一)

文章目录 一、简介1.1 配置文件一览 二、全局配置2.0 user2.1 worker_processes2.2 events模块2.3 http模块 三、server模块3.1 listen3.2 server_name3.3 location&#xff1a;请求处理位置 Nginx 配置文件的完整指南 (二) 一、简介 Nginx是一款高性能的Web服务器和反向代理服…

AWS EKS 集群自动扩容 Cluster Autoscaler

文章目录 一&#xff0c;需求工作需求说明 二&#xff0c;部署精简命令执行1&#xff0c;要求2&#xff0c;查看EC2 Auto Scaling groups Tag3&#xff0c;创建Serviceaccount需要的Policy&#xff0c;Role4&#xff0c;部署Cluster Autoscaler5&#xff0c;验证6&#xff0c;常…

深入探析设计模式:工厂模式的三种姿态

深入探析设计模式&#xff1a;工厂模式的三种姿态 1. 简单工厂模式1.1 概念1.2 案例1.3 优缺点 2. 抽象工厂模式2.1 概念2.2 案例&#xff1a;跨品牌手机生产2.3 优缺点 3. 超级工厂模式3.1 概念3.2 案例&#xff1a;动物园游览3.3 优缺点 4. 总结 欢迎阅读本文&#xff0c;今天…

vscode | linux | c++ intelliense 被弃用解决方案

每日一句&#xff0c;vscode用的爽是爽&#xff0c;主要是可配置太强了。如果也很会研究&#xff0c;可以直接去咸鱼接单了 废话少说&#xff0c;直接整。 用着用着说是c intelliense被弃用&#xff0c;很多辅助功能无法使用&#xff0c;像查看定义、查看引用、函数跳转、智能提…

springboot集成ES

1.引入pom依赖2.application 配置3.JavaBean配置以及ES相关注解 3.1 Student实体类3.2 Teacher实体类3.3 Headmaster 实体类4. 启动类配置5.elasticsearchRestTemplate 新增 5.1 createIndex && putMapping 创建索引及映射 5.1.1 Controller层5.1.2 service层5.1.3 ser…

sass笔记

声明变量 通过$标识符进行命名及引用混合器 类似vue中的函数 通过 mixin标识定义 include 标识调用& 父选择器标识extend 进行继承可嵌套可导入 通过 import 文件位置’ 、进行导入 <style> //1 声明变量 $name: 15px; $color: skyblue;mixin border-radius($num) {/…

进入嵌入式之后究竟会干些什么?

嵌入式被称为互联网、计算机行业的万金油&#xff0c;未来的就业方向多种多样&#xff0c;工作内容也不一而足&#xff0c;但可以分为如下几个角度&#xff1a; 架构师 在大型企业中&#xff0c;一个人很难承担过多的任务&#xff0c;因为这会带来很大的风险。大企业更需要在…

“RFID与光伏板的完美融合:探索能源科技的新时代!“

随着科技的不断发展&#xff0c;人类创造出了许多令人惊叹的发明。其中&#xff0c;RFID&#xff08;Radio Frequency Identification&#xff09;技术的应用在各个领域日益广泛。最近的研究表明&#xff0c;将RFID技术应用于光伏板领域&#xff0c;不仅可以提高光伏板的效率&a…

Java进阶(4)——结合类加载JVM的过程理解创建对象的几种方式:new,反射Class,克隆clone(拷贝),序列化反序列化

目录 引出类什么时候被加载JVM中创建对象几种方式1.new 看到new : new Book()2.反射 Class.forName(“包名.类名”)如何获取Class对象【反射的基础】案例&#xff1a;连接数据库方法 3.克隆&#xff08;拷贝&#xff09;clone浅拷贝深拷贝案例 序列化和反序列化对象流-把对象存…

intelij idea 2023 创建java web项目

1.点击New Project 2.创建项目名称为helloweb &#xff0c;jdk版本这里使用8&#xff0c;更高版本也不影响工程创建 点击create 3.新建的工程是空的&#xff0c;点击File-> Project Structure 4.点击Modules 5.点击加号&#xff0c;然后键盘输入web可以搜索到web模块&…

完全备份、增量备份、差异备份、binlog日志

Top NSD DBA DAY06 案例1&#xff1a;完全备份与恢复案例2&#xff1a;增量备份与恢复案例3&#xff1a;差异备份与恢复案例4&#xff1a;binlog日志 1 案例1&#xff1a;完全备份与恢复 1.1 问题 练习物理备份与恢复练习mysqldump备份与恢复 1.2 方案 在数据库服务器192…

考研408 | 【计算机网络】 传输层

导图 传输层的功能 传输层的两个协议 传输层的寻址与端口 UDP协议 UDP的主要特点 UDP首部格式&#xff1a; UDP校验&#xff1a; TCP协议 TCP协议的特点 TCP报文段首部格式 TCP连接管理 TCP的连接建立 SYN洪泛攻击 TCP的连接释放 TCP可靠传输 序号&#xff1a; 确认&#xff1…

三本书与三场发布会,和鲸社区重新定义编程类书籍从阅读到实践新体验

当 AI 开发者社区配备 AI 基础设施开发平台工具时&#xff0c;它还能做什么&#xff1f; 答案是&#xff1a;过去半年&#xff0c;和鲸社区凭借在气象、医学、社科等垂直领域的长期积累以及多方伙伴的支持&#xff0c;联合举办了三场新书发布会——从 Python 到 R 语言 、从气…

虚拟机远程连接出现 Connection refused: connect问题(已解决)

如果你也跟我一样出现了这样的问题&#xff0c;并且网上的方法试了都不行&#xff0c;不妨试试我的简单粗暴的方法&#xff0c; 那就是拔网线&#xff0c;我的就是拔网线&#xff0c;重新连接就行了&#xff0c;佛了

【从0开始学架构笔记】01 基础架构

文章目录 一、架构的定义1. 系统与子系统2. 模块与组件3. 框架与架构4. 重新定义架构 二、架构设计的目的三、复杂度来源&#xff1a;高性能1. 单机复杂度2. 集群复杂度2.1 任务分配2.2 任务分解&#xff08;微服务&#xff09; 四、复杂度来源&#xff1a;高可用1. 计算高可用…

【网络教程】如何创建/添加钉钉机器人以及如何获取机器人的Token/Secret

文章目录 创建钉钉机器人添加钉钉机器人获取机器人的Token/Secret相关网站创建钉钉机器人 这里以PC端的操作为例,按照如下操作进行 访问 钉钉开放平台选择机器人选项卡,点击右上角的创建应用,这里会有一个弹窗,我这里选择的是继续使用旧版,如图按照要求填写相关信息创建自…