实时操作系统分类、特点及实现原理

本章节将介绍各类操作系统的特点。

裸机系统

单片机的程序可以分为三种:轮循系统、前后台系统和多任务系统。

轮询系统

即在裸机编程时,先初始化相关硬件,让主程序在一个死循环里面不断循环,顺序地处理各种事件。不能说轮询是低端的,轮询系统是一种非常简单的软件结构,但适用于仅需要顺序执行代码且不需要外部事件来驱动就能完成的事情,这会变得简单可靠。

如果加入按键操作等需要检测外部信号的事件,整个系统的实时响应能力就会体现不好。试想一下,但按键按下时,程序正在运行顺序1程序,而且顺序1程序占用的程序时间片比较长,系统就有可能错过对按键的检测(直到按键松开),实时性极差,用户无法接受。

前后台系统

相比轮询系统,前后台系统在轮询系统的基础上加入了中断的概念,外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断我们称之为前台,main()函数中的无限循环称为后台。

在顺序执行后台程序时,如果有中断,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断服务程序中标记事件。如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回后台程序处理。通过中断可以大大提供程序的实时响应能力,避免造成外部事件的丢失。

多任务系统

相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务与中断一样,也具有优先级,优先级高的任务会被优先执行。但一个紧急事件在中断中被标志之后,如果事件对应的任务优先级足够高,就会立刻得到响应,相比前后台系统,多任务系统的实时性又被提高了。

在多任务系统中,程序的主体会分割成一个个独立的、无限循环且不能返回的任务,每个任务都是独立的、互不干扰的,而且具备自身的优先级,由操作系统调度管理。整个系统的额外开销就是操作系统占据的少量FLASH和RAM,但是对于现在的片上资源,已经是微不足道。

实时操作系统与分时操作系统

Linux操作系统是一种分时操作系统,但可以通过配置内核修改为实时操作系统(软实时)。

实时操作系统

实时操作系统(RTOS)是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系 统作出快速响应,并控制所有实时任务协调一致运行的操作系统。因而,提供及时响应和高可靠性是其主要特点。实时操作系统有硬实时和软实时之分,硬实时要求 在规定的时间内必须完成操作,这是在操作系统设计时保证的;软实时则只要按照任务的优先级,尽可能快地完成操作即可。我们通常使用的操作系统在经过一定改 变之后就可以变成实时操作系统。

实时操作系统是保证在一定时间限制内完成特定功能的操作系统。例如,可以为确保生产线上的机器人能获取某个物 体而设计一个操作系统。在“硬”实时操作系统中,如果不能在允许时间内完成使物体可达的计算,操作系统将因错误结束。在“软”实时操作系统中,生产线仍然 能继续工作,但产品的输出会因产品不能在允许时间内到达而减慢,这使机器人有短暂的不生产现象。一些实时操作系统是为特定的应用设计的,另一些是通用的。 一些通用目的的操作系统称自己为实时操作系统。但某种程度上,大部分通用目的的操作系统,如微软的Windows NT或IBM的OS/390有实时系统的特征。这就是说,即使一个操作系统不是严格的实时系统,它们也能解决一部分实时应用问题。

特点

  • 多任务;
  • 有线程优先级
  • 多种中断级别

相关术语

  • 代码临界段:指处理时不可分割的代码。一旦这部分代码开始执行则不允许中断打入;
  • 资源:任何为任务所占用的实体;
  • 共享资源:可以被一个以上任务使用的资源;
  • 任务:也称作一个线程,是一个简单的程序。每个任务被赋予一定的优先级,有它自己的一套CPU寄存器和自己的栈空间。典型地,每个任务都是一个无限的循环,每个任务都处在以下五个状态下:休眠态,就绪态,运行态,挂起态,被中断态;
  • 任务切换:将正在运行任务的当前状态(CPU寄存器中的全部内容)保存在任务自己的栈区,然后把下一个将要运行的任务的当前状态从该任务的栈中重新装入CPU的寄存器,并开始下一个任务的运行;
  • 内核:负责管理各个任务,为每个任务分配CPU时间,并负责任务之间通讯。分为不可剥夺型内核于可剥夺型内核;
  • 调度:内核的主要职责之一,决定轮到哪个任务运行。一般基于优先级调度法;
  • 任务优先级:分为优先级不可改变的静态优先级和优先级可改变的动态优先级;
  • 优先级反转:优先级反转问题是实时系统中出现最多的问题。共享资源的分配可导致优先级低的任务先运行,优先级高的任务后运行。解决的办法是使用“优先级继承”算法来临时改变任务优先级,以遏制优先级反转。
  • 互斥
    • 虽然共享数据区简化了任务之间的信息交换,但是必须保证每个任务在处理共享共享数据时的排他性。使之满足互斥条件的一般方法有:关中断,使用测试并置位指令(TAS),禁止做任务切换,利用信号量。
    • 因为采用实时操作系统的意义就在于能够及时处理各种突发的事件,即处理各种中断,因而衡量嵌入式实时操作系统的最主要、最具有代表性的性能指标参数无疑应该是中断响应时间了。中断响应时间通常被定义为:
    • 中断响应时间=中断延迟时间+保存CPU状态的时间+该内核的ISR进入函数的执行时间[2]。
    • 中断延迟时间=MAX(关中断的最长时间,最长指令时间) + 开始执行ISR的第一条指令的时间[2]。

分时操作系统

使一台计算机同时为几个、几十个甚至几百个用户服务的一种操作系统。把计算机与许多终端用户连接起来,分时操作系统将系统处理机时间与内存空 间按一定的时间间隔,轮流地切换给各终端用户的程序使用。由于时间间隔很短,每个用户的感觉就像他独占计算机一样。分时操作系统的特点是可有效增加资源的 使用率。例如UNIX系统就采用剥夺式动态优先的CPU调度,有力地支持分时操作。

产生分时系统是为了满足用户需求所形成的一种新型 OS 。它与多道批处理系统之间,有着截然不同的性能差别。用户的需求具体表现在以下几个方面: 人—机交互 共享主机 便于用户上机

特点

  • 交互性:用户与系统进行人机对话。
  • 多路性:多用户同时在各自终端上使用同一CPU。
  • 独立性:用户可彼此独立操作,互不干扰,互不混淆。
  • 及时性:用户在短时间内可得到系统的及时回答。
  • 影响响应时间的因素:终端数目多少、时间片的大小、信息交换量、信息交换速度。

相关术语

  • 时间片 :是把计算机的系统资源(尤其是 CPU时间)进行时间上的分割,每个时间段称为一个时间片,每个用户依次轮流使用时间片。
  • 分时技术:把处理机的运行时间分为很短的时间片,按时间片轮流把处理机分给各联机作业使用。
  • 分时操作系统:是一种联机的多用户交互式的操作系统。一般采用时间片轮转的方式使一台计算机为多个终端服务。对每个用户能保证足够快的响应时间,并提供交互会话能力。
  • 设计目标: 对用户的请求及时响应,并在可能条件下尽量提高系统资源的利用率。
  • 工作方式:
    • 一台主机连接了若干个终端;每个终端有一个用户在使用;交互式地向系统提出命令请求;系统接受每个用户的命令;采用时间片轮转方式处理服务请求;并通过交互方式在终端上向用户显示结果;用户根据上步结果发出下道命令
    • 分时系统实现中的关键问题:及时接收。及时处理。
QNX LynxOS RT-Linux KURT-Linux
基本特征 QNX是一个分布式、嵌入式、可规模扩展的实时操作系统。它遵循POSIX.1、(程序接口)和POSIX.2(Shell和工具)、部分遵循POSIX.1b(实时扩展)。它最早开发于1980年,到现在已相当成熟。 LynxOS是一个分布式、嵌入式、可规模扩展的实时操作系统,它遵循POSIX.1a、POSIX.1b和POSIX.1c标准。它最早开发于1988年。 RT-Linux是一个嵌入式硬实时操作系统,它部分支持POSIX.1b标准。 KURT-Linux不是为嵌入式应用设计的,不同于硬(hard)实时/软(soft)实时应用,他们提出”严格(firm)”实时应用的概念,如一些多媒体应用和ATM网络应用,KURT是为这样一些应用设计的”严格的”实时系统。
体系结构 QNX是一个微内核实时操作系统,其核心仅提供4种服务:进程调度、进程间通信、底层网络通信和中断处理,其进程在独立的地址空间运行。所有其它OS服务,都实现为协作的用户进程,因此QNX核心非常小巧(QNX4.x大约为12Kb)而且运行速度极快。 LynxOS目前还不是一个微内核结构的操作系统,但它计划使用所谓的”Galaxy”技术将其从大型集成化内核改造成微内核,这一技术将在 LynxOS 3.0中引入。新的28Kb微内核提供以下服务:核心启动和停止、底层内存管理、出错处理、中断处理、多任务、底层同步和互斥支持。 RT-Linux实现了一个小的实时核心,仅支持底层任务创建、中断服务例程的装入、底层任务通信队列、中断服务例程(ISR)和Linux进 程。原来的非实时Linux核心作为一个可抢先的任务运行于这个小核心之上,所有的任务都在核心地址空间运行。它不同于微内核和大型内核,属于实时 EXE(realtime executive)体系结构。其可靠性和可维护性对电信服务系统来说都不够理想。 KURT-Linux核心包括两个部分:内核和实时模块。内核负责实时事件的调度,实时模块为用户进程提供特定的实时服务。它不属于微内核结构。
调度策略 QNX 提供POSIX.1b标准进程调度:32个进程优先级;抢占式的、基于优先级的正文切换;可选调度策略:FIFO、轮转策略、适应性策略。 LynxOS 其调度策略为:LynxOS支持线程概念,提供256个全局用户线程优先级;硬实时优先级调度:在每个优先级上实现了轮转调度、定量调度和FIFO调度策略;快速正文切换和阻塞时间短;抢占式的RTOS核心。 在操作系统之下实现了一个简单的实时核心,Linux本身作为一个可抢占的任务在核内运行,优先级最低,随时会被高优先级任务抢占。用户可自行编写调度程序,它们可实现为可加载的核心模块;已实现的调度程序有:基于优先级的抢占式调度和EDF调度;基于优先级的调度使用”单调率算法”,它直接支持周期任务。 可运行在两种状态之下:通常状态和实时状态。在通常状态下,所有进程都可以运行,但某些核心服务将带来中断屏蔽的不可预期性。实时模式只允许实时进程运行。支持FIFO调度策略、轮转调度策略和UNIX分时调度策略;增加了SCHED-KURT调度策略,这是一种静态调度策略,使用一个特殊的调度文件记录预先定义好的待调度进程的参数。
系统服务 多种资源管理器,包括各种文件系统和设备管理,支持多个文件系统同时运行,包括提供完全POSIX.1及UNIX语法的POSIX文件系统,支持多种闪存设备的嵌入式文件系统,支持对多种文件服务器(如Windows NT/95、LAN Manager等)的透明访问的SMB文件系统、DOS文件系统、CDROM文件系统等。设备管理。在进程和终端设备间提供大吞吐量、低开销接口服务。图形/窗口支持。包括QNX Windows、X Window System for QNX、对MS Windows NT/95和X Window系统的远程图形连接。TCP/IP for QNX。高性能、容错型QNX网络–FLEET,使得所有连入网络的计算机变成一个逻辑上的超级计算机。透明的分布式处理。FLEET网络处理与消息传递和进程管理原语的集成,将本地和网络IPC统一起来,使得网络对IPC而言是透明的。 网络和通信。TCP/IP协议栈。Internet工具。LynxOS流机制为开发和移植基于流的驱动程序和应用提供了核心支持。文件系统。实时的类UNIX层次结构文件系统:连续结构文件、带缓冲/不带缓冲、原始分区和原始设备访问。基于Motif的图形用户接口。分布式计算资源。SCMP与VME总线上的多处理结合,PCI桥服务、CompactPCI Hot-swap Services、Lynx/HA-DDS分布式数据系统。 UNIX用户的开发工具和应用软件都被移植到Linux上。TCP/IP网络协议。各种Internet客户/服务端软件。X Window。C/C++、Java等语言编译器。

RTOS概览

RTOS商业前景参考:实时操作系统(RTOS)市场简报

μClinux

μClinux是一种优秀的嵌入式Linux版本,其全称为micro-control Linux,从字面意思看是指微控制Linux。同标准的Linux相比,μClinux的内核非常小,但是它仍然继承了Linux操作系统的主要特性,包括良好的稳定性和移植性、强大的网络功能、出色的文件系统支持、标准丰富的API,以及TCP/IP网络协议等。因为没有MMU内存管理单元,所以其多任务的实现需要一定技巧。

μClinux在结构上继承了标准Linux的多任务实现方式,分为实时进程和普通进程,分别采用先来先服务和时间片轮转调度,仅针对中低档嵌入式CPU特点进行改良,且不支持内核抢占,实时性一般。

综上可知,μClinux最大特点在于针对无MMU处理器设计,这对于没有MMU功能的stm32f103来说是合适的,但移植此系统需要至少512KB的RAM空间,1MB的ROM/FLASH空间,而stmf103拥有256K的FLASH,需要外接存储器,这就增加了硬件设计的成本。

μClinux结构复杂,移植相对困难,内核也较大,其实时性也差一些,若开发的嵌入式产品注重文件系统和与网络应用则μClinux是一个不错的选择。

μC/OS-II

μC/OS-II是在μC/OS的基础上发展起来的,是用C语言编写的一个结构小巧、抢占式的多任务实时内核。μC/OS-II能管理64个任务,并提供任务调度与管理、内存管理、任务间同步与通信、时间管理和中断服务等功能,具有执行效率高、占用空间小、实时性能优良和扩展性强等特点。

在文件系统的支持方面,由于μC/OS-II是面向中小型嵌入式系统的,即使包含全部功能,编译后内核也不到10 KB,所以系统本身并没有提供对文件系统的支持。但是μC/OS-II具有良好的扩展性能,如果需要也可自行加入文件系统的内容。

在对硬件的支持上,μC/OS-II能够支持当前流行的大部分CPU,μC/OS-II由于本身内核就很小,经过裁剪后的代码最小可以为2KB,所需的最小数据RAM空间为4 KB,μC/OS-II的移植相对比较简单,只需要修改与处理器相关的代码就可以。

综上可知,μC/OS-II是一个结构简单、功能完备和实时性很强的嵌入式操作系统内核,针对于没有MMU功能的CPU,它是非常合适的。它需要很少的内核代码空间和数据存储空间,拥有良好的实时性,良好的可扩展性能,并且是开源的,网上拥有很多的资料和实例,所以很适合向stm32f103这款CPU上移植。

eCos

eCos(embedded Configurable operating system),即嵌入式可配置操作系统。它是一个源代码开放的可配置、可移植、面向深度嵌入式应用的实时操作系统。最大特点是配置灵活,采用模块化设计,核心部分由小同的组件构成,包括内核、C语言库和底层运行包等。每个组件可提供大量的配置选项(实时内核也可作为可选配置),使用eCos提供的配置工具可以很方便地配置,并通过不同的配置使得eCos能够满足不同的嵌入式应用要求。

eCos操作系统的可配置性非常强大,用户可以自己加入所需的文件系统。eCos操作系统同样支持当前流行的大部分嵌入式CPU,eCos操作系统可以在16位、32位和64位等不同体系结构之间移植。eCos由于本身内核就很小,经过裁剪后的代码最小可以为10 KB,所需的最小数据RAM空间为10 KB。在系统移植方面 eCos操作系统的可移植性很好,要比μC/OS-II和μClinux容易。

综上所述,eCos最大特点是配置灵活,并且支持无MMU的CPU的移植,开源且具有很好的移植性,也比较合适于移植到stm32平台的CPU上。但eCOS的应用还不是太广泛,还没有像μC/OS-II那样普遍,并且资料也没有μC/OS-II多。eCos适合用于一些商业级或工业级对成本敏感的嵌入式系统,例如消费电子领域中的一些应用。

FreeRTOS

由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对于μC/OS-II、 embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为6.0版。

作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。 FreeRTOS内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU总是让处于就绪态的、优先级最高的任务先运行。

FreeRT0S内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。

FreeRTOS的不足:相对于常见的μC/OS—II操作系统,FreeRTOS操作系统既有优点也存在不足。其不足之处, 一方面体现在系统的服务功能上,如FreeRTOS只提供了消息队列和信号量的实现,无法以后进先出的顺序向消息队列发送消息;另一方 面,FreeRTOS只是一个操作系统内核,需外扩第三方的GUI(图形用户界面)、TCP/IP协议栈、FS(文件系统)等才能实现一个较复杂的系统, 不像μC/OS-II可以和μC/GUI、μC/FS、μC/TCP-IP等无缝结合。

mbed OS

开源嵌入式操作系统,ARM公司将mbed OS免费提供给所有厂商使用,mbed提供了一个相对更加系统和更加全面的智能硬件开发环境。

主要功能:提供用于开发物联网设备的通用操作系统基础,以解决嵌入式设计的碎片化问题。支持所有重要的连接性与设备管理开放标准,以实现面向未来的设计。使安全可升级的边缘设备支持新增处理能力与功能。通过自动电源管理解决复杂的能耗问题。

主要特点:开发速度快,功能强大,安全性高,为了量产化而设计,可离线开发,也可以在网页上编辑。

RTX

是ARM公司的一款嵌入式实时操作系统,使用标准的C结构编写,运用RealView编译器进行编译。不仅仅是一个实时内核,还具备丰富的中间层组件,不但免费,而且代码也是开放的。

主要功能:开始和停止任务(进程),除此之外还支持进程通信,例如任务的同步、共享资源(外设或内存)的管理、任务之间消息的传递。开发者可以使用基本函数去开启实时运行器,去开始和终结任务,以及去传递任务间的控制(轮转调度)。开发者可以赋予任务优先级。

主要特点:支持时间片,抢占式和合作式调度。不限制数量的任务,每个任务都具有254的优先级。不限制数量的信号量,互斥信号量,消息邮箱和软定时器。支持多线程和线程安全操作。使用MDK基于对话框的配置向导,可以很方便的完成MDK的配置。

VxWorks

美国WindRiver公司于1983年设计开发的一种嵌入式实时操作系(RTOS),具有硬实时、确定性与稳定性,也具备航空与国防、工业、医疗、汽车、消费电子产品、网络及其他行业要求的可伸缩性与安全性。

主要功能:支持可预测的任务同步机制、支持多任务间的通信、存储器优化管理、操作系统的(中断延迟、任务切换、驱动程序延迟等)行为是可知的和可预测的。实时时钟服务+中断管理服务。

主要特点:具有一个高性能的操作系统内核Wind(实时性好、可裁减)友好的开发调试环境、较好的兼容性、支持多种开发和运行环境

QNX

诞生于1980年,是一种商用的遵从POSIX规范的类Unix嵌入式实时操作系统。

主要功能:支持在同一台计算机上同时调度执行多个任务;也可以让多个用户共享一台计算机,这些用户可以通过多个终端向系统提交任务,与QNX进行交互操作。

主要特点:核心仅提供4种服务:进程调度、进程间通信、底层网络通信和中断处理,其进程在独立的地址空间运行。所有其它OS服务,都实现为协作的用户进程,因此QNX核心非常小巧(QNX4.x大约为12Kb)而且运行速度极快。

NuttX

NuttX是一个实时嵌入式操作系统(Embedded RTOS),第一个版本由 Gregory Nutt 于 2007 年在宽松的 BSD 许可证下发布。

主要功能:可以构建为开放的、平面的嵌入式 RTOS,或单独构建为具有系统调用接口的微内核。容易扩展到新的处理器架构、 SoC 架构或板级架构。实时的、确定性的、支持优先级继承。BSD 套接字接口。优先级管理的扩展。可选的具有地址环境的任务(进程)。

主要特点:配置灵活,采用模块化设计,核心部分由小同的组件构成,包括内核、C语言库和底层运行包等。每个组件可提供大量的配置选项(实时内核也可作为可选配置),使用eCos提供的配置工具可以很方便地配置,并通过不同的配置使得eCos能够满足不同的嵌入式应用要求。

都江堰操作系统(djyos)

(注意:网友提示都江堰操作系统官网提示,stm32的f4和f7没调通)

都江堰操作系统,简称djyos,得名于一个伟大的水利工程:都江堰。与传统操作系统不同,djyos不是以线程而是以事件为调度核心,这种调度算法使程序员摆脱模拟计算机执行过程编写程序的思维方式,而是按人类认知世界的方式编写应用程序,就如同在嵌入式编程中引入了VC似的。djyos的调度算法使程序员可以摆脱线程和进程的束缚,djyos没有有关线程的api,一个完全不懂线程知识的程序员也可以顺利地在djyos下编写应用程序。djyos 操作系统是以事件为核心进行调度的,这种调度策略使程序员可以按人类认知事物的习惯而不是计算机的习惯来编程。

Alios Things

据著名媒体嵌入式操作系统RTOS介绍,AliOS Things 是 AliOS 家族旗下、面向 IoT 领域的、高可伸缩的物联网操作系统。AliOS Things将致力于搭建云端一体化IoT基础设施,具备极致性能、极简开发、云端一体、丰富组件、安全防护等关键能力,并支持终端设备连接到阿里云Link,可广泛应用在智能家居、智慧城市、新出行等领域。点评:阿里系,背靠阿里资源来势汹汹杀入物联网市场,芯片+模组厂商合作,是非常有力的一个玩家,但这也是其最大的劣势!

Huawei LiteOS

嵌入式操作系统RTOS介绍,Huawei LiteOS 是华为面向IoT领域,构建的轻量级物联网操作系统,以轻量级低功耗、快速启动、互联互通、安全等关键能力,为开发者提供 “一站式” 完整软件平台,有效降低开发门槛、缩短开发周期。点评:华为系,不过其开源程度比较低,其主要用于华为自己的产品,有大树罩着。

RT-Thread

嵌入式操作系统RTOS介绍,RT-Thread是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,由熊谱翔先生带领并集合开源社区力量开发而成,RT-Thread也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。RT-Thread具备一个IoT OS平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。经过11年的累积发展,RT-Thread已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过两千万台,成为国人自主开发、国内最成熟稳定和装机量最大的开源RTOS。

点评:国内最有可能成为Top 1,优势在于丰富的组件,中立立场!赶上了时机,得到诸多芯片厂商的支持,也挺受开发者喜欢的。缺点在于本身的教程文档和freertos等之类的比还是很弱。

嵌入式操作系统RTOS推荐的学习资源:1.文档:https://www.rt-thread.org/document/site/ 2.书籍:《 嵌入式实时操作系统:RT-Thread设计与实现 》《 RT-Thread内核实现与应用开发实战指南 基于STM32 》

SylixOS

嵌入式操作系统RTOS介绍,SylixOS 是一个开源的跨平台的大型实时操作系统(RTOS),SylixOS诞生于2006年,经过十多年的持续开发,SylixOS 已成为功能最为全面的国产操作系统之一。目前已有众多产品和项目应用案例,行业涉及航空航天、军事防务、轨道交通、智能电网、工业自动化等诸多领域。SylixOS 完全符合 POSIX 规范,开源社区丰富的自由软件移植非常方便。参考网站:http://www.sylixos.com/

常见RTOS

Free RTOS

优势

  • SafeRTOS便是基于FreeRTOS而来,前者是经过安全认证的RTOS,因此对于FreeRTOS的安全性也有了信心。
  • 大量开发者使用,并保持高速增长趋势。2011、2012、2013、2014、2015、2017年(暂时没有2016年的数据)的EEtimes杂志嵌入式系统市场报告显示,FreeRTOS在RTOS内核使用榜和RTOS内核计划使用榜上都名列前茅。更多的人使用可以促进发现BUG,增强稳定性。
  • 简单。内核只有3个.c文件,全部围绕着任务调度,没有任何其它干扰,便于理解学习。而且,我根本不需要其它繁多的功能,只要任务调度就够了。
  • 文档齐全。在FreeRTOS官方网站上,可以找到所有你需要的资料。
  • 免费、开放源码。完全可以免费用于商业产品,开放源码更便于学习操作系统原理、从全局掌握FreeRTOS运行机理、以及对操作系统进行深度裁剪以适应自己的硬件。
  • 2017年底,FreeRTOS作者加入亚马逊,担任首席工程师,FreeRTOS也由亚马逊管理。同时修改了用户许可证,FreeRTOS变得更加开放和自由。背靠亚马逊,相信未来FreeRTOS会更加稳定可靠。此外,以前价格不菲的《实时内核指南》和《参考手册》也免费开放下载,这使得学习更加容易。学习的资料来源主要是FreeRTOS的官方网站和源代码。FreeRTOS的创始人RichardBarry编写了大量的移植代码和配套文档,我只不过是沿着Richard Barry铺好的路前进,所以,这没什么困难的。

任务管理

创建任务

任务函数创建

void ATaskFunction( void *pvParameters ) 
{ 
    // 可以像普通函数一样定义变量。用这个函数创建的每个任务实例都有一个属于自己的iVarialbleExample变
    // 量。但如果iVariableExample被定义为static,这一点则不成立 – 这种情况下只存在一个变量,所有的任务实
    // 例将会共享这个变量。
    int iVariableExample = 0; 
    
    /* 任务通常实现在一个死循环中。 */ 
    for( ;; ) 
    { 
    /* 完成任务功能的代码将放在这里。 */ 
    } 
    
    /* 如果任务的具体实现会跳出上面的死循环,则此任务必须在函数运行完之前删除。传入NULL参数表示删除
    的是当前任务 */ 
    vTaskDelete( NULL ); 
}

任务状态

分为运行和非运行状态,并在两者之间切换。转移过程称为切入或切出。

创建任务

portBASE_TYPE xTaskCreate(  pdTASK_CODE pvTaskCode,                //函数名,指向函数的指针
                            const signed portCHAR * const pcName,  //任务名
                            unsigned portSHORT usStackDepth,       //栈空间大小,单位:字
                            void *pvParameters,                    //传递到任务中的参数
                            unsigned portBASE_TYPE uxPriority,     //优先级,最低为0
                            xTaskHandle *pxCreatedTask );          //传出任务的句柄
返回值:成功或者溢出

创建任务后,需要执行任务调度器vTaskStartScheduler(),注意单核处理器只能运行一个任务,任务一直处于切入切出的状态。

任务优先级

调度器保证总是在所有可运行的任务中选择具有最高优先级的任务, 并使其进入运行态。如果被选中的优先级上具有不止一个任务,调度器会让这些任务轮流执行。这种行为方式在之前的例子中可以明显看出来。两个测试任务被创建在同一个优先级上,并且一直是可运行的。所以每个任务都执行一个时间片,任务在时间片起始时刻进入运行态, 在时间片结束时刻又退出运行态。

要能够选择下一个运行的任务,调度器需要在每个时间片的结束时刻运行自己本身。一个称为心跳(tick,有些地方被称为时钟滴答,本文中一律称为时钟心跳)中断的周期性中断用于此目的。时间片的长度通过心跳中断的频率进行设定,心跳中断频率由FreeRTOSConfig.h 中的编译时配置常量 configTICK_RATE_HZ进行配置。比如说,如果 configTICK_RATE_HZ 设为 100(HZ),则时间片长度为 10ms。

初始优先级运行后可以通过 vTaskPrioritySet() API 函数进行修改,优先级数目可以自定义,为常量configMAX_PRIORITIES的值。范围0~configMAX_PRIORITIES-1。如果一个任务持续运行,且优先级较高,其他任务将”饿死“。

扩充非运行态

解决低优先级饿死问题。增加阻塞态,由事件进行驱动。增加挂起状态,调用vTaskSuspend()挂起,vTaskResume()唤醒。新增就绪状态。任务状态机如下所示:

空闲任务与空闲任务钩子函数

调用调度器时,自动创建了一个空闲任务,优先级为0,总是在运行。通过钩子函数(回调)可以在空闲任务中添加应用程序。注意函数不能阻塞或者挂起。钩子函数固定格式为vApplicationIdleHook()。

任务优先级

通过vTaskPrioritySet()函数设置优先级,第一个参数为目标任务句柄(NULL为自身),第二个参数为值。查询优先级使用uxTaskPriorityGet()。获取目标任务句柄方式:

/* 声明变量用于保存任务2的句柄。 */ 
xTaskHandle xTask2Handle; 
 
int main( void ) 
{ 
    /* 任务1创建在优先级2上。任务参数没有用到,设为NULL。任务句柄也不会用到,也设为NULL */ 
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL ); 
    /* The task is created at priority 2 ______^. */ 
    
    /* 任务2创建在优先级1上 – 此优先级低于任务1。任务参数没有用到,设为NULL。但任务2的任务句柄会被
    用到,故将xTask2Handle的地址传入。 */ 
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle ); 
    /* The task handle is the last parameter _____^^^^^^^^^^^^^ */ 
    
    /* Start the scheduler so the tasks start executing. */ 
    vTaskStartScheduler(); 
    
    /* If all is well then main() will never reach here as the scheduler will 
    now be running the tasks. If main() does reach here then it is likely that 
    there was insufficient heap memory available for the idle task to be created. 
    CHAPTER 5 provides more information on memory management. */ 
    for( ;; ); 
}

删除任务

使用vTaskDelete()删除目标任务,传递参数为目标任务句柄,传入NULL删除自身。

任务调度算法–优先级抢占式调度

固定优先级抢占式调度”。所谓”固定优先级”是指每个任务都被赋予了一个优先级,这个优先级不能被内核本身改变(只能被任务修改)。”抢占式”是指当任务进入就绪态或是优先级被改变时,如果处于运行态的任务优先级更低,则该任务总是抢占当前运行的任务。

任务可以在阻塞状态等待一个事件,当事件发生时其将自动回到就绪态。时间事件发生在某个特定的时刻,比如阻塞超时。时间事件通常用于周期性或超时行为。任务或中断服务例程往队列发送消息或发送任务一种信号量,都将触发同步事件。同步事件通常用于触发同步行为,比如某个外围的数据到达了。

选择任务优先级

单调速率调度(Rate Monotonic Scheduling, RMS)是一种常用的优先级分配技术。其根据任务周期性执行的速率来分配一个唯一的优先级。 具有最高周期执行频率的任务赋予高最优先级;具有最低周期执行频率的任务赋予最低优先级。这种优先级分配方式被证明了可以最大化整个应用程序的可调度性(schedulability),但是运行时间不定以及并非所有任务都具有周期性,会使得对这种方式的全面计算变得相当复杂。

队列管理

队列特点

  • 数据存储-FIFO
  • 可被多任务存取
  • 读队列时阻塞
  • 写队列时阻塞

使用队列

  • xQueueCreate(),创建队列,参数1为队列深度,参数2为数据单元长度,返回队列句柄。
  • xQueueSendToBack(),插入队列尾部;xQueueSendToFront(),插入队列头部,参数1为队列句柄,即create函数的返回值,参数2为数据单元指针,参数3为阻塞等待时间。
  • xQueueSendToFrontFromISR()与xQueueSendT oBackFromISR(),中断中使用。
  • xQueueReceive(),接收数据单元并删除,相当于pop();xQueuePeek(),不删除元素获取数据。参数1为句柄,参数2为存入数据的指针,参数3为阻塞等待时间。
  • uxQueueMessagesWaiting(),获取有效单元数目。

使用队列传递结构体,可以实现一收多发,接收方可以不设置阻塞时间。

工作于大型数据单元

队列中存储数据指针,以避免大量数据的复制操作。注意1.指针指向的内存空间的所有权必须明确;2.指针指向的内存空间必须有效。

中断管理

延迟中断

使用二值信号量进行任务与中断的同步,中断任务较大时,将中断推迟为一个处理任务,如果比较紧急,可以设置该任务优先级最高。对信号量的操作为PV操作。

  • P操作为获取Take信号量,信号值-1等同于读取长度为1的队列,队列空任务将阻塞,函数为xSemaphoreTake();
  • V操作为给出Give信号量,信号值+1等同于插入长度为1的队列,队列满任务将阻塞,函数为xSemaphoreGiveFromISR();

在使用中,首先要注册中断,将中断处理函数与中断源绑定,设置中断处理函数,该函数给出信号量(解除延迟中断任务的阻塞)并进行上下文切换;再设置延迟中断任务,该任务的工作就是获取信号量并执行相关代码。过程描述如下:

  1. 中断产生。
  2. 中断服务例程启动,给出信号量以使延迟处理任务解除阻塞。
  3. 当中断服务例程退出时,延迟处理任务得到执行。延迟处理任务做的第一件事便是获取信号量。
  4. 延迟处理任务完成中断事件处理后, 试图再次获取信号量,如果此时信号量无效,任务将切入阻塞待等待事件发生。

计数信号量

解决二值信号量不能处理连续中断请求(会丢失)的问题。典型用法包括事件计数和资源管理。可以设置最大计数。

中断中使用队列

xQueueSendToFrontFromISR(),xQueueSendToBackFromISR()与xQueueReceiveFromISR()分别是xQueueSendToFront(),xQueueSendToBack()与xQueueReceive()的中断安全版本,专门用于中断服务例程中。可以使用队列在中断和任务之间进行传递数据和事件通信。

中断嵌套

嵌套的含义本质是低优先级中断被更高中断优先级的中断抢断。注意软件优先级与硬件中断优先级无关。

资源管理

临界资源保护问题。具体包括以下情形:

  1. 访问外设-并发
  2. 读-改-写操作
  3. 变量的非原子访问
  4. 函数重入
    如果一个函数可以安全地被多个任务调用,或是在任务与中断中均可调用,则这个函数是可重入的。 每个任务都单独维护自己的栈空间及其自身在的内存寄存器组中的值。 如果一个函数除了访问自己栈空间上分配的数据或是内核寄存器中的数据外,不会访问其它任何数据如果一个函数除了访问自己栈空间上分配的数据或是内核寄存器中的数据外,不会访问其它任何数据,则这个函数就是可重入的。

互斥

互斥用来保护临界资源的一致性问题。

临界区与挂起调度器

基本临界区

基本临界区是指宏 taskENTER_CRITICAL()与taskEXIT_CRITICAL()之间的代码区间

/* 为了保证对PORTA寄存器的访问不被中断,将访问操作放入临界区。 
进入临界区 */ 
taskENTER_CRITICAL(); 
 
/* 在taskENTER_CRITICAL() 与 taskEXIT_CRITICAL()之间不会切换到其它任务。 中断可以执行,也允许
嵌套, 但只是针对优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断 –  而且这些中断不允许访问 
FreeRTOS API 函数. */ 
PORTA |= 0x01; 
 
/* 我们已经完成了对PORTA的访问,因此可以安全地离开临界区了。 */ 
taskEXIT_CRITICAL();
挂起( 锁定) 调度器

也可以通过挂起调度器来创建临界区。挂起调度器有些时候也被称为锁定调度器。基本临界区保护一段代码区间不被其它任务或中断打断。 由挂起调度器实现的临界区只可以保护一段代码区间不被其它任务打断,因为这种方式下,中断是使能的。

如果一个临界区太长而不适合简单地关中断来实现, 可以考虑采用挂起调度器的方式。但是唤醒(resuming, or un-suspending)调度器却是一个相对较长的操作。所以评估哪种是最佳方式需要结合实际情况。

互斥量

与用于同步的二值信号量相比,互斥量必须归还,后者可以丢弃。互斥量的不合理使用会带来优先级反转问题,表现位高优先级等待低优先级释放临界资源所有权才能执行,尤其当其中穿插中等优先级任务时,低高的任务均无法执行。–解决方法,优先级继承,就是将临界区的任务优先级提高到等待所有权的所有任务中的最高优先级,这样优先执行,避免被抢占,等到释放后恢复原有优先级,这样的坏处是消耗资源大。

另一个问题是死锁,表示两个或者多个互相循环等待释放临界资源。

守护任务

守护任务提供了一种干净利落的方法来实现互斥功能, 而不用担心会发生优先级反转和死锁。

守护任务是对某个资源具有唯一所有权的任务。 只有守护任务才可以直接访问其守护的资源——其它任务要访问该资源只能间接地通过守护任务提供的服务。

内存管理

malloc和free带来的问题:

  1. 这两个函数在小型嵌入式系统中可能不可用。
  2. 这两个函数的具体实现可能会相对较大,会占用较多宝贵的代码空间。
  3. 这两个函数通常不具备线程安全特性。
  4. 这两个函数具有不确定性。每次调用时的时间开销都可能不同。
  5. 这两个函数会产生内存碎片。
  6. 这两个函数会使得链接器配置得复杂。

当内核请求内存时,其调用 pvPortMalloc()而不是直接调用 malloc();当释放内存时,调用 vPortFree()而不是直接调用 free()。pvPortMalloc()具有与 malloc()相同的函数原型;vPortFree()也具有与 free()相同的函数原型。

FreeRTOS 自带有三种 pvPortMalloc()与 vPortFree()实现范例, 这三种方式都会在本章描述。FreeRTOS 的用户可以选用其中一种,也可以采用自己的内存管理方式。

这三个范例对应三个源文件:heap_1.c,heap_2.c,heap_3.c——这三个文件都放在目录 FreeRTOS\Source\Portable\MemMang 中。 早期版本的 FreeRTOS 所采用的原始内存池和内存块分配方案已经被移除了, 因为定义内存块和内存池的大小需要较深入的努力和理解。

Free RTOS实战

参考:STM32 Free RTOS

Free RTOS进阶

参考:
FreeRTOS系列第5篇—FreeRTOS在Cortex-M3上的移植
FreeRTOS系列第6篇—FreeRTOS内核配置说明
FreeRTOS系列第7篇—Cortex-M内核使用FreeRTOS特别注意事项
FreeRTOS系列第13篇—FreeRTOS内核控制
FreeRTOS系列第14篇—FreeRTOS任务通知
FreeRTOS系列第16篇—可视化追踪调试
FreeRTOS高级篇

RT-Thread

一个国产RTOS,适用于STM32系列单片机。关于线程、时钟、IPC(邮箱与消息队列)和内存等可以查看官方文档

实战

参考STM32 RT-Thread OS实战