【学习日记】【FreeRTOS】时间片的实现
前言
本文以野火的教程和代码为基础,对 FreeRTOS 中时间片的概念作了解释,并且给出了实现方式,同时发现并解决了野火教程代码中的 bug。
一、时间片是什么
在前面的文章中,我们已经知道任务根据不同的优先级被放入就绪列表中不同下标的链表中,先执行优先级高的链表。而且我们知道,在一条相同优先级的就绪链表中可以存放多个 TCB。
那么,你有没有想过,当一条相同优先级的就绪链表中存放了多个 TCB 时,怎么执行?有些人可能会想:相同优先级的话就顺序执行呗,执行完任务1就接着执行任务2,这样一直往下执行就可以啦。
那如果我们在一个任务中放入了耗时很长的代码,或者更极端一些,放入一个死循环,那我们的任务岂不是就永远无法执行完毕,也就永远没办法切换到相同优先级的其他任务了。
所以,我们在这里引入了对于时间片的支持。**时间片(time slice)实际上就是为了让 CPU 资源能较为公平地分配到每一个任务中。在相同的优先级中,每个任务按照固定顺序依次执行一个时间片的时间,然后切换到下一个任务。**如果一个任务在时间片结束之前没有执行完,它将被挂起,任务的上下文将被保留,并在下一个循环中重新获得时间片后继续执行上次没有执行完的代码。
通过划分时间片,操作系统能够公平地分享处理器时间给多个任务,使得它们能够以看似同时的方式进行处理,提高了系统的并发性和响应性能。
二、时间片代码的实现
其实在前面的文章中,我们的代码已经可以支持时间片了,但是为了让代码效率更高,还是需要对代码进行一些优化。主要是以下这些代码:
- SysTick 中断 xPortSysTickHandler()
- 时基计数函数 xTaskIncrementTick()
- 时间片的核心实现
1. SysTick 中断 xPortSysTickHandler() 的修改
之前的代码是每进入一次 SysTick 中断后都调用一次 taskYIELD() 进行任务的切换。
但实际上,进入中断后并不一定需要切换任务。需要进行任务切换的有以下这些情况:
- 当前执行的这个任务的优先级不是最高的
- 当前执行的这个任务的优先级虽然是最高的,但是有其他优先级和它一样的任务在等待执行
所以我们在 SysTick 中断中调用时基计数函数 xTaskIncrementTick(),并在 xTaskIncrementTick() 对上面所说的情况进行判断,返回是否需要进行任务切换,再在 SysTick 中断看情况是否进行任务切换。
- 代码:
/*
*************************************************************************
* SysTick中断服务函数
*************************************************************************
*/
void xPortSysTickHandler( void )
{/* 关中断 */vPortRaiseBASEPRI();{/* 更新系统时基 */if( xTaskIncrementTick() != pdFALSE ){taskYIELD();}}/* 开中断 */vPortClearBASEPRIFromISR();
}
2. 时基计数函数 xTaskIncrementTick() 的修改
- 添加一个返回值 xSwitchRequired,标识是否需要进行任务切换,初始化为 pdFALSE,表示不需要任务切换
- 遇到这些情况,就修改 pdFALSE 为 pdTRUE,表示需要进行任务切换
- 当前执行的这个任务的优先级不是最高的
- 当前执行的这个任务的优先级虽然是最高的,但是有其他优先级和它一样的任务在等待执行
- 定义了 configUSE_PREEMPTION 为 1,表示使用任务抢占,也就是优先级
- 定义了 configUSE_TIME_SLICING 为 1,表示使用时间片
实际上,野火的教程中出现了两个 bug:
- 添加了返回值 xSwitchRequired 却没有 return
- configUSE_TIME_SLICING 的定义被注释掉了,也就是说,野火的代码实际上没有完全实现时间片的功能
我们把上面这些 bug 修复,得到了以下的代码:
#define configUSE_PREEMPTION 1
#ifndef configUSE_TIME_SLICING#define configUSE_TIME_SLICING 1
#endif//系统时基计数
BaseType_t xTaskIncrementTick( void )
{TCB_t * pxTCB;TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE;const 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 );#if ( configUSE_PREEMPTION == 1 ){if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){xSwitchRequired = pdTRUE;}}#endif /* configUSE_PREEMPTION */}}}/* xConstTickCount >= xNextTaskUnblockTime */#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ){if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ){xSwitchRequired = pdTRUE;}}#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) *//* 任务切换 *///portYIELD();return xSwitchRequired;
}
3. 时间片的核心实现——列表索引的妙用
那么如果要实现时间片,也就是说在进行任务切换的时候,我们要获取到同一优先级链表下当前任务的下一个任务的指针,然后再切换到下一个任务去执行。这要怎么实现呢?
我们调用任务切换函数后,就会产生 PendSV 中断,中断中会调用上下文切换函数,而在上下文切换函数中,会调用选择优先级最高任务的函数,这个函数中首先确定最高的优先级,然后更新当前任务指针(listGET_OWNER_OF_NEXT_ENTRY())。
实际上,listGET_OWNER_OF_NEXT_ENTRY() 就是实现时间片的关键。
还记得我们在进行链表实现时,链表中有一个 pxIndex 的索引吗,listGET_OWNER_OF_NEXT_ENTRY() 使用这个索引,记住了上一次执行的任务的位置,这样就能在每次调用时获取当前任务的下一个任务的指针。
- 代码:
/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \List_t * const pxConstList = ( pxList ); \/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \/* 当前链表为空 */ \if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \{ \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \} \/* 获取节点的OWNER,即TCB */ \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
三、时间片代码实现的验证
我们定义两个优先级较低(优先级为 2)的任务,并在这两个任务中使用循环延时改变电平,然后再定义一个优先级较高(优先级为 3)的任务,在这个任务中使用阻塞延时改变电平。代码如下:
/* 任务1 优先级为2 */
void Task1_Entry( void *p_arg )
{for( ;; ){flag1 = 1;//vTaskDelay( 1 );delay (100); flag1 = 0;delay (100);//vTaskDelay( 1 ); }
}/* 任务2 优先级为2 */
void Task2_Entry( void *p_arg )
{for( ;; ){flag2 = 1;//vTaskDelay( 1 );delay (100); flag2 = 0;delay (100);//vTaskDelay( 1 ); }
}/* 任务2 优先级为3 */
void Task3_Entry( void *p_arg )
{for( ;; ){flag3 = 1;vTaskDelay( 2 );//delay (100); flag3 = 0;vTaskDelay( 2 );//delay (100);}
}
- 结果如下:
任务3 延时 2 ms:
任务3 延时 10 ms:
可以看到,在任务3 没用接过 CPU控制权改变电平时,任务1 任务2 平均分配 CPU 使用时间,时间片的实现成功。
而如果我们的时间片功能没有实现,那么任务3 在进入阻塞延时后将交出 CPU 使用权,开始执行任务1。由于任务1 的延时是循环延时而不是阻塞延时,那么 CPU 将一直执行任务1 直到任务3 的阻塞延时结束再跳到任务3 执行,而任务2 永远不会得到执行。
四、关于野火代码 bug 的讨论
在野火的官方例程中,configUSE_TIME_SLICING 的定义被注释掉了,也就是说,野火的代码实际上没有完全实现时间片的功能。
以上文定义的验证代码为例,注释掉configUSE_TIME_SLICING,导致在任务3 交出 CPU 控制权给其他任务后,如果其他任务里面是一个循环,那么在任务3 重新获得 CPU 控制权前将一直执行这个任务;而得益于 listGET_OWNER_OF_NEXT_ENTRY(),在任务3 下一次交出 CPU 控制权后,将会执行和刚才任务不同的另一个任务。
也就是出现了一种怪异的现象:表面看起来优先级较低的任务1 和 2 有轮流执行,似乎实现了时间片算法。但实际上正确的表现应该是在任务3 交出 CPU 控制权后(此时任务3 中变量电平稳定为1 或者 0),任务1 任务2 在任务3 中变量电平稳定期间可以多次切换,实现真正的时间片;而不是直到任务3 改变电平后再进行任务1 和任务2 之间的相互切换。
同样是任务 3 阻塞延时为2ms:
-
注释后(没有真正实现时间片的功能):
-
没有注释(真正实现时间片功能):
以上就是本文的全部内容啦!
后记
如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!
相关文章:
【学习日记】【FreeRTOS】时间片的实现
前言 本文以野火的教程和代码为基础,对 FreeRTOS 中时间片的概念作了解释,并且给出了实现方式,同时发现并解决了野火教程代码中的 bug。 一、时间片是什么 在前面的文章中,我们已经知道任务根据不同的优先级被放入就绪列表中不…...
CentOS Docker仓库和代理配置
无法直接访问外部网络时,除了Host自己的全局代理设置之外,需要单独给Docker Client和Instance设置代理。 如执行docker run时遇到下面的错误 docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp 3.216.…...
Lnton羚通算法算力云平台在环境配置中Windows10终端和VSCode下如何打开Anaconda-Prompt
在Windows 10的终端和VSCode中,可以直接打开Anaconda Prompt。下面是两种方法: Windows 10终端:在开始菜单中搜索"Anaconda Prompt",然后点击打开。这将启动Anaconda Prompt终端,你可以在其中执行conda相关命…...
Python web实战之细说Django的集成测试
关键词: Python Web开发、Django、集成测试、实战、测试驱动开发、自动化测试、Selenium、测试框架、测试用例、代码覆盖率、持续集成 今天给大家分享一下Python Web开发——Django的集成测试,如何利用集成测试来提高代码质量、减少bug。 1. 什么是集成…...
Laravel 模型的作用域 模型的访问器和修改器 ⑨
作者 : SYFStrive 博客首页 : HomePage 📜: THINK PHP 📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗 📌:觉得文章不错可以点点关注 ὄ…...
每日一学——交换机
交换机是一种网络设备,用于连接多台计算机和其他网络设备,以实现数据的交换和传输。它通过将数据包在不同端口之间转发,将数据从一个设备发送到目标设备。交换机可以提供高速、可靠和安全的局域网连接。 交换机的工作原理是根据目标MAC地址来…...
数学建模大全及优缺点解读
分类模型 1、距离聚类(系统聚类)(常用,需掌握) 优点: ①将一批样本数据按照他们在性质上的亲密程度在没有先验知识的情况下自动进行分类 ②是一种探索性的分析方法,分类结果不一定相同 例如&am…...
C++简介
文章目录 C简介C版本C11例子 C14例子 C17C20例子 C简介 C是一种高级编程语言,它是对C语言的扩展和增强。C由Bjarne Stroustrup于1980年发明,主要用于系统级编程、游戏开发、嵌入式系统等领域。 C具有许多特性,其中最重要的是面向对象编程&a…...
【广州华锐互动】3D空间编辑器:一款简洁易用的VR/3D在线编辑工具
随着虚拟现实技术的不断发展,数字孪生技术的应用已经被广泛应用于产品设计和制作中,能充分发挥企业应用3D建模的优势,凸显了三维设计的价值,在生产阶段也能够充分发挥3D模型的作用。 如今,广州华锐互动开发的3D空间编辑…...
golang云原生项目☞redis配置
配置redis适用与golang云原生架构。包括redis与数据库一致性等重要内容 1、编写redis配置文件、使用viper读取 配置文件 db.yml redis:addr: 127.0.0.1port: 6379password: tiktokRedisdb: 0 # 数据库编号读取配置文件 var (config viper.Init("db")zapL…...
C++ malloc/free/new/delete详解(内存管理)
C malloc/free/new/delete详解(内存管理) malloc/free典型用法内存分配实现过程brk和mmap申请小于128k的内存申请大于128k的内存释放内存brk和mmap的区别 new/delete典型用法 内存分配实现过程new/delete和malloc/free的区别malloc对于给每个进程分配的内…...
SpringBoot中Mapper.xml的入参方式
在SpringBoot开发过程中,我们使用 ***Mapper.xml***Mapper.java 来封装对数据库表的 CURD 操作,正常每张表会有一组对应的文件。 一、Mapper常见用法 下面例举一个查询操作: 数据表t_sap_customer,表中有字段id、code、name、c…...
回归预测 | MATLAB实现WOA-RBF鲸鱼优化算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图)
回归预测 | MATLAB实现WOA-RBF鲸鱼优化算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现WOA-RBF鲸鱼优化算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图&#…...
浅析Python爬虫ip程序延迟和吞吐量影响因素
作为一名资深的爬虫程序员,今天我们很有必要来聊聊Python爬虫ip程序的延迟和吞吐量,这是影响我们爬取效率的重要因素。这里我们会提供一些实用的解决方案,让你的爬虫程序飞起来! 网络延迟 首先,让我们来看看网络延迟对…...
【100天精通python】Day43:python网络爬虫开发_爬虫基础(urlib库、Beautiful Soup库、使用代理+实战代码)
目录 1 urlib 库 2 Beautiful Soup库 3 使用代理 3.1 代理种类 HTTP、HTTPS 和 SOCKS5 3.2 使用 urllib 和 requests 库使用代理 3.3 案例:自建代理池 4 实战 提取视频信息并进行分析 1 urlib 库 urllib 是 Python 内置的标准库,用于处理URL、发送…...
Linux:安全技术与防火墙
目录 一、安全技术 1.安全技术 2.防火墙的分类 3.防水墙 4.netfilter/iptables关系 二、防火墙 1、iptables四表五链 2、黑白名单 3.iptables命令 3.1查看filter表所有链 iptables -L 编辑3.2用数字形式(fliter)表所有链 查看输出结果 iptables -nL 3.3 清空所有链…...
Confluent kafka 异常退出rd_tmpabuf_alloc0: rd kafka topic info_new_with_rack
rd_tmpabuf_alloc0: rd kafka topic info_new_with_rack 根据网上的例子,做了一个测试程序。 C# 操作Kafka_c# kafka_Riven Chen的博客-CSDN博客 但是执行下面一行时,弹出上面的异常,闪退。 consumer.Subscribe(queueName) 解决方案&…...
最新ChatGPT网站程序源码+AI系统+详细图文搭建教程/支持GPT4.0/AI绘画/H5端/Prompt知识库
一、前言 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧!…...
chatGPT-对话柏拉图
引言: 古希腊哲学家柏拉图,在他的众多著作中,尤以《理想国》为人所熟知。在这部杰作中,他勾勒了一个理想的政治制度,提出了各种政体,并阐述了他对于公正、智慧以及政治稳定的哲学观点。然而,其…...
Java项目-苍穹外卖-Day04
公共字段自动填充 这些字段在每张表基本都有,手动进行填充效率低,且后期维护更改繁琐 使用到注解AOP主要 先答应一个AutoFill注解 再定义一个切面类进行通知 对应代码 用到了枚举类和反射 package com.sky.aspect; /*** 自定义切面类,…...
SQL递归获取完整的树形结构数据
在 SQL 中,WITH RECURSIVE 用于创建递归查询,它允许在查询中引用自身。这种查询通常用于处理具有层次结构的数据,例如树形结构。 以下是使用 WITH RECURSIVE 创建递归查询的一般语法: WITH RECURSIVE [alias] ([column1], [colu…...
如何使用营销活动,提升小程序用户的参与度
在当今数字化时代,小程序已成为企业私域营销的重要一环。然而,仅仅拥有小程序还不足以吸引用户的兴趣和参与。营销活动作为推动用户参与的有效手段,可以在激烈的市场竞争中脱颖而出。本文将深入探讨如何使用营销活动,提升小程序用…...
IDEA中使用Docker插件构建镜像并推送至私服Harbor
一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的,需要加点配置,开启Docker的远程访问 # 首先查看docker配置文件所在位置 systemctl status docker# 会输出如下内容: ● docker.service - Docker Ap…...
第7章 高性能门户首页构建
mini商城第7章 高性能门户首页构建 一、课题 高性能门户建设 二、回顾 1、了解文件存储系统的概念 2、了解常用文件服务器的区别 3、掌握Minio的应用 三、目标 1、OpenResty 百万并发站点架构 OpenResty 特性介绍 搭建OpenResty Web站点动静分离方案剖析 2、多级缓存架…...
用加持了大模型的 Byzer-Notebook 做数据分析是什么体验
Byzer-Notebook 是专门为 SQL 而研发的一款 Web Notebook。他的第一公民是 SQL,而 Jupyter 则是是以 Python 为第一公民的。 随着 Byzer 引擎对大模型能力的支持日渐完善, Byzer-Notebook 也在不自觉中变得更加强大。我和小伙伴在聊天的过程中才发现他已…...
学习设计模式之观察者模式,但是宝可梦
前言 作者在准备秋招中,学习设计模式,做点小笔记,用宝可梦为场景举例,有错误欢迎指出。 观察者模式 观察者模式定义了一种一对多的依赖关系,一个对象的状态改变,其他所有依赖者都会接收相应的通知。 所…...
课程项目设计--spring security--用户管理功能--宿舍管理系统--springboot后端
写在前面: 还要实习,每次时间好少呀,进度会比较慢一点 本文主要实现是用户管理相关功能。 前文项目建立 文章目录 验证码功能验证码配置验证码生成工具类添加依赖功能测试编写controller接口启动项目 security配置拦截器配置验证码拦截器 …...
学习设计模式之装饰器模式,但是宝可梦
装饰模式 为了不改变组件的结构,动态地扩展其功能。 通常,扩展功能通过子类进行,但是继承的方式具有静态特征,耦合度高。 意图:动态地给对象添加额外的功能 主要解决:继承方式是静态特征,扩…...
【AWS】创建IAM用户;无法登录IAM用户怎么办?错误提示:您的身份验证信息错误,请重试(已解决)
目录 0.背景问题分析 1.解决步骤 0.背景问题分析 windows 11 ,64位 我的问题情景: 首先我创建了aws的账户,并且可以用ROOT用户登录,但是在登录时选择IAM用户,输入ROOT的名字和密码,就会提示【您的身份验证…...
微服务基础知识
文章目录 微服务基础知识一、系统架构的演变1、单体应用架构2、垂直应用架构3、分布式SOA架构(1)什么是SOA(2)SOA架构 4、微服务架构5、SOA和微服务的关系(1)SOA(2)微服务架构 二、分…...
公司请人做的网站 域名属于谁/站长工具名称查网站
不同类型的消费者1、DefaultMQPushConsumer由系统控制读取操作,收到消息后自动调用传入的处理方法来处理设置好各种参数和传入处理消息的函数。系统收到后自动调用处理函数来处理消息,自动保存Offset,并加入新的消费者之后自动做负载均衡Cons…...
中贸网做的网站/湘潭营销型网站建设
修改表结构 --修改数据表 ALTER TABLE 表名 SQL代码的书写不考虑顺序,但是批量执行代码需要要考虑好先执行哪些,后执行哪些 在修改数据表结构时,必须要明确:修改的字段中是否存在数据,例如:如果需要更改…...
做deal网站/宁波网站推广怎么做
Spring Security网络上很多前后端分离的示例很多都不是完全的前后分离,而且大家实现的方式各不相同,有的是靠自己写拦截器去自己校验权限的,有的页面是使用themleaf来实现的不是真正的前后分离,看的越多对Spring Security越来越疑…...
网站开发z亿玛酷1流量订制/国际新闻直播
天津科技大学计算机科学与信息工程学院简介(College of Computer Science and Information Engineering)计算机科学与信息工程学院成立于2003年,其前身为成立于2000年的计算机系。学院以本科教育为主,稳步发展研究生教育,积极开展中外合作办学…...
南川网站建设/自媒体平台大全
今天突然磁盘满了,查看了一下,都是k8s、docker关联的目录占用太大(/var/lib/docker/overlay2和/data/registry/docker/registry/v2/blobs/sha256),使用第一种方式删除了悬空镜像,清理了19G的空间࿰…...
游戏网站建设免费/域名批量注册查询
Java虚拟机的内存空间 Java虚拟机(Java Virtual MachineJVM)的内存空间分为五个部分,分别是: 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区。 1.2. 程序计数器的作用 程序计数器有两个作用: 字节码解释器通过改变程…...