介绍
“实时操作系统(RTOS)是一种旨在为实时应用程序过程数据提供服务的操作系统(OS),通常没有缓冲延迟。” (wikipedia.org) RTOS中的关键因素是最小的中断延迟和最小的线程切换延迟。 RTOS的响应速度或可预测性要比给定时间段内可以执行的工作量高。 对于嵌入式设备,一般规则是,当应用程序需要执行的操作不只是一些简单的操作时,请使用RTOS。 RTOS允许以增加更多应用程序/系统功能(例如,通信堆栈,电源管理等)进行扩展的方式来构建应用程序。
实时操作系统具有以下目标:
小延迟:毕竟是实时的!
确定性:同样,它是实时的。您需要知道需要花费多长时间才能确保按时完成。
结构化软件:使用RTOS,您可以以结构化方式进行分治。直接在应用程序中添加其他组件。
可伸缩性:RTOS必须能够从简单的应用程序扩展到具有堆栈,驱动程序,文件系统等的复杂应用程序。
卸载开发:RTOS管理系统的许多方面,使开发人员可以专注于其应用程序。例如,RTOS与调度一起通常处理电源管理,中断表管理,内存管理,异常处理等。
在本研讨会中,我们将介绍通用的RTOS主题。 SimpleLink™软件开发套件(SDK)支持TI-RTOS和FreeRTOS。它还在两个RTOS之上都支持POSIX API。下面,我们将使用POSIX API作为RTOS的具体示例,但是这些概念适用于两种RTOS产品。
这是我们将学到的东西:
对RTOS术语的基本了解
对RTOS调度程序的基本了解
术语
首先让我们对一些关键定义进行标准化。 假设您对嵌入式处理有一个基本的了解,即了解什么是中断,什么是堆栈等。
标准线程类型
在本研讨会中,我们将使用术语线程作为任何执行块的通用术语。 这是每个基于RTOS的应用程序中的典型线程。
中断服务程序(ISR):由硬件中断启动的线程。 ISR运行完毕。 ISR共享同一堆栈。
任务:等待事件发生时可能阻塞的线程。 传统上,任务是长期存在的线程(与运行至完成的ISR相反)。 每个任务都有自己的堆栈,这使它可以长期存在。
空闲:最低优先级的线程仅在没有其他线程准备执行时运行。 通常,空闲只是具有最低优先级的特殊任务。
调度器
每个RTOS都有一个调度程序。 调度程序负责管理系统中线程的执行。 调度程序有两种主要的管理方法:
-
抢占式调度:这是最常见的RTOS调度器类型。 使用抢占式调度程序,正在运行的线程将继续运行,直到
完成(例如,ISR完成)
较高优先级的线程准备就绪(在这种情况下,较高优先级的线程优先于较低优先级的线程)
线程在等待资源时放弃处理器(例如,一个任务调用sleep())。
TI-RTOS和FreeRTOS都具有抢占式调度程序。 该研讨会将重点讨论抢先式调度程序。
- 时间片调度:这种调度类型确保为每个线程分配一个执行插槽。 这种类型的调度通常不利于实时应用。 如果需要,TI-RTOS内核支持使用Tasks进行时间分片调度。
其他关键术语
线程安全:如果一段代码以确保多个线程同时正确访问(读/写)的方式操作共享数据结构,则它是线程安全的。 请注意,线程安全不仅仅是RTOS问题(例如,修改同一内存的中断必须小心)。
阻塞:如果任务正在等待资源并且不消耗任何CPU,则该任务将被阻塞。 例如,如果任务调用
Task_sleep()
或Semaphore_pend()
(超时时间为非零且信号灯不可用),则该任务将被阻塞,并允许另一个线程运行。 注意:在紧密的循环中旋转寄存器不会阻止…这是轮询。裸机:不使用RTOS的应用程序的通用名称。
裸机与RTOS
让我们更仔细地研究典型的裸机应用。 这些应用程序通常可以分为三个关键部分
初始化:初始化main()中的硬件和软件组件。
超级循环状态机:用于管理应用程序的代码。 这些操作基于中断的结果(例如接收到SPI数据包或计时器到期)或轮询的结果。
ISR:由外设(例如UART),定时器或其他特定于设备的项目(例如,异常或多核通信)的中断执行的代码。 这是这些关键部件的图示。
裸机程序应用有其使用场景。 它们通常体积小,速度快,并且通过简单的应用程序相对容易理解。但是 一旦需要更复杂的逻辑,RTOS就开始发光。
让我们看一下裸机应用程序与最小RTOS应用程序(然后与更传统的RTOS应用程序)的比较。 如您所见,在裸机应用程序和最小RTOS应用程序之间,我们之前讨论的三个关键部分(init,super-loop和ISR)基本相同。 但是,使用最小的RTOS应用程序,您现在可以迈向更复杂的应用程序,该应用程序允许多个开发人员添加其内容,而无需处理潜在的易碎超级循环。
RTOS组件
这是RTOS的一些主要组件。 SimpleLink SDK为TI-RTOS和FreeRTOS提供了所有这些功能。
调度程序:抢占式调度程序,可确保最高优先级的线程正在运行。
通信机制:信号量,消息队列,队列等
临界区机制:互斥锁,门,锁等
计时服务:时钟,计时器等
电源管理:对于低功耗设备,电源管理通常是RTOS的一部分,因为它知道设备的状态。
内存管理:可变大小的堆,固定大小的堆等。
外围驱动器:UART,SPI,I2C等
协议栈:BLE,WiFi等
文件系统:FatFs等
设备管理:异常处理,引导等
SimpleLink SDK中的POSIX支持
POSIX是用于操作系统兼容性的IEEE工业API标准。 SimpleLink SDK同时具有TI-RTOS和FreeRTOS支持。 但是,在这两种RTOS的基础上,它还提供POSIX支持。 这允许应用程序独立于底层RTOS。
SimpleLink SDK中的POSIX API是基础RTOS之上的一个小垫片。 创建POSIX Pthread时,将创建基础的TI-RTOS(或FreeRTOS)任务。 同样,当创建POSIX Pthread信号量时,也会创建基础的TI-RTOS(或FreeRTOS)信号量。
POSIX支持的一个不错的功能是能够从Web上获取基于POSIX的代码并快速使它开始工作的能力。
有关SimpleLink SDK中POSIX支持的详细说明,请参阅 POSIX Overview Workshop。
NOTE: POSIX 不是 RTOS.
它是OS兼容性层,允许应用程序在操作系统之间轻松移植。
RTOS线程通信
所有的RTOS都提供标准的通信机制,例如信号量,互斥量,消息队列,链接列表等。让我们仔细看看其中的一些...
信号
信号量允许资源管理。 任务可以在sem_wait()
上阻塞,直到资源可用为止(通过sem_post()
)。 常见的用例是Hwi接收数据并发布信号量,以便任务可以对其进行处理。 这是所希望的,因为它使中断的持续时间最小化。
大多数RTOS支持二进制和计数信号量。
消息队列
消息队列对于在线程之间发送数据很有用。 消息队列可以配置为发送/接收任何大小的用户定义消息。 在这里,一个任务正在向另一个任务发送消息:
当您要将特定功能集中到单个任务中时,消息队列很有用。 所有其他线程可以将消息发送到集中式任务进行处理。 消息队列以线程安全的方式处理消息。
请注意,SimpleLink SDK的POSIX支持层中的消息队列建立在TI-RTOS中的邮箱和FreeRTOS中的队列之上。
执行
让我们来看一个抢占式调度程序。 假设在main()中创建了以下线程:
ISRX:中断服务程序
MidA:首先在main()中创建,优先级4
MidB:在main())中创建第二个,优先级为4
High:在main()中以优先级8创建最后一个
让我们看下面的执行图,并讨论正在发生的事情。
一旦内核的调度程序启动(在本例中为main()
中的BIOS_start()
),所有任务就可以运行了,但是由于它具有最高的优先级并且可以运行,因此它是第一个运行最高的任务(高)。这是对上图中提到的一些关键过渡点的描述。
ISRX断言,因此它将抢占所有任务。高现在处于抢占状态。
一旦ISRX完成,High将再次开始运行,直到在
Task_sleep()
(或某些阻止API)上被阻止为止。现在MidA可以运行了。MidA一直运行到遇到阻塞调用为止(例如
Semaphore_pend()
)。现在MidB可以运行了。MidB一直运行到High解除阻止(例如
Task_sleep()
过期)。 MidB现在被抢占。High运行直到ISRX被声明并抢占High。注意:现在有两项任务被抢占。
MidA准备就绪(例如ISRX发布了被阻止的信号量)。由于有更高优先级的线程正在运行,因此MidA无法运行。
ISRX完成,因此High再次运行,然后再次阻塞,因此MidB再次运行直到阻塞。由于没有更高优先级的任务在运行,因此MidA现在可以运行。注意:自MidB准备就绪以来,自MidB运行以来,MidA必须等到MidB完成之后。
MidA块,现在没有线程在运行或准备运行,所以Idle最终运行到...
MidB解锁并运行。
以上所有上下文切换都是由RTOS中的调度程序管理的。