FreeRTOS学习总结

news2024/10/10 8:49:45

背景:在裸机开发上,有时候我们需要等待某个信号或者需要延迟时,CPU的运算是白白浪费掉了的,CPU的利用率并不高,我们希望当一个函数在等待的时候,可以去执行其他内容,提高CPU的效率,同时提高程序运行的效率,因此,我们决定使用实时操作系统。

实时操作系统有很多,这里学用的是freeRTOS,实时操作系统能够实现并发操作,所谓的并发操作就是能够看起来同时在做多件事情,这样就能很好地解决上述问题,当任务在等待时,直接切换到另一个任务执行就好了,等待信号到了,再切换回来执行,这样就很好地提高了CPU的利用率,延迟也是一样的,既然要延迟一定时间,与其让CPU什么都不做,不如让这段时间去做其他的事情。

一、任务

为了提高程序运行的效率,我们可以根据需要,对实现某个功能的函数创建一个任务,然后让任务在等待时,切换到其他任务。当只有一个任务时,程序就像在裸机上开发一样,因此,我们需要讨论的更多是多任务的情况。

1、变量

        a、局部变量

        在每个任务里创建的局部变量,它们都会分配自己的栈内存。不同任务的局部变量,都拥有不同的副本。因此,我们在不同任务之间进行切换时,这些局部变量不会互相干涉。

        b、全局变量,静态变量

        对于这些变量,多个任务使用的是同一个副本,也就是说,每个任务都可能操作这个变量,进而影响到其他任务的正确运行。因为任务可能在某个时候被切换走,如果在修改变量的时候被切换了并且被其他任务修改了该变量,可能就会导致出错。

        当我们在学习FreeRTOS的时候,更多是学习如何解决多个任务对于使用这些变量之间的冲突,或者让其同步。而某个信息对于多个任务之间互相有影响,我们称之为通信。当然,要保证通信的正确性,后续会采用几种通信方式来解决冲突的问题。

2、任务

        a、任务优先级

        在上述内容中说明了FreeRTOS实现的是并发操作,说它是看起来像是同时在运行,因为任务之间切换很快,所以对于我们来说,就像是多个任务在同时运行一样。那么在任务切换的时候,我们就需要用到任务优先级了。

        不是说像是同时运行一样吗,那优先级高低都无所谓吧?其实“像是同时运行一样”就是我们通过控制任务优先级实现的结果,所以同时运行的前提是我们让每个任务都有机会运行,然后快速切换就能够实现同时运行的效果了。如果我们给一个任务最高的优先级,并且不让其出现等待的情况,那么其他任务就不能执行,也就不会出现多个任务同时运行的情况了。

        我们在创建任务的时候,我们都会给任务一个优先级。通常来说,优先级高的任务在准备好的时候优先执行,这个准备好可能是等待到了某个需要的东西或者说延迟到了。如果优先级高的函数不挂起,就是上述说的不出现等待或者其他情况,就会一直执行,然后其他任务不能执行就被饿死了。

        现在我们知道优先级高的任务会优先执行了,那么优先级相同的呢?那优先级相同,说明两个任务同等重要,这个时候只要让他们轮流执行就好了。轮流执行,一个任务要执行多久才进行切换呢?FreeRTOS给我们提供了一个tick 的中断,你可以通过设置这个tick来配置切换的时长。

        我们知道,我们可以创建任务和删除任务,如果我们只有一个任务呢?而且我们还让这个任务暂停了,这个时候会发生什么,没有任务执行了吗?事实上,FreeRTOS有一个空闲任务Idle task,当没有任务执行时,就会运行这个任务,同时,对于释放内存的行为,也是在Idle任务里进行的,因此如果不加以暂停让Idle运行的话,如果一直创建任务且不释放任务,内存最后会耗尽。(我们之前提到每个任务里的局部变量都有自己的栈还要TCB)因此任务被删除时,应该释放这部分内存。

        这个Idle任务的优先级是最低的,为了保证你自己的任务能够正常运行不被打断。

        任务的切换是通过调度器实现的,如果我在某个时候不想切换任务,那我就把调度器暂停就好了,等忙完了我的事情再把它恢复。

        b、任务状态

        在FreeRTOS中,任务有三个状态,分别是阻塞态,就绪态,暂停态

        很多时候,我们的任务不会一直运行而是等到有某个信号的时候才开始运行,然后运行完之后再继续等待。等待着的任务就可以说这个任务处于阻塞态,需要牢记阻塞的概念,在后续会频繁地使用阻塞这个名词,在实现数据的同步和互斥时,都是通过任务间的阻塞来实现的。处于阻塞态的任务是不占用CPU的,这也是我们使用RTOS操作系统的本质。

        就绪态就是已经准备好的任务,处于这个状态的任务只要一切换到它就可以运行,比如说有个低级任务也不用等待什么信号才能运行,就只需要高优先级的任务执行完切换到它就可以运行了,那么这个任务就是处于就绪态的。处于就绪态的任务只要一切换到它就能执行。

        暂停态顾名思义就是暂停了,可以在任务中自己暂停自己,但是要退出暂停就需要别人来进行操作。那暂停态和阻塞态的区别是什么?暂停态要运行的信号是需要自己给定的,在什么时候调用退出暂停函数就能进入就绪态。而阻塞态是等到什么信号就能进入就绪态,这个信号什么时候来都可以。

        但是按照个人的理解,如果在某个信号出现的时候调用退出函数,应该也能让处于暂停态的任务实现和阻塞态任务一样的操作。一般情况下,暂停态使用较少。

        3、调度算法

        所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。我们在上文中知道,准备好了等着运行的任务就是处于就绪态的任务,那有多个任务处于就绪态的时候,如何协调好它们之间的切换就是调度算法要做的事情。

        在上文说的优先级高的能够抢占低优先级的任务,同等优先级的任务轮流运行,其实就是调度算法的一种实现,你也可以通过配置来实现同等优先级不轮流运行。

        调度算法可配置的三个宏定义:

        可抢占:

        意味着高优先级的任务准备好了之后可以直接运行,就是会从低优先级手里把CPU“抢”过来。

        不可抢占:

        不能抢就只能协商或者等待了,就算是低优先级,也得等他主动让出CPU,高优先级的任务才能运行。更高优先级的都不能抢占,那同等优先级的就更不用说了,因此也就不能配置时间片轮转。

        时间片轮转:

        时间片轮转的前提是配置可抢占,之后同等优先级的任务如果需要轮流运行,就配置为1。

        不轮转:

        同等优先级的任务不轮流执行,只能等任务主动让出CPU或者被更高级的任务抢占。

        时间片轮转调度的一个关键特点是任务可以被其他任务抢占。当一个任务的时间片用完时,调度器会暂停该任务并切换到另一个任务执行。而在不可抢占的任务调度中,一旦一个任务开始执行,它会一直运行直到主动放弃处理器(例如进入阻塞状态等待某个事件)。在这种情况下,无法根据时间片来强制切换任务,因为任务不会被其他任务抢占。所以,不可抢占的任务调度与时间片轮转调度的机制不兼容,一般不能同时配置。

        空闲任务让步:

        空闲任务Idle是否让步于用户函数,让步意味着每执行一次就看看有没有用户函数要运行,主动让步给用户任务,低人一等。

        不让步:

        不让步就把空闲任务当作普通任务,大家轮流执行,没有谁更特殊。(但是这种情况出现在有和空闲任务相同优先级的情况,可以配置任务优先级和空闲任务优先级相同)一般情况下,空闲任务还是会被高优先级任务抢占。   

适用场景

  1. 对处理器资源要求低的系统:在一些资源受限的嵌入式系统中,空闲任务不让步可以确保处理器在没有其他任务运行时不会频繁进行任务切换,从而降低系统开销。

    • 例如,在一个简单的传感器节点中,大部分时间系统处于空闲状态,空闲任务不让步可以减少任务切换的开销,节省能源。
  2. 特定的实时性要求:在某些实时系统中,如果需要确保在特定情况下处理器不被空闲任务干扰,可以将空闲任务设置为不让步。

    • 比如在一个关键任务需要在特定时间内响应的系统中,为了避免空闲任务在关键任务执行前抢占处理器,可将空闲任务设置为不让步。

      4、同步和冲突

        同步和冲突主要是解决临界资源在多个任务访问的时候出现的冲突问题。什么是临界资源,同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时,应该是这样:A用完,B再用;B用完,A再用。

        实现对临界资源的管理,可以有同步和互斥的方式。

        能实现同步、互斥的内核方法有:任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)。

        a、队列

        队列可以实现通信同步,将对数据的访问有序进行。

一方面,队列避免了多个任务对临界资源的直接访问,解决了多个任务同时访问修改出现的冲突问题。

另一方面,队列的写入和读出是基于原子操作的,就是说在写入和读出的时候不会被其他任务中断。

  1. 避免直接竞争:通过将临界资源的访问封装在队列的操作中,任务不再直接竞争访问临界资源。例如,如果多个任务需要访问一个共享的内存区域,这些任务可以通过将数据放入队列和从队列中取出数据的方式来间接访问共享内存,而不是直接对共享内存进行读写操作。

    • 这样可以避免多个任务同时对临界资源进行读写操作时可能出现的冲突和数据不一致问题。
  2. 任务同步:队列的阻塞机制可以确保任务在正确的时间访问临界资源。例如,如果一个任务需要等待另一个任务完成对临界资源的操作后才能继续执行,它可以通过从队列中读取特定的数据项来实现同步。当另一个任务完成对临界资源的操作后,它将数据放入队列,从而通知等待的任务可以继续执行。

    • 这种同步方式可以保证任务之间对临界资源的访问顺序,避免冲突。
  3. 原子操作:在 FreeRTOS 中,队列的操作通常是原子性的。这意味着在执行队列的写入或读取操作时,不会被其他任务中断。例如,当一个任务正在向队列中写入数据时,其他任务不能同时对该队列进行写入或读取操作。

    • 这种原子性可以确保队列操作的完整性,避免在对临界资源进行访问时出现数据不一致的问题。

        在读写队列时,如果队列没有数据,或者说数据满了,要进行读写的任务会进入阻塞态(也可以通过函数设置不阻塞,直接不写入和读取)如果队列没有数据,导致多个任务进入了阻塞,数据来的时候应该让谁先读呢?优先级高的先读,同等优先级的等得更久的先读,确实是一种很合理的方式。

        b、信号量

        信号量分为二进制信号量,顾名思义就是只有两个值,0,1和计数型信号量,可以设置始末值。在信号量里有两个动作,give给,计数加一;take获得,计数减一。

        从字面意思来理解,只有给了东西之后才能获得,也就是有了值之后才可以进行减法,变成0。如果是二进制信号量,因为最大值是1,如果多次give给,只有第一次会成功。如果值为0的时候获得take,则会进入阻塞。

在 FreeRTOS 中,当一个任务在获取信号量时,如果没有信号量,会进入阻塞状态。这个阻塞会在以下情况结束:

当另一个任务或者中断服务程序释放(给出)信号量时,被阻塞的任务会被唤醒,结束阻塞状态。而不是等到另一个任务进入阻塞。另一个任务进入阻塞与否与当前被阻塞任务的唤醒条件无关。

        于是我们就可以使用上面的机制来对数据同步访问,我可以在一个任务里对某个全局变量进行修改,修改后给出信号量,在另一个任务中则在获得信号量之后才可以对全局变量进行修改,在没有获得信号量时进行阻塞,这样就防止了“同时”修改数据导致的变量出错。

        c、互斥锁

        互斥量其实和信号量类似,如果把信号量的给和获得类比于钥匙,那么信号量可以看作是我把钥匙给了你,你才能运行。而互斥量则是谁拿到钥匙就只有谁能开门。但是freertos没有实现这个,其他任务也可以打开你的锁,因此互斥锁更多是要求程序员在写代码时根据自己需求实现。

        死锁

        任务A获得信号量,但是还没有释放,在这之间任务A调用函数B,但是函数B要获得信号量,因为A没有释放,所以B阻塞,然后导致A休眠,但是B等待A释放锁,A休眠无法释放,死锁发生。

        递归锁

        为了解决死锁的发生,引入了递归锁。递归锁的特性是可以多次获得锁,多次释放。在上面的例子中,因为是在同一个任务中,因此函数B也可以继续获得这个锁,只需要后续对应相同的释放次数即可。

        d、事件组

        事件组可以简单地认为就是一个整数:
        1、该整数的每一位表示一个事件
        2、每一位事件的含义由程序员决定,比如:Bit0表示用来串口是否就绪,Bit1表示按键是否被按下
        3、这些位,值为1表示事件发生了,值为0表示事件没发生
        4、一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位
        5、可以等待某一位、某些位中的任意一个,也可以等待多位

        理解事件组可以像理解队列,信号量一样,都是要等待到需要的“通知”,不管是队列里有数据了还是信号量为1了,事件组也一样,你可以等待某个bit变为你需要的值,也可以等待多个bit之间进行的与或操作,表示你需要多个通知才触发,否则就进入阻塞。不过有一点要注意的是,队列和信号量都是消耗进行的,队列读了会少,信号量读了会减一,在事件组里,你可以更自由,决定是否修改该bit的值。

        事件发生时,可以唤醒所有符合事件的任务,因此具有广播功能。

        e、任务通知

        和之前的信号量不同,任务通知是有对象的,通知很明显可以通知谁,选择需要通知的任务。我们知道我们在前面实现通信都是引入了某个变量(结构体),比如需要创建一个队列,一个信号量作为通信的中介。而任务通知则不需要,在创建任务时,TCB结构体里就已经包含了。

每个任务都有一个结构体:TCB(Task Control Block),里面有2个成员:
1、一个是uint8_t类型,用来表示通知状态
2、一个是uint32_t类型,用来表示通知值

通知状态有3种取值:
1、taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
2、taskWAITING_NOTIFICATION:任务在等待通知
3、taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)

通知值可以有很多种类型:
1、计数值
2、位(类似事件组)
3、任意数值

可以通过发送任务通知函数让通知值加一,并把任务状态置为pending,然后接收任务取出通知值。有点类似计数信号量的用法,但因为通知值通知的是明确的任务,接收任务也只能通过自己的通知值来获取数据。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

朝花夕拾:多模态图文预训练的前世今生

Diffusion Models专栏文章汇总:入门与实战 前言:时间来到2024年,多模态大模型炙手可热。在上一个时代的【多模态图文预训练】宛若时代的遗珠,本文的时间线从2019年到2022年,从BERT横空出世讲到ViT大杀四方,…

通过阿里云Milvus与PAI搭建高效的检索增强对话系统

阿里云Milvus现已无缝集成于阿里云PAI平台,一站式赋能用户构建高性能的RAG(Retrieval-Augmented Generation)对话系统。您可以利用Milvus作为向量数据的实时存储与检索核心,高效结合PAI和LangChain技术栈,实现从理论到…

数学建模算法与应用 第8章 时间序列分析

目录 8.1 确定性时间序列分析方法 Matlab代码示例:移动平均法提取趋势 8.2 平稳时间序列模型 Matlab代码示例:差分法与ADF检验 8.3 时间序列的Matlab相关工具箱及命令 Matlab代码示例:ARIMA模型的建立 8.4 ARIMA序列与季节性序列 Matl…

【Golang】Go语言中缓冲bufio的原理解读与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

Ubuntu关闭anaconda自动进入base虚拟环境

问题描述:安装好Anconda后,每次打开终端后都会自动进入到base的虚拟环境中去 直接使用通常情况下也不会有什么影响,但是为了避免,有以下两个方法: 1.使用conda deactivate #每次使用conda deactivate,退…

鸿蒙开发(NEXT/API 12)【ArkWeb接入密码保险箱】系统安全

网页中的登录表单,登录成功后,用户可将用户名和密码保存到鸿蒙系统密码保险箱中。再次打开该网页时,密码保险箱可以提供用户名、密码的自动填充。 手机使用场景 在网站中输入用户名、密码,登陆成功后,ArkWeb会提示将用…

线程池的核心参数——Java全栈知识(50)

线程池的核心参数 线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数 corePoolSize 核心线程数目maximumPoolSize 最大线程数目 (核心线程救急线程的最大数目)keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此…

前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)

前端Vue字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin) 引言 最近前端引入了UI给的思源黑体字体文件,但是字体文件过于庞大,会降低页面首次加载的速度,目前我的项目中需要用到如下三个字体文…

视频理解模型

LSTM 视频分解成图片帧分别进行特征提取,最后把提取到的特征放到LSTM网络里提取时序信息。 3D-ConvNet 把一组图片帧作为一个整体输入到3D卷积网络中,由于多了一个维度,参数变得多,模型变深,但当时没有大量的视频数…

PMP--冲刺题--解题--71-80

文章目录 14.敏捷--合规--测试无问题,安全团队却拒绝部署,则意味着可能存在某方面安全问题71、 [单选] 一个项目经理正在为一家政府所有的公司管理一个采用迭代方法的项目。第一个有用的生产发布由三次迭代组成。每次迭代都在测试环境中成功通过了客户代…

qwt实现码流柱状图多色柱体显示

qwt实现码流柱状图多色柱体显示 1. 前言2. qt实现柱状图3.qwt基础说明3.1 qwt安装与使用3.1.1 下载qwt源码3.1.2 编译3.1.3 安装3.1.4 使用3.2 QwtPlotBarChart类3.2.1画图步骤3.2.2 specialSymbol3.3.3 barTitle4 BsBarChart定制4.1 每个柱体可以显示不同的颜色4.2 每个柱体可…

网络安全-IPv4和IPv6的区别

1. 2409:8c20:6:1135:0:ff:b027:210d。 这是一个IPv6地址。IPv6(互联网协议版本6)是用于标识网络中的设备的一种协议,它可以提供比IPv4更大的地址空间。这个地址由八组十六进制数字组成,每组之间用冒号分隔。IPv6地址通常用于替代…

大数据毕业设计选题推荐-B站热门视频数据分析-Python数据可视化-Hive-Hadoop-Spark

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Python 工具库每日推荐【Pillow】

文章目录 引言Python图像处理库的重要性今日推荐:Pillow工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:创建图像拼贴案例分析高级特性图像增强图像水印扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript 设计模式 专栏,感兴…

数学建模算法与应用 第5章 插值与拟合方法

目录 5.1 插值方法 Matlab代码示例:线性插值 Matlab代码示例:样条插值 5.2 曲线拟合的线性最小二乘法 Matlab代码示例:线性拟合 5.3 最小二乘优化与多项式拟合 Matlab代码示例:多项式拟合 5.4 曲线拟合与函数逼近 Matlab代…

深入理解链表(SList)操作

目录: 一、 链表介绍1.1、 为什么引入链表1.2、 链表的概念及结构1.3、 链表的分类 二、 无头单向非[循环链表](https://so.csdn.net/so/search?q循环链表&spm1001.2101.3001.7020)的实现2.1、 [单链表](https://so.csdn.net/so/search?q单链表&spm1001.2…

系统架构师备考记忆不太清楚的点-信息系统-需求分析

霍尔三维结构 逻辑维:解决问题的逻辑过程 过程有明确问题、确立目标、系统综合、系统分析、优化、系统决策、实施计划 时间维:工作进度 这个纬度则是做工作计划的输出 有 规划阶段、拟定方案、研制阶段、生产阶段、安装阶段、运行阶段、更新阶段 知…

TiDB 优化器丨执行计划和 SQL 算子解读最佳实践

导读 在数据库系统中,查询优化器是数据库管理系统的核心组成部分,负责将用户的 SQL 查询转化为高效的执行计划,因而会直接影响用户体感的性能与稳定性。优化器的设计与实现过程充满挑战,有人比喻称这是数据库技术要持续攀登的珠穆…

Android SELinux——基础介绍(一)

Android 系统的安全策略是保护用户的隐私和数据不受侵害的重要保证,一个相对安全的计算环境对于确保移动设备的安全至关重要。随着新的威胁不断出现,Android 的安全策略也在不断发展和完善,以应对新的挑战。 一、概念介绍 1、SELinux SELin…

图像处理(一)——CMC特刊推荐

特刊征稿 01 期刊名称: Data and Image Processing in Intelligent Information Systems 截止时间: 提交截止日期:2024年11月15日 目标及范围: 感兴趣的主题包括但不限于: 先进的数据处理技术; 智能数据分析; 智能系统…