当前位置: 首页 > news >正文

【学习日记】【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】时间片的实现

前言 本文以野火的教程和代码为基础&#xff0c;对 FreeRTOS 中时间片的概念作了解释&#xff0c;并且给出了实现方式&#xff0c;同时发现并解决了野火教程代码中的 bug。 一、时间片是什么 在前面的文章中&#xff0c;我们已经知道任务根据不同的优先级被放入就绪列表中不…...

CentOS Docker仓库和代理配置

无法直接访问外部网络时&#xff0c;除了Host自己的全局代理设置之外&#xff0c;需要单独给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中&#xff0c;可以直接打开Anaconda Prompt。下面是两种方法&#xff1a; Windows 10终端&#xff1a;在开始菜单中搜索"Anaconda Prompt"&#xff0c;然后点击打开。这将启动Anaconda Prompt终端&#xff0c;你可以在其中执行conda相关命…...

Python web实战之细说Django的集成测试

关键词&#xff1a; Python Web开发、Django、集成测试、实战、测试驱动开发、自动化测试、Selenium、测试框架、测试用例、代码覆盖率、持续集成 今天给大家分享一下Python Web开发——Django的集成测试&#xff0c;如何利用集成测试来提高代码质量、减少bug。 1. 什么是集成…...

Laravel 模型的作用域 模型的访问器和修改器 ⑨

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; THINK PHP &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…...

每日一学——交换机

交换机是一种网络设备&#xff0c;用于连接多台计算机和其他网络设备&#xff0c;以实现数据的交换和传输。它通过将数据包在不同端口之间转发&#xff0c;将数据从一个设备发送到目标设备。交换机可以提供高速、可靠和安全的局域网连接。 交换机的工作原理是根据目标MAC地址来…...

数学建模大全及优缺点解读

分类模型 1、距离聚类&#xff08;系统聚类&#xff09;&#xff08;常用&#xff0c;需掌握&#xff09; 优点&#xff1a; ①将一批样本数据按照他们在性质上的亲密程度在没有先验知识的情况下自动进行分类 ②是一种探索性的分析方法&#xff0c;分类结果不一定相同 例如&am…...

C++简介

文章目录 C简介C版本C11例子 C14例子 C17C20例子 C简介 C是一种高级编程语言&#xff0c;它是对C语言的扩展和增强。C由Bjarne Stroustrup于1980年发明&#xff0c;主要用于系统级编程、游戏开发、嵌入式系统等领域。 C具有许多特性&#xff0c;其中最重要的是面向对象编程&a…...

【广州华锐互动】3D空间编辑器:一款简洁易用的VR/3D在线编辑工具

随着虚拟现实技术的不断发展&#xff0c;数字孪生技术的应用已经被广泛应用于产品设计和制作中&#xff0c;能充分发挥企业应用3D建模的优势&#xff0c;凸显了三维设计的价值&#xff0c;在生产阶段也能够充分发挥3D模型的作用。 如今&#xff0c;广州华锐互动开发的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详解&#xff08;内存管理&#xff09; malloc/free典型用法内存分配实现过程brk和mmap申请小于128k的内存申请大于128k的内存释放内存brk和mmap的区别 new/delete典型用法 内存分配实现过程new/delete和malloc/free的区别malloc对于给每个进程分配的内…...

SpringBoot中Mapper.xml的入参方式

在SpringBoot开发过程中&#xff0c;我们使用 ***Mapper.xml***Mapper.java 来封装对数据库表的 CURD 操作&#xff0c;正常每张表会有一组对应的文件。 一、Mapper常见用法 下面例举一个查询操作&#xff1a; 数据表t_sap_customer&#xff0c;表中有字段id、code、name、c…...

回归预测 | MATLAB实现WOA-RBF鲸鱼优化算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现WOA-RBF鲸鱼优化算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现WOA-RBF鲸鱼优化算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#…...

浅析Python爬虫ip程序延迟和吞吐量影响因素

作为一名资深的爬虫程序员&#xff0c;今天我们很有必要来聊聊Python爬虫ip程序的延迟和吞吐量&#xff0c;这是影响我们爬取效率的重要因素。这里我们会提供一些实用的解决方案&#xff0c;让你的爬虫程序飞起来&#xff01; 网络延迟 首先&#xff0c;让我们来看看网络延迟对…...

【100天精通python】Day43:python网络爬虫开发_爬虫基础(urlib库、Beautiful Soup库、使用代理+实战代码)

目录 1 urlib 库 2 Beautiful Soup库 3 使用代理 3.1 代理种类 HTTP、HTTPS 和 SOCKS5 3.2 使用 urllib 和 requests 库使用代理 3.3 案例&#xff1a;自建代理池 4 实战 提取视频信息并进行分析 1 urlib 库 urllib 是 Python 内置的标准库&#xff0c;用于处理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 根据网上的例子&#xff0c;做了一个测试程序。 C# 操作Kafka_c# kafka_Riven Chen的博客-CSDN博客 但是执行下面一行时&#xff0c;弹出上面的异常&#xff0c;闪退。 consumer.Subscribe(queueName) 解决方案&…...

最新ChatGPT网站程序源码+AI系统+详细图文搭建教程/支持GPT4.0/AI绘画/H5端/Prompt知识库

一、前言 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01…...

chatGPT-对话柏拉图

引言&#xff1a; 古希腊哲学家柏拉图&#xff0c;在他的众多著作中&#xff0c;尤以《理想国》为人所熟知。在这部杰作中&#xff0c;他勾勒了一个理想的政治制度&#xff0c;提出了各种政体&#xff0c;并阐述了他对于公正、智慧以及政治稳定的哲学观点。然而&#xff0c;其…...

Java项目-苍穹外卖-Day04

公共字段自动填充 这些字段在每张表基本都有&#xff0c;手动进行填充效率低&#xff0c;且后期维护更改繁琐 使用到注解AOP主要 先答应一个AutoFill注解 再定义一个切面类进行通知 对应代码 用到了枚举类和反射 package com.sky.aspect; /*** 自定义切面类&#xff0c…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...