freeRTOS

父类

证书

知识

实时系统

MIT

RTOS

系统简介

FreeRTOS 是采用一款适用于微控制器的开源实时操作系统,让您可以轻松地编写、部署、保护、连接和管理低功耗的小型边缘设备。

FreeRTOS 在 MIT 开源许可证下免费分发,包括一个内核和一组不断丰富的软件库,适用于各种行业部门和应用程序。

许多半导体厂商产品的 SDK(Software Development Kit—软件开发工具包) 包就使用 FreeRTOS 作为其操作系统,尤其是WiFi、BLE这些带协议栈的芯片或模块。

系统特点

FreeRTOS 是一个可裁剪的小型 RTOS 系统,其特点包括:

  • 内核支持抢占式,合作式和时间片调度。

  • 提供了一个用于低功耗的 Tickless 模式。

  • 系统的组件在创建时可以选择动态或者静态的 RAM,比如任务、消息队列、信号量、软件定时器等等。

  • FreeRTOS-MPU 支持 Corex-M 系列中的 MPU 单元,如 STM32F429。

  • FreeRTOS 系统简单、小巧、易用,通常情况下内核占用 4k-9k 字节的空间。

  • 高可移植性,代码主要 C 语言编写。

  • 高效的软件定时器。

  • 强大的跟踪执行功能。

  • 堆栈溢出检测功能。

  • 任务数量不限。

  • 任务优先级不限。

系统状态

FreeRTOS中的任务一共有四种状态分别是运行状态(Running State),就绪状态(Ready State),阻塞状态(Blocked State),挂起状态(Suspended State)

TCB_t的全称为Task Control Block,也就是任务控制块,这个结构体包含了一个任务所有的信息

任务调度

FreeRTOS对任务的调度采用时间片(time slicing)的调度方式。时间片,顾名思义,把一段时间等分成了很多个时间段,在每一个时间段保证优先级最高的任务能执行,同时如果几个任务拥有相等的优先级,则它们会轮流使用每个时间段占用CPU资源。调度器会在每个时间片结束的时候通过周期中断(tick interrupt)执行一次,选择哪个任务在下一个时间片会运行。

时间片的大小由configTICK_RATE_HZ这个参数设置。如果configTICK_RATE_HZ设置为10HZ,则时间片的大小为100ms。configTICK_RATE_HZ的值由应用需求决定,通常设为100HZ(时间片大小相应为10ms)。

vTaskList( char * pcWriteBuffer ) 这个函数可以打印出栈名、栈状态、优先级、栈的剩余空间,使用该功能FreeRTOSconfig.h要配置:

  • configUSE_TRACE_FACILITY 1

  • configUSE_STATS_FORMATTING_FUNCTIONS 1

任务调度机制是嵌入式实时操作系统的一个重要概念,也是其核心技术。对于可剥夺型内核,优先级高的任务一旦就绪就能剥夺优先级较低任务的CPU使用权,提高了系统的实时响应能力。不同于μC/OS-II,FreeRTOS对系统任务的数量没有限制,既支持优先级调度算法也支持轮换调度算法,因此FreeRTOS采用双向链表而不是采用查任务就绪表的方法来进行任务调度。

具有固定优先级调度程序的RTOS的核心思想是,应该在具有较低优先级的任务之前安排高优先级任务,但是当两个或多个任务需要协调其工作与全局数据区等共享资源或外围设备时,可能会导致系统出错。

其中一个可能出错的事情就是优先级反转(priorityinversion),低优先级任务无意中阻止了具有更高优先级的任务。 如果你意识到这个陷阱,这也很容易地避免。但是,如果发现系统的响应性偶尔会出现延迟,则可能是因为优先级反转。使用Tracealyzer,可以通过绘制任务的响应时间来发现此类延迟。要查看此图中任何极端值的原因,只需双击以显示相应的任务执行跟踪。

任务间通讯

任务通讯的几种方式:queue,semaphores mutexes和event groups。

其中semaphores mutexes都是基于队列的方式实现,notify机制和event groups最为类似,但是实现方式有较大差异。Notify机制是在每个任务中添加一个32位无符号字符标记,其他任务对该任务的通知。

信号量

信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。

  • 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。

  • 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

  • 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。

二值型信号量

二值信号量相当于长度为1的队列,那么计数型信号量就是长度大于1的队列,同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列是否为空即可。

二值型信号量是任务间、任务与中断间同步的重要手段。

  • 1、没有优先级继承

  • 2、可以在中断中使用

  • 3、可以在其他任务释放

互斥型信号量

互斥型信号量是任务间资源保护的重要手段。

申明互斥型信号量,在FreeRTOS中二值型信号量和互斥型信号量类型完全相同。从功能上二值型信号量用于同步,而互斥型信号量用于资源保护。

不同于二值信号量的是互斥信号量具有优先级继承的特性,可以有效解决优先级反转现象。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会被低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级传承。

  • 1、优先级继承

  • 2、互斥量不能在中断中使用

  • 3、互斥量获取和释放需要再同一个task中

递归互斥信号量

递归互斥信号量可以看做一个特殊的互斥信号量,已经获取了互斥信号量的任务就不能再次获取这个互斥信号量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量,而且次数不限制。并且获取多少次信号量,就需要释放多少次信号量。

源文件解读

FreeRTOS

Demo 文件夹里面就是 FreeRTOS 针对不同的 MCU 提供的相关例程,其中就有 ST 的 F1、F4 和F7 的相关例程。

License 文件夹里面就是 相关的许可信息,要用 FreeRTOS 做产品的得仔细看看,尤其是要出口的产品。

Source 文件夹里面就是 FreeRTOS 的源码文件,include 文件夹是一些头文件,移植的时候是需要的,下面的这些.C 文件就是 FreeRTOS 的源码文件。

portable 文件夹里面就是FreeRTOS系统和具体的硬件之间的连接桥梁!MemMang 这个文件夹是跟内存管理相关的,我们移植的时候是必须的。

RVDS 文件夹针对不同的架构的 MCU 做了详细的分类,STM32F429 就参考 ARM_CM4F,打开 ARM_CM4F 文件夹,里面有两个文件,这两个文件就是我们移植的时候所需要的!

FreeRTOS-Plus

里面也有 Demo 和 Source,Demo 文件夹里存放的肯定是一些例程, 而Source文件夹中存放的并不是 FreeRTOS 系统的源码,是在这个 FreeRTOS系统上另外增加的一些功能代码,比如 CLI、FAT、Trace 等等。

问题总结

优先级翻转

taskA的任务优先级高于taskB,但是由于taskA等待请求获取shareData资源,taskC持有shareData资源但被优先级高于它的taskB抢占阻塞,于是高优先级的taskA被挂起。

优先级继承

在高优先级的taskA获取资源锁时,将taskC的优先级临时提高为taskA的优先级,那么上述案例中,taskB就无法打断taskC的执行,因此taskC执行完成释放资源锁后,taskA能及时的进入ready状态

优先级恢复流程相对比较简单,在taskC使用完,调用释放接口的时候,会执行优先级恢复,此时taskC继续恢复其低优先级。

信号量一般是用于同步的,同步的场景上,需要保证优先级高的任务优先执行,做到真正的实时性,优先级继承会打破这个需求。

死锁

死锁是两个或多个任务之间的循环依赖。

例如,如果任务1已经获得A,并且被阻止等待B,而任务2先前已获得B,并且被阻止等待A,则这两个任务都不会被唤醒。 尽管没有更高优先级的任务正在运行,但是当多个任务突然停止执行时,可能是出现死锁问题的明确迹象。 同样,死锁的检测是Tracealyzer可以展示的内容。

如果希望避免死锁,首先要注意的是,只有当任务试图同时持有两个资源时才会发生死锁。 因此:构建代码时,使任何任务在同一时间都不会持有多个共享资源,这样不会产生死锁。

内存泄漏

通常不建议在嵌入式软件中进行动态内存分配,但有时会出于各种原因(对或错)进行动态内存分配。问题在于,如果使用它,则必须确保一旦内存块不再使用时,就释放每个已分配的内存块。如果在某些情况下遗漏了这一点,就会出现内存泄漏,并最终耗尽内存。请记住:即使在项目中禁止动态内存分配,也可能有第三方软件库或外部开发团队在不知情的情况下使用动态内存分配。

如果内存泄漏只是偶尔发生,那么它就特别危险,因为在功能测试期间很容易错过“缓慢”的内存泄漏,但在部署单元一段时间后,可能会导致严重错误。考虑到许多嵌入式系统的长期运行特性,以及一些安全关键系统可能存在的致命或严重故障,内存泄漏是绝对不希望在软件中出现的一个错误。

ARM对嵌入式操作系统进行了顶层设计,不同的操作系统要对他进行适配,这样更换操作系统就比较方便了,使用ARM提供的API编写的应用层程序,更换操作系统后是不需要修改的。

RTOS对比

对比uCOS-III

从文件数量上来看 FreeRTOS 要比uC/OSII 和 uC/OSIII 小的多。

uCOS-III中所有的内核对象(如任务控制块、消息队列、信号量等)都是静态创建的,需要用户提供。FreeRTOS中的内核对象支持动态和静态两种创建方法。

为了实现中断和任务的同步,需要在中断中进行post操作,uC/OS-III为了减少中断执行的时间,提高系统中断响应的实时性,设计了OS_tickTask和OS_IntQTask,这样原本在中断里需要进行的一些较为耗时的操作就被放到了任务级代码中执行了。而FreeRTOS并没有这样的设计。

在FreeRTOS的PendSV中断中,它会计算就绪的最高优先级的任务,再去进行上下文切换。而uC/OS-III在触发PendSV中断前,会计算好已就绪的最高优先级的任务,放在OSTCBHighRdyPtr中,这样在PendSV中断中就不用计算就绪的最高优先级的任务是谁了。所以uC/OS-III中PendSV中断的执行时间更短,这有利于提高系统的实时性。

uCOS-III的任务操作句柄就是任务控制块TCB的指针。FreeRTOS中单独设置了任务操作句柄这种数据类型,它实质上也是TCB的指针。表面上看,多此一举,但其实这种设计对用户是友好的,用户不需要了解TCB这种内核数据结构的存在,就可以操作任务了。

uCOS-III内核中的链表大多是不循环的双向链表(有头有尾),在插入和删除操作时,要考虑特殊情况(比如插入表头、插入表尾等特殊情况)。

而FreeRTOS内核中的链表为双向循环链表,并引入了xListEnd保证了链表永远非空,所以每个元素的插入和删除都是作为表中的一般元素(非表头和表尾)进行的,操作效率要比uC/OS-III高一些。

FreeRTOS功能更丰富、更易用;uC/OS-III的实时性更好、效率更高、健壮性更好。

其实RTOS最主要的功能就是任务调度,其它功能都可以自己开发,难度不大。单独从任务调度器的角色出发去对比这两个RTOS,我觉得uC/OS-III更漂亮、更优秀。

uC/OS-III通过的安全认证比FreeRTOS要多,FreeRTOS的代码书写是不符合一些标准的。在FreeRTOS的基础上建立了另外两个RTOS:SafeRTOS、OpenRTOS,它们具有更好的安全性,通过了更多的检验和标准,但是与FreeRTOS不一样,需要收费。

相关问题

μC/OS 2.86任务卡死在低优先级任务出不来,高优先级任务不执行,后来从Micrium下载μC/OS 2.91从里面what’s new.pdf里面查到对Cortex-M3有问题(中断优先级大小顺序问题),已修正,然后用高版本的果然没问题。

然后在下一个项目里面使用了FreeRTOS,感觉跟μC/OS差不多,只是任务栈消耗的稍大。我用的IAR,里面有μC/OS、FreeRTOS插件,可以在运行的时候看到任务栈历史最大使用和当前使用,以及CPU负载率等等很重要的信息。

NVIC_PriorityGroup_4 抢占优先级的要比“MAX”更大,而比“LOWEST”更小

configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY=5
configLIBRARY_LOWEST_INTERRUPT_PRIORITY=15

FreeRTOS中数值越大优先级越高,这种优先级可以成为逻辑优先级。Cortex M3/M4中断中,数值越大优先级越低,这种优先级成为中断优先级。两者相反,所以才会出现比”MAX”更大而比“LOWEST”更小的情况。