嵌入式系统/RTOS 实现
本节中的章节将讨论编写自己的实时操作系统所涉及的一些通用概念。读者可能能够阅读和理解这些页面中的材料,而无需事先了解操作系统设计和实现,但那些主题的背景知识肯定会有所帮助。
需要记住的一点是,一些嵌入式系统被锁起来,预计在没有重新启动的情况下运行多年。如果我们使用传统的内存管理方案来控制内存分配,最终可能会导致内存碎片,这会占用宝贵的时间来整理碎片,并且对于时间敏感的任务来说是一个主要问题。因此,本页将讨论如何在 RTOS 中实现内存管理方案,并将讨论 malloc( ) 和 free( ) 的基本实现。
有多种方法可以处理内存
- 有些系统从不进行 malloc() 或 free() - 所有内存都在编译时分配。
- 有些系统使用 malloc() 和 free() 并进行手动垃圾回收。
- 在手动垃圾回收中,有可能导致内存碎片如此严重,以至于有一天系统会锁定,因为没有一块足够大的内存来满足合理大小的 malloc() 请求,尽管许多空闲内存块的总大小远远超过了请求的 malloc(),这将是糟糕的。
- 一个微不足道的小错误可能会慢慢泄漏内存,直到系统耗尽内存并锁定,这将是糟糕的。
- 一些早期的自动垃圾回收方案在垃圾回收和/或内存整理期间会进行“停止世界”几秒钟。这样的系统可能会错过实时截止时间,这将是糟糕的。
- 一些后来的自动垃圾回收方案进行“增量”垃圾回收和内存整理。
- 许多实时系统从一组固定大小的内存块池中一次分配一块内存。这完全消除了外部碎片。
嵌入式系统有一个连接到某些硬件(LED、按钮、限位开关、电机、串行端口、电池充电器等)的微处理器。
每个硬件部分通常都与一小段软件相关联,称为“任务”。例如,“检查键盘并确定自上次检查以来是否有任何键被按下”。或者“检查主轴的当前位置,并更新 PID”。
任务通常有实时限制,例如
- 在撞击限位开关后,电机必须在 1/10 秒内关闭,以避免永久性损坏
- PID 循环必须至少每 1/100 秒更新一次,以避免振荡
- MP3 播放器必须以 44.1 kHz 的频率解码新样本 - 不快,否则听起来像松鼠 - 不慢,否则听起来像在水下。
一些嵌入式系统只有一个任务。
其他嵌入式系统只有一个微控制器连接到许多不同的硬件 - 它们需要“多任务处理”。
大多数 RTOS 都提供了一些方法来允许任务相互通信和同步,通常使用以下一种或多种方法
- 消息传递(通常是最高优先级消息优先,而不是先进先出)
- 事件标志
- 互斥机制:序列化令牌、互斥体、信号量、监视器等
“任务调度程序”(或通常称为“调度程序”)是软件中调度(选择)下一个要运行的任务的部分。
调度程序可以说是 RTOS 中最难实现的组件。调度程序维护一个表格,记录系统中每个任务的当前状态,以及每个任务的当前优先级。调度程序还需要管理定时器。
一般来说,任务可以处于 3 种状态
- 活动。在给定处理器上,一次只能有一个活动线程。
- 就绪。此任务已准备好执行,但当前未执行。
- 阻塞。此任务当前正在等待锁或临界区变为空闲。
有些系统甚至允许其他状态
- 休眠。任务自愿放弃控制一段时间。
- 低优先级。此任务仅在所有其他任务都处于阻塞或休眠状态时才运行。
调度程序有两种调用方式
- 当前任务自愿 yield() 给调度程序,直接调用调度程序,或者
- 当前任务已“运行足够长的时间”,定时器硬件会中断它,而定时器中断例程会调用调度程序。
调度程序必须保存当前任务的当前状态(将所有寄存器的内容保存到与该任务关联的内存中),它必须查看任务列表以查找就绪状态中优先级最高的任务,然后必须将控制权切换回该任务(通过从与新任务关联的内存中恢复所有寄存器值)。
调度程序应首先检查以确保它已启用。如果调度程序已禁用,则不应该抢占当前线程。这可以通过检查当前全局标志值来实现。有些函数希望禁用调度程序,因此此标志应通过某些访问器方法可访问。维护全局标志的另一种方法是简单地说任何希望禁用调度程序的函数都可以简单地禁用定时器。这样调度程序永远不会被调用,也不需要检查任何标志。
一些 RTOS 在系统调用的整个持续时间内都会禁用进程调度程序 - 为了避免错过截止时间,这需要系统调用非常快地完成。其他 RTOS 有可抢占的系统调用;它们只在极短的“临界区”内禁用进程调度程序和其他中断。
我们将在下一章锁和临界区中更多地讨论那些需要(短暂地)禁用调度程序的函数,以及它们在临界区内的操作。
另一本书操作系统设计/调度进程/抢占更详细地介绍了各种调度程序算法。
RTOS 和其他操作系统的最大区别之一是 RTOS 试图最大程度地减少中断延迟 - 对外部硬件的响应时间。这需要最大程度地减少禁用中断(包括定时器中断)的时间。一些 RTOS 供应商发布了最坏情况下的中断禁用时间。
- "实时系统的设计模式:资源模式" 由 Bruce Powel Douglass 2002 年撰写
- Microchip RTOS 应用笔记 包括“Microchip AN585:面向 PIC16/17 的实时操作系统”,其中描述了编写自己的 RTOS。
- Greg Hawley 指出,“在大多数情况下,购买 RTOS 是比...从头开始构建 RTOS 更好的选择”。
- "完美的实时操作系统" 作者:Colin Walls 2004年 [链接失效]
- "真正简单的内存管理:胖指针" 描述了一种简单的垃圾回收和内存碎片整理方案,该方案与小型实时系统兼容(它从不进行“停止世界”操作)。
- "与 8 位 MCU 操作系统调情" 作者:Dave Armour 2009年 描述了实现抢占式操作系统所需的最基本功能:TaskCreate()、TaskDestroy() 和一个非常简单的定时器驱动的任务切换器。“FLIRT” 使用了 144 字节的闪存。
- "抢占式示例代码" 注释良好,不到 200 行代码的示例演示了两个任务的抢占。非常基础,在任何方向上都有很多改进的空间。