实时操作系统 (RTOS) 及其应用

作者:高级嵌入式软件工程师 Lim Jia Zhi

投稿人:DigiKey 北美编辑

什么是 RTOS

实时操作系统 (RTOS) 是一种轻型操作系统,用于在资源和时间受限的设计中使多任务和任务集成变得易于执行,这在嵌入式系统中常见。此外,“实时”一词表示的是执行时间而非原始速度的可预测性/确定性,因此一个 RTOS 由于其确定性,通常可以证明满足硬实时性要求。

RTOS 的主要概念包括:

任务

任务(也可称为进程/线程)是独立函数,以无限循环方式运行,通常每个函数只负责一个功能。任务在自己的时间(时间隔离)和内存堆栈(空间隔离)内独立运行。通过使用硬件内存保护单元 (MPU) 可以保证任务之间的空间隔离,这种保护单元会限制可访问的内存区域,并在出现违规访问时触发故障异常。通常情况下,内部外设都是内存映射的,所以 MPU 也可以用来限制外设访问。

任务可以处于以下不同的状态:

  • 阻塞——任务正在等待一个事件(如延迟超时、数据/资源的可用性)。
  • 就绪——任务随时可以在 CPU 上运行,但由于 CPU 被另一个任务使用而没有运行。
  • 运行——任务被分配到 CPU 上运行。

调度器

RTOS 中的调度器控制哪一个任务可在 CPU 上运行,且有不同的调度算法。通常包括如下几种算法:

  • 抢占算法——如果另一个优先级更高的任务就绪,则中断任务。
  • 协作算法——只有当当前正在运行的任务主动让出时,才会进行任务切换。

抢占调度可以让优先级较高的任务中断优先级较低的任务,以满足实时约束条件,但代价是在上下文切换过程会产生更多的开销。

任务间通信 (ITC)

多任务通常需要相互分享信息或事件。最简单的共享方式是直接在 RAM 中读/写共享全局变量,但由于竞争条件会导致数据损坏风险,因此这种方式并不可取。比较好的方法是通过 setter 和 getter 函数来读取/写入文件范围内的静态变量,通过禁用中断或在 setter/getter 函数内部使用互斥 (mutex) 来防止竞争条件。更洁净的方法是使用线程安全的 RTOS 对象,如在任务之间传递信息的消息队列。

除了信息共享外,RTOS 对象还能够同步任务执行,因为任务可以被阻塞,以等待 RTOS 对象的可用性。大多数 RTOS 都有如下对象:

  • 消息队列
    • 传递数据的先入先出 (FIFO) 队列
    • 可以通过复制或引用(指针)方式来发送的数据
    • 用于在任务之间或中断与任务之间发送数据
  • 旗语
    • 可作为参考计数器,记录特定资源的可用性
    • 可以是二进制或计数旗语
    • 用于保护资源的使用或同步任务执行
  • Mutex
    • 类似于二进制旗语,一般用于保护单一资源的使用(MUTual EXclusion)
    • FreeRTOS mutex 带优先级继承机制,以避免优先级反转问题(高优先级任务结束,等待低优先级任务的情况)。
  • 信箱
    • 简单的存储位置,用来共享单个变量
    • 可视为单一元素队列
  • 活动组
    • 条件组(旗语、队列、事件标志等的可用性)
    • 任务可以被阻塞,可以等待特定的组合条件得到满足
    • 在 Zephyr 中作为轮询 API,在 FreeRTOS 中作为 QueueSets

系统节拍

RTOS 需要一个时基来测量时间,通常以系统节拍计数器变量的形式在周期性的硬件定时器中断内递增。有了系统节拍,应用程序只需使用一个硬件定时器,就可以维持多个基于时间的服务(任务执行间隔、等待超时、时间分片)。但是,较高的节拍率只能提高 RTOS 的时基分辨率,并不能使软件运行得更快。

为什么使用 RTOS

组织

应用程序总是可以用裸机的方式来编写,但随着代码的复杂程度增加,拥有某种架构将有助于管理应用程序的不同部分,从而保持其隔离。此外,通过架构化开发方式和熟悉的设计语言,新的团队成员可以更快地理解代码并投入工作。RFCOM Technologies 已经使用不同的微控制器,如Texas InstrumentsHerculesRenesasRL78RX 以及 STMicroelectronicsSTM32,在不同的 RTOS 上开发应用。类似的设计模式让我们可以在不同的微控制器,甚至在不同的 RTOS 上开发应用。

模块化

分而治之。通过在不同的任务中分离功能,我们可以很容易地增加新功能,而不会破坏其他功能;但前提是新功能不会使 CPU 和外设等共享资源过载。没有 RTOS 的开发通常会处于一个无限大的循环中,所有功能都是该循环的一部分。循环内任何一个功能的改变都会对其他功能产生影响,使得软件难以修改和维护。

通信堆栈和驱动程序

许多额外的驱动程序或堆栈,如 TCP/IP、USB、BLE 堆栈和图形库都是针对现有的 RTOS 开发或移植的。应用程序开发人员可以专注于软件的应用层,大大缩短上市时间。

小提示

静态分配

针对 RTOS 对象使用静态分配内存方式,意味着在编译时为每个 RTOS 对象在 RAM 中保留内存堆栈。例如,xTaskCreateStatic() 是 freeRTOS 中的静态分配函数。这就能保证 RTOS 对象的成功创建,从而避免因处理可能出现的分配失败而造成麻烦,并使应用程序更加具有确定性。

在决定任务所需的堆栈规格方面,可以用规格较大的(足够大)堆栈来运行任务,然后在运行时检查堆栈的使用情况,以确定其最高水位线。也有静态堆栈分析工具可供选择。

操作系统抽象层 (OSAL) 和有意义的抽象化

就像硬件抽象层 (HAL) 一样,使用 RTOS 抽象层可以让应用软件轻松迁移到其他 RTOS。RTOS 的功能很相似,所以创建 OSAL 应该不会太复杂。例如:

直接使用 freeRTOS API:

if( xSemaphoreTake( spiMutex, ( TickType_t ) 10 ) == pdTRUE ) { //dosomething }

将 RTOS API 打包添加到 OSAL 中:

if( osalSemTake( spiMutex, 10 ) == true) { //dosomething }

使用在任务间通信上的抽象层,以使代码更易于阅读并尽量减少 RTOS 对象的范围。

if( isSpiReadyWithinMs( 10 ) ) { //doSomething }

此外,如果有多个 SPI 模块可用,这种抽象还允许程序员改变下方所用的 RTOS 对象(例如,从 mutex 到计数旗语)。OSAL 和其他抽象层通过简化单元测试中的 mock 函数插入,同样有助于软件测试。

节拍间隔的选择

理想情况下,因为开销较少,所以节拍率越低越好。为了选择合适的节拍率,开发人员可以将应用程序中的模块时间约束情况罗列下来(如重复间隔、超时持续时间等)。如果有一些离群模块需要小的间隔,可以考虑为离群模块设置专门的定时器中断,而非增大 RTOS 的节拍率。如果高频功能很短(如写入寄存器,以打开/关闭 LED),就可在中断服务例程 (ISR) 内完成,否则使用延迟中断处理技术。延迟中断处理是一种将中断计算推迟到 RTOS 任务中的技术,ISR 只通过 RTOS 对象产生一个事件,然后 RTOS 任务就会被该事件解除封锁并进行计算。

抑制低功率应用的节拍

当系统较长时间处于空闲状态时,Tickless idle(无节拍闲置)会禁用节拍中断。对于嵌入式固件来说,降低功耗的一个重要方法就是让系统尽可能长时间地处于低功耗模式。通过禁用周期性节拍中断,然后设置倒计时定时器,以便在被阻塞的任务即将执行时进行中断,从而实现无节拍闲置。如果没有任务等待超时,则可以无限期地禁用节拍中断,直到发生另一个中断(例如,按钮按下)。例如,在低功耗蓝牙 (BLE) 信标的情况下,MCU 可以在广告间隔之间进入深度睡眠。如图 1 所示,信标大部分时间进入深度睡眠模式,功耗仅为几十微安。

BLE 信标的电流消耗图(点击放大) 1:BLE 信标的电流消耗(图片来源:RFCOM)

总结

RTOS 提供调度器、任务和任务间通信 RTOS 对象等功能,以及通信堆栈和驱动程序。它可以让开发人员专注于嵌入式软件的应用层,轻松快捷地设计多任务软件。但是,就像其他工具一样,必须正确使用才能发挥出更大的价值。为了创建安全、可靠和高效的嵌入式软件,开发人员应该知道什么时候使用 RTOS 功能及其配置方法。

免责声明:各个作者和/或论坛参与者在本网站发表的观点、看法和意见不代表 DigiKey 的观点、看法和意见,也不代表 DigiKey 官方政策。

关于此作者

高级嵌入式软件工程师 Lim Jia Zhi

Lim Jia Zhi 是一名嵌入式软件工程师,拥有电子电气工程专业学位。他为物联网解决方案中的设备开发软件,涵盖边缘网关、电池供电的边缘设备,也参与了安全关键型嵌入式软件的开发。他积极学习如何设计高效可靠的嵌入式系统的各种方法,如使用设计模式和工具,拥有软件开发周期。(jia.zhi.lim@rfcom-tech.com (+65) 6635 4217)

关于此出版商

DigiKey 北美编辑