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.mzph.cn/web/55065.shtml

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

相关文章

windows修改文件最后修改时间

一、需要修改日期的文件 背景:有时候我们需要做一些文件定期删除的操作,但是测试时候并不一定有符合测试的文件,这时候就需要可以方便的修改文件的最后修改时间。 系统环境:windows 测试文件:如上 修改时间方式:windows 脚本。 二、测试脚本 (1) 脚本 # 指定文件路径 …

自然语言处理:第五十三章 Ollama

代码: ollama/ollama: Get up and running with Llama 3.1, Mistral, Gemma 2, and other large language models. (github.com) 官网: Ollama 写在前面: 笔者更新不易,希望走过路过点个关注和赞,笔芯!!! 写在前面: 笔者更新不易…

Android Framework默认授予app通知使用权限

安卓通知使用权限 在安卓系统中,应用程序需要获取通知使用权限才能向用户发送通知。以下是关于安卓通知使用权限的一些信息: 权限获取方式 当用户安装应用时,系统可能会在安装过程中提示用户授予应用通知权限。用户可以选择允许或拒绝。 应…

架构设计笔记-18-安全架构设计理论与实践

知识要点 常见的安全威胁: 信息泄露:信息被泄露或透露给某个非授权的实体。破坏信息的完整性:数据被非授权地进行增删、修改或破坏而受到损失。拒绝服务:对信息或其他资源的合法访问被无条件地阻止。攻击者向服务器发送大量垃圾…

OCM认证考试须知:掌握这些关键点,轻松应对考试

在Oracle认证体系中,OCM(OracleCertifiedMaster)是最高级别的认证。它代表了在Oracle数据库技术领域的顶尖水平。 OCM认证不仅要求你具备深厚的理论知识,还要求你能够解决复杂的数据库问题,并具备高级的项目管理能力。…

数据结构期中代码注意事项(二叉树及之前)1-11

注意&#xff1a;链表为空。是否越界访问。每写一步都要思考该步是否会有越界&#xff08;多/少&#xff09;等问题。这一步是否有不能走的条件&#xff08;删除的时候不为空&#xff09;。只有该节点开辟了空间&#xff0c;该节点才能被指向。能用c就用c。#include <iostre…

TensorRT-LLM七日谈 Day4

在Day2 中&#xff0c;我们梳理了trt-llm对于TinyLLama的调用&#xff0c;在Day3,我们也熟悉了一下Trt-llm常规的三步流程。 这里其实有个问题&#xff0c;在针对tiny-llama的部署中&#xff0c;其实没有显式的进行模型转换&#xff0c;那麽其推理接口中到底包含了什么&#x…

46 C 语言文件的打开与关闭、写入与读取函数:fopen、fclose、fputc、fputs、fprintf、fgetc、fgets、fscanf

目录 1 文件的存储形式 2 打开文件——fopen() 函数 2.1 功能描述 2.2 函数原型 2.3 文件打开方式&#xff08;模式&#xff09; 3 关闭文件——fclose() 函数 3.1 功能描述 3.2 函数原型 4 常见的文件写入方式 4.1 fputc() 函数 4.1.1 功能描述 4.1.2 函数原型 4…

windows自动化(一)---windows关闭熄屏和屏保

电脑设置关闭屏幕和休眠时间不起作用解决方案 一共三个方面注意&#xff1a; 一、关闭屏保设置&#xff1a; 二、电源管理设置 三、关闭盖子不做操作&#xff1a; 第一点很重要&#xff0c;就算二三都做了&#xff0c;一没做&#xff0c;照样不行。

一篇python的pandas数据分析,分组与聚合使用!

在数据分析中,数据分组与聚合是常用的操作,能够帮助我们从大量数据中提取出有用的信息.我们讨论了描述性统计,了解了如何通过均值、方差等统计量概述数据的特征.而在本篇中,我们将学习如何对数据进行分组和聚合,以便进行更深入的分析.最后,我们将在后续的章节中使用这些分析结果…

PHP政务招商系统——高效连接共筑发展蓝图

政务招商系统——高效连接&#xff0c;共筑发展蓝图 &#x1f3db;️ 一、政务招商系统&#xff1a;开启智慧招商新篇章 在当今经济全球化的背景下&#xff0c;政务招商成为了推动地方经济发展的重要引擎。而政务招商系统的出现&#xff0c;更是为这一进程注入了新的活力。它…

ES(Elasticsearch)SSL集群部署

8.x后ES不在需要自行准备JDK环境&#xff0c;部署的服务包含ES、Kibana、Logstash&#xff0c;使用二进制方式部署&#xff0c;为了提高安全性&#xff0c;加密logstash、kibana及其他客户端到ES间的通信。 1、准备工作 1.1、 es无法使用root用户启动 useradd -m -s /bin/bas…

WebGl 使用uniform变量动态修改点的颜色

在WebGL中&#xff0c;uniform变量用于在顶点着色器和片元着色器之间传递全局状态信息&#xff0c;这些信息在渲染过程中不会随着顶点的变化而变化。uniform变量可以用来设置变换矩阵、光照参数、材料属性等。由于它们在整个渲染过程中共享&#xff0c;因此可以被所有使用该着色…

【C#网络编程】基础概念2

文章目录 网络、数据包和协议网络数据包协议TCP、UDP 地址客户端和服务器套接字 网络、数据包和协议 计算机网络通过通信通道互连的机器组成&#xff0c;通常把这些机器称为主机和路由器&#xff0c;主机是是运行应用程序&#xff08;如 Web 浏览器&#xff09;的计算机。路由器…

决战Linux操作系统

前言&#xff1a; 你是否也曾经为Linux所困扰过&#xff0c;在网上找的资料零零散散&#xff0c;是否学完Linux后还是懵懵懂懂&#xff0c;别怕&#xff0c;这篇博客是博主精心为你准备的&#xff0c;现在&#xff0c;就让我们一起来走进Linux的世界&#xff0c;决战Linux&…

一文详解数据库范式

背景 在开发中&#xff0c;我们经常需要考虑如何设计合适的表结构&#xff0c;而则往往需要考虑数据库的范式。数据库的三范式&#xff08;3NF&#xff09;是数据库设计过程中用来减少数据冗余和提高数据一致性的重要规则。它们分别是第一范式&#xff08;1NF&#xff09;、第二…

oracle数据坏块处理(一)-通过rman备份修复

表有坏块时&#xff0c;全表查询会报错&#xff1a; 这时候如果有前面正常的rman备份&#xff0c;那么我们就可以通过rman备份直接对数据文件块做恢复 先对数据文件做个逻辑检查&#xff1a; RMAN> backup check logical VALIDATE DATAFILE EXB_DATA/exb/datafile/cuteinf…

C#中Assembly3个获取路径的方法

在C#中&#xff0c;经常要获取路径 &#xff0c;可以通过Assembly的三个重载方法来获取&#xff0c;如下所示这三个分别是GetCallingAssembly、GetEntryAssembly和GetExecutingAssembly。 string tmpEntryPath Assembly.GetEntryAssembly().Location;string tmpExeasmPath As…

STM32CubeIDE使用ADC采用DMA重大BUG

问题描述 STM32CubeIDE 1.8.0问题 大牛攻城狮最近调试STM32L151CBT6。由于项目上使用该款芯片做控制电源使用&#xff0c;其中涉及到多路ADC的数据采样。使用STM32CubeIDE 1.8.0版本详细如下图所示 这里大概率是STM32CubeMX版本太低了&#xff0c;从图上看才是6.4.0 注意这里…

五、UI弹窗提示

一、制作弹窗UI 二、创建脚本 1、继承WindowRoot&#xff08;UI基类&#xff09; 获取UI上面的组件 2、初始化 将这个文本失活 3、写一个提示出现的方法 这个派生类中&#xff0c;继承了基类的两个方法&#xff0c;设置显示和设置文本 对应基类的这两个方法 将动画赋值给动…