RTOS 基础知识
实时操作系统 (RTOS) 是一种体积小巧、具备确定性的计算机操作系统。RTOS 通常用于需要在严格时限内响应外部事件的嵌入式系统中,例如医疗设备和汽车电子控制单元 (ECU)。通常,这类嵌入式系统中可能只有一两个功能需要确定性时序保障;然而,即使嵌入式系统本身不要求严格的实时响应能力,使用 RTOS 也能带来诸多优势。
RTOS 通常比通用操作系统 (如 Windows, Linux, macOS) 体积更小、系统负载更轻。因此,RTOS 非常适合那些内存资源、计算能力或功耗受限的设备。
多任务处理
内核是操作系统的核心组件。像 Linux 这样的通用操作系统所采用的内核,允许多个用户“看似”同时地访问计算机的处理器。这些用户可以各自执行多个程序,这些程序看起来像是在并发运行。
每个正在执行的程序,在操作系统控制下,由一个或多个线程来实现。如果一个操作系统能够以这种方式执行多个线程,则称为多任务处理。
像 FreeRTOS 这样小巧的实时操作系统 (RTOS) 通常将线程称为任务 (Task)。这是因为它们不支持虚拟内存机制,因此进程 (Process) 和线程 (Thread) 之间在资源隔离上没有本质区别。
使用多任务操作系统可以简化那些原本非常复杂的软件应用程序的设计:
- 操作系统的多任务调度能力及其任务间通信 (Inter-Task Communication, ITC) 功能,允许将一个复杂的应用程序划分为一组更小、更易管理的任务。
- 这种划分有助于简化软件测试,确保开发团队职责清晰(不同团队或个人负责不同任务),并促进代码的复用性。
- 复杂的时序控制和执行顺序协调的细节将由 RTOS 内核来负责处理,从而减轻了应用程序代码在这方面的负担。
多任务处理与并发
常规单核处理器一次只能执行一个任务,但多任务操作系统可以快速切换任务, 使所有任务看起来像是同时在执行。下图展示了 三个任务相对于时间的执行模式。任务名称用不同颜色标示,并写在左侧。时间从左向右移动, 彩色线条显示在特定时间执行的任务。上方展示了所感知的并发执行模式, 下方展示了实际的多任务执行模式。
调度
操作系统内核中包含一个称为调度器的部分,其核心职责是在任何给定时刻决定接下来由哪个任务占用处理器执行。在执行任务的过程中,内核拥有对其多次暂停和恢复的能力。当任务 B 取代任务 A 开始执行(即任务 A 被暂停、任务 B 被恢复)时,我们称任务 A 换出,任务 B 换入。
调度器依据特定的调度策略(即决定“何时选择哪个任务执行”的算法)来做出决策。在非实时的多用户系统(如通用计算机)中,调度策略通常旨在确保处理器时间分配的公平性。实时嵌入式系统则采用不同的策略,详见下文说明。
任务换出的发生,唯一原因就是调度算法决定执行另一个不同的任务。 这种切换可以在以下两种情形下触发:
- 当前任务不知情/被强制暂停: 例如调度器响应外部事件(如硬件中断)或定时器到期(如任务的时间片用完)。
- 当前任务主动退出运行状态: 任务自身调用特定的内核 API 函数,使其让出(Yield)、休眠/延迟(Sleep/Delay) 或阻塞(Block)。
主动退出运行状态的行为对后续调度产生关键影响:
- 让出: 让出 CPU 后,调度器在后续决策中可能再次立即选择该任务执行。
- 休眠/延迟: 在指定的延迟时间到期之前,该任务不会被调度器选择执行。
- 阻塞: 在该任务等待的特定事件(例如 UART 数据到达)发生或其设定的超时期满之前,该任务不会被调度器选择执行。
操作系统内核的核心管理职能包括精确跟踪所有任务的状态以及管理这些状态之间的转换,其根本目标是确保调度器在每一时刻都能根据配置的调度策略和每个任务当前的实时状态,选出最适合的任务赋予处理器执行权。
参考上图中的数字标记:
- 在标记 (1) 处,任务 1 正在执行。
- 在标记 (2) 处,内核将任务 1 换出……
- ……并在标记 (3) 处将任务 2 换入。
- 在任务 2 执行期间,在标记 (4) 处,任务 2 锁定了处理器外设以进行独占访问(图中不可见)。
- 在标记 (5) 处,内核将任务 2 换出……
- ……并在标记 (6) 处将任务 3 换入。
- 任务 3 试图访问之前被任务 2 锁定的处理器外设,发现其被锁定,在标记 (7) 处阻塞以等待外设解锁。
- 在标记 (8) 处,内核将任务 1 换入。
- 如此往复。
- 在标记 (9) 处,任务 2 再次执行,完成对外设的操作并解锁。
- 在标记 (10) 处,任务 3 再次执行,发现外设可用,继续执行直到再次被换出。
实时调度
实时操作系统 (RTOS) 实现多任务处理所依据的核心原理与通用(非实时)系统相同,但两者的设计目标存在根本性差异。这种差异尤其体现在调度策略上。实时嵌入式系统的核心目标是及时响应现实世界的事件,这些事件通常具有截止时间,系统必须在截止时间前完成响应。因此,RTOS 的调度策略必须被设计为能保证满足这些截止时间的要求。
在小型 RTOS(例如 FreeRTOS)中,实现该目标的关键方法是为每个任务分配优先级。此时,RTOS 采用的调度策略始终确保当前能够执行(即未进入休眠或阻塞状态)的、优先级最高的任务获得处理器时间。当存在多个具备同等最高优先级且处于可执行状态的任务时,调度策略也可以在它们之间进行“公平”的处理时间分配。
需要强调的是,这种基础形式的实时调度策略存在其固有的局限性——它无法超越物理硬件的时间限制。因此,应用程序开发者必须确保所设定的时序约束(即任务响应的截止时间等要求)能够基于其所设定的任务优先级安排被实际达成。
示例
以下为最基本的示例,涉及一个带有键盘、LCD 和控制算法的实时系统。
用户每次按键后, 必须在合理的时间内获得视觉反馈,如果用户在此期间无法看到按键已被接受, 则该软件产品的使用感会很差(软实时)。如果最长可接受的响应时间是 100 毫秒,则任何介于 0 和 100 毫秒之间的响应都是 可接受的。此功能可作为自主任务实现,结构如下:
void vKeyHandlerTask( void *pvParameters )
{// Key handling is a continuous process and as such the task// is implemented using an infinite loop (as most real-time// tasks are).for( ;; ){[Block to wait for a key press event][Process the key press]}
}
现在假设实时系统还在执行依赖于数字滤波输入的控制功能。输入必须 每 2 毫秒采样一次、滤波一次并执行控制周期。为了正确操作滤波器,采样时间 必须精确到 0.5 毫秒。此功能可作为自主任务实现,结构如下:
void vControlTask( void *pvParameters )
{for( ;; ){[Delay waiting for 2ms since the start of the previous cycle][Sample the input][Filter the sampled input][Perform control algorithm][Output result]}
}
软件工程师必须为控制任务分配最高优先级,因为:
- 控制任务的截止时间比按键处理任务更严格。
- 错过截止时间对控制任务的后果比对按键处理任务更严重。
下图演示了实时操作系统如何调度这些任务。RTOS 会自行创建一个任务,即空闲任务, 仅当没有其他任务能够执行时,该任务才会执行。RTOS 空闲任务总是处于 可以执行的状态。
- 起初,两个任务都不能运行:vControlTask 等待合适的时间来开始新的控制周期, 而 vKeyHandlerTask 则在等待按键操作。处理器时间分配给了 RTOS 空闲任务。
- 在时间 t1 处,发生按键事件。vKeyHandlerTask 可以执行,其优先级高于 RTOS 空闲任务, 因此获得了处理器时间。
- 在时间 t2 处,vKeyHandlerTask 已完成按键处理并更新 LCD。在按下另一个键之前该任务无法继续执行, 因此将自己挂起,RTOS 空闲任务恢复执行。
- 在时间 t3 处,定时器事件指示执行下一个控制循环的时间到了。vControlTask 现在可以执行, 因为优先级最高的任务被立刻分配了处理器时间。
- 在时间 t3 和 t4 之间,vControlTask 仍在执行时,发生了按键事件。vKeyHandlerTask 可以执行, 但由于其优先级低于 vControlTask,因此未获得任何处理器时间。
- 在 t4 处, vControlTask 完成了控制周期的处理,并且直到下一次定时事件的发生前不能重新开始运行, 进入阻塞态。vKeyHandlerTask 现在成为可以运行的最高优先级的任务, 因此获得处理器时间以处理先前的按键事件。
- 在 t5 处,按键事件处理完成,并且 vKeyHandlerTask 进入阻塞态等待下一次按键事件。再一次, 两个任务都未进入就绪态,RTOS 空闲任务获得处理器时间。
- 在 t5 与 t6 之间,定时事件发生并处理,没有进一步的按键事件发生。
- 在 t6 处发生按键事件,但在 vKeyHandlerTask 完成按键处理之前,发生了定时事件。 此时两个任务都可以执行。由于 vControlTask 具有更高的优先级, 因此 vKeyHandlerTask 在完成按键操作之前被挂起,vControlTask 获得处理器时间。
- 在 t8 处,vControlTask 完成控制周期的处理,然后进入阻塞态等待下一次事件。vKeyHandlerTask 再次 成为运行的最高优先级任务,因此获得处理器时间,以便完成按键处理 。