FreeRTOS学习总结

背景:在裸机开发上,有时候我们需要等待某个信号或者需要延迟时,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.rhkb.cn/news/443903.html

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

相关文章

视频格式不支持播放怎么办?几招教你转换成mp4格式

视频已成为我们生活中不可或缺的一部分,无论是学习、娱乐还是工作交流,视频都扮演着重要角色。然而,在享受视频带来的便利时,我们时常会遇到一个令人头疼的问题——视频格式不支持播放。不同设备、平台和软件对视频格式的支持各不…

什么是组态软件?Web组态软件又是什么?

从事相关工作的对“组态软件”应该都不陌生,那Web组态软件又是什么呢?本文将对Web组态可视化软件(下称“Web组态软件”)做简单介绍,可视化编辑器是Web组态软件中的一个重要功能模块。除了编辑器,还有哪些功能模块?又…

leetcode---素数,最小质因子,最大公约数

1 判断一个数是不是质数(素数) 方法1&#xff1a;依次判断能否被n整除即可&#xff0c;能够整除则不是质数&#xff0c;否则是质数 方法2&#xff1a;假如n是合数&#xff0c;必然存在非1的两个约数p1和p2&#xff0c;其中p1<sqrt(n)&#xff0c;p2>sqrt(n)。 方法3&…

医院管理新思维:Spring Boot技术应用

5系统详细实现 5.1 医生模块的实现 5.1.1 病床信息管理 医院管理系统的医生可以管理病床信息&#xff0c;可以对病床信息添加修改删除操作。具体界面的展示如图5.1所示。 图5.1 病床信息管理界面 5.1.2 药房信息管理 医生可以对药房信息进行添加&#xff0c;修改&#xff0c;…

Java中System类和RunTime类的Api

目录 System 类 1)out 2)err 3)in 4)currentTimeMillis() 5)nanoTime() 6)arraycopy(Object 要从里面复制东西的数组, int 要从里面复制东西数组的索引起始位置, Object 获得复制元素的数组, int 获得复制元素数组的起始索引, int 要复制东西的个数) 7)gc() 8)exit(int status)…

运维工具之ansible

Ansible 1.什么是ansible? ​ ansible是基于ssh架构的自动化运维工具&#xff0c;由python语言实现&#xff0c;通过ansible可以远程批量部署等。 2.部署前提 ​ 控制端需要安装ansible,被控制端要开启ssh服务&#xff0c;并允许远程登录&#xff0c;被管理主机需要安装py…

探讨Facebook在全球社交网络中的技术优势

Facebook作为全球最大的社交网络之一&#xff0c;其技术优势在于多个方面&#xff0c;这些优势不仅塑造了用户体验&#xff0c;也影响了整个社交媒体生态。 个性化用户体验 Facebook通过分析用户的行为和兴趣&#xff0c;提供个性化的内容推荐。利用机器学习算法&#xff0c;平…

仅用一分钟,AI如何帮你构建完整的论文初稿?揭秘背后科技!

大家好&#xff01;在今天的分享中&#xff0c;我们将深入探讨一项令人兴奋的技术进展&#xff1a;仅用一分钟&#xff0c;AI如何帮助你构建一篇完整的论文初稿。这项技术不仅节省了研究人员和学生的宝贵时间&#xff0c;还改变了我们对学术写作的传统认知。 首先&#xff0c;…

【读书笔记·VLSI电路设计方法解密】问题10:从概念到硅片开发SoC芯片的主要任务

从概念到硅片的SoC芯片开发过程可分为以下四个任务&#xff1a;设计、验证、实现和软件开发。 设计&#xff1a;通常从市场调研和产品定义开始&#xff0c;然后进行系统设计&#xff0c;最后以RTL编码结束。验证&#xff1a;确保芯片按照设计规格能够准确执行功能&#xff0c;…

深度学习500问——Chapter17:模型压缩及移动端部署(4)

文章目录 17.9 常用的轻量级网络有哪些 17.9.1 SequeezeNet 17.9.2 MobileNet 17.9.3 MobileNet-v2 17.9.4 Xception 17.9 常用的轻量级网络有哪些 17.9.1 SequeezeNet SqueezeNet出自 F.N.landola, S.Han等人发表的论文《SqueezeNet&#xff1a;ALexNet-level accuracy with…

目标检测中的损失函数

损失函数是用来衡量模型与数据的匹配程度的&#xff0c;也是模型权重更新的基础。计算损失产生模型权重的梯度&#xff0c;随后通过反向传播算法&#xff0c;模型权重得以更新进而更好地适应数据。一般情况下&#xff0c;目标损失函数包含两部分损失&#xff0c;一个是目标框分…

RandLA-Net PB 模型 测试

tensorflow ckpt 模型 转换 pb 模型, 测试模型是否正确, 后续实现 c++ 部署。 Code: https://github.com/QingyongHu/RandLA-Net 测试PB 模型 RandLANetConvert.py import tensorflow.compat.v1 as tf tf.disable_v2_behavior

R语言中的plumber介绍

R语言中的plumber介绍 基本用法常用 API 方法1. GET 方法2. POST 方法3. 带路径参数的 GET 方法 使用 R 对数据进行操作处理 JSON 输入和输出运行 API 的其他选项其他功能 plumber 是个强大的 R 包&#xff0c;用于将 R 代码转换为 Web API&#xff0c;通过使用 plumber&#x…

PowerJob做定时任务调度

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、区别对比二、使用步骤1. 定时任务类型2.PowerJob搭建与部署 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; PowerJob是基于java开…

如何优化抖音直播间数据?

在数字驱动的时代&#xff0c;缺乏精准的数据支撑&#xff0c;任何线上活动都难以形成有效的流量循环。特别是在抖音直播这一领域&#xff0c;深入理解并优化核心数据&#xff0c;是提升直播效果、吸引并留住观众的关键。那么&#xff0c;抖音直播平台在评估一场直播时&#xf…

【重学 MySQL】四十六、创建表的方式

【重学 MySQL】四十六、创建表的方式 使用CREATE TABLE语句创建表使用CREATE TABLE LIKE语句创建表使用CREATE TABLE AS SELECT语句创建表使用CREATE TABLE SELECT语句创建表并从另一个表中选取数据&#xff08;与CREATE TABLE AS SELECT类似&#xff09;使用CREATE TEMPORARY …

安装最新 MySQL 8.0 数据库(教学用)

安装 MySQL 8.0 数据库&#xff08;教学用&#xff09; 文章目录 安装 MySQL 8.0 数据库&#xff08;教学用&#xff09;前言MySQL历史一、第一步二、下载三、安装四、使用五、语法总结 前言 根据 DB-Engines 网站的数据库流行度排名&#xff08;2024年&#xff09;&#xff0…

【Redis】持久化(上)---RDB

文章目录 持久化的概念RDB手动触发自动触发bgsave命令的运行流程RDB文件的处理RDB的优缺点RDB效果展示 持久化的概念 Redis支持AOF和RDB两种持久化机制,持久化功能能有效的避免因进程退出而导致的数据丢失的问题,当下次重启的时候利用之前持久化的文件即可实现数据恢复. 所以此…

一键生成PPT的AI工具-Kimi!

一键生成PPT的AI工具-Kimi&#xff01; 前言介绍Kimi为什么选择Kimi如何使用Kimi在线编辑PPT下载生成的PPT自己编辑 结语 &#x1f600;大家好&#xff01;我是向阳&#x1f31e;&#xff0c;一个想成为优秀全栈开发工程师的有志青年&#xff01; &#x1f4d4;今天不来讨论前后…

启动hadoop后没有 NodeManager和 ResourceManager

跟着黑马网课学下去时发现我的hadoop启动后没有NodeManager和ResourceManager 找到日志的路径 我在/export/server/hadoop/etc/hadoop/hadoop-env.sh文件里配置了日志存放的路径 这里找到你的日志路径&#xff0c;每个人的习惯和看的教程不同&#xff0c;日志放的地方大概率也…