45、实时系统开发:QF 应用集成与 RTC 内核解析

实时系统开发:QF 应用集成与 RTC 内核解析

1. QF 应用系统集成

QF 应用与嵌入式实时软件的集成是一个重要方面,特别是与设备驱动程序和 I/O 系统的集成。通常,这种集成应基于事件驱动范式。QF 允许从任何软件发布或发布事件,不一定是从活动对象。如果你编写自己的设备驱动程序或可以访问设备驱动程序源代码,你可以直接使用 QF 工具来创建、发布或发布事件。

在访问设备时,应将任何设备视为共享资源,并将其访问限制在一个活动对象中。这样做是最安全的,因为它避免了重入问题。只要严格限制一个活动对象的访问,活动对象内的 RTC 执行就允许使用非重入代码。即使代码受到某种互斥机制的保护(商业设备驱动程序通常如此),将访问限制在一个线程中也可以避免优先级反转和活动对象相互阻塞导致的不确定性。

需要注意的是,从一个活动对象访问设备并不意味着每个设备都需要一个单独的活动对象。通常,可以使用一个活动对象来封装多个设备。

2. QF 应用开发总结

QF 实时框架的内部实现使用了许多底层机制,如临界区、互斥锁和消息队列。但是,在执行活动对象的基础设施到位后,基于 QF 的应用程序开发可以更加轻松和快速。更高的生产力来自封装的活动对象,这些对象可以在不使用传统多任务程序中麻烦的底层机制的情况下进行编程。然而,整个应用程序仍然可以充分利用多线程。

开发 QP 应用程序涉及定义信号和事件类、详细设计活动对象的状态机以及在具体平台上部署应用程序。QP 软件组件的高可移植性使你能够在与最终目标不同的平台上开发大部分代码。

使用活动对象进行编程需要程序员遵守一定的规则,因为禁止共享内存和资源。许多软件开发人员的经验表明,不违反此规则也可以编写高效的应用程序。此外,这种规则实际上有助于创建更安全、更健壮、更易于测试和维护的软件产品。

事件队列和事件池可以看作是事件驱动编程范式固有的成本。这些数据结构与执行栈一样,用一些内存换取编程的便利性。你应该在应用程序开发开始时使用过大的队列、池和栈,仅在产品开发接近尾声时缩小它们的大小。可以结合基本的经验和分析技术来最小化事件队列和事件池的大小。

在将 QP 应用程序与设备驱动程序和其他软件组件集成时,应避免在活动对象之间共享任何非重入或受互斥锁保护的代码。最佳策略是将对此类代码的访问定位在一个专用的活动对象中。

基于活动对象的应用程序比传统的阻塞任务更能适应变化,因为活动对象从不阻塞,因此比阻塞任务更具响应性。此外,事件驱动系统的更高适应性源于信号事件和对象状态的关注点分离。具体来说,活动对象使用状态机而不是阻塞来表示操作模式,使用事件传递而不是解锁来发出有趣事件的信号。

以下是 QF 应用开发的一些关键要点总结表格:
|要点|详情|
| ---- | ---- |
|集成基础|基于事件驱动范式,可从任何软件发布事件|
|设备访问|限制为一个活动对象,可封装多个设备|
|开发步骤|定义信号和事件类、设计状态机、平台部署|
|资源使用|避免共享非重入或互斥锁保护代码|
|适应性|比传统阻塞任务更适应变化|

3. 选择抢占式内核的原因

在深入了解 QK 之前,需要明确的是,抢占式多任务在应用程序的设计和调试中带来了全新的复杂性。分析和调试一个任务不能在每条指令上相互抢占,而只能在每个 RTC 步骤后相互让步的程序要容易得多。允许任务抢占可能会导致各种棘手的问题,这些问题最终都源于任务之间的资源共享。你必须格外小心,因为资源共享可能比你想象的更隐蔽。例如,你可能在不知不觉中使用了标准库或其他来源的一些非重入代码。此外,抢占式多任务在栈使用(RAM)以及调度和上下文切换的 CPU 周期方面总是比非抢占式调度(如“普通”协作内核)成本更高。

以下是选择抢占式内核的一些错误原因:
- 在活动对象计算模型中,不需要抢占式内核来划分问题。活动对象已经将原始问题进行了划分,无论底层内核或 RTOS 如何。
- 不需要抢占式内核来实现高效阻塞,因为事件驱动系统通常不会阻塞。
- 由于事件驱动系统不进行轮询或阻塞,RTC 步骤往往自然很短。因此,使用简单的非抢占式内核就有可能实现足够的任务级响应。通常,可以通过使用“提醒”状态模式将长的 RTC 步骤分解成足够短的片段来轻松提高普通内核的任务级响应。
- 不需要抢占式内核来利用 MCU 的低功耗睡眠模式。普通协作内核允许你安全地使用低功耗睡眠模式。

然而,抢占式内核对于特定类型的问题来说是非常强大且不可或缺的工具。抢占式内核几乎可以独立于低优先级任务执行高优先级任务。当高优先级任务需要运行时,它会立即抢占当前正在运行的任何低优先级任务,因此低优先级处理对于所有高优先级任务来说实际上是透明的。因此,基于优先级的抢占式内核在时间域上解耦了高优先级任务和低优先级任务。这种独特的能力在控制型应用中至关重要。

例如,在 GPS 接收器应用中,GPS 信号跟踪的硬实时控制循环必须与缓慢的、浮点密集型的数值计算、图形 LCD 访问和其他 I/O 并行执行。这种类型的应用不适合使用非抢占式内核,因为在非抢占式内核中,任务级响应是整个系统中最长的 RTC 步骤。要识别并将所有低优先级 RTC 步骤分解成足够短的片段以满足控制循环的严格时序约束是不切实际的。使用非抢占式内核还会导致设计脆弱,因为低优先级任务的任何更改都可能影响高优先级控制任务的时序。相比之下,使用抢占式内核可以确保高优先级处理几乎不受低优先级任务变化的影响。

内核类型的选择实际上是时间域耦合和资源共享之间的权衡。非抢占式内核允许任务之间共享资源,但在时间域上耦合了任务。抢占式内核在时间域上解耦了任务,但对任务之间的资源共享很敏感。在抢占式内核下,任何允许安全共享资源的机制(如互斥锁)都会在时间域上引入一些任务之间的耦合。

4. RTC 内核介绍

所有事件驱动系统都以离散的 RTC 步骤处理事件。具有讽刺意味的是,大多数传统的 RTOS 迫使程序员使用连续的无限循环结构的任务来建模这些简单的一次性事件反应。这种严重的不匹配是由所有传统阻塞内核背后的顺序编程范式造成的。

虽然事件驱动的活动对象计算模型可以与传统的阻塞内核一起工作,但它实际上并没有有效地利用这种内核的能力。一个结构为无限事件循环的活动对象任务实际上只在循环中的一个地方且仅在一种条件下(即事件队列为空时)阻塞。因此,使用能够在任务执行路径的任意位置阻塞的复杂机制来在一个预先已知的点进行阻塞显然是过度设计。

事件驱动范式需要一种不同的、更简单的真正事件驱动的运行至完成(RTC)内核。这种类型的内核完全打破了任务的循环结构,而是使用一次性的、离散的运行至完成函数来构建任务,非常类似于 ISR。实际上,RTC 内核将中断视为具有“超高”优先级的任务,不同之处在于中断由中断控制器在硬件中进行优先级排序,而任务由 RTC 内核在软件中进行优先级排序。

以下是 RTC 内核的一些特点:
- 单栈的抢占式多任务 :传统实时内核为每个运行的任务维护相对复杂的执行上下文,包括单独的栈空间。跟踪这些上下文的细节并在它们之间进行切换需要大量的记录和复杂的机制来实现上下文切换的魔法。相比之下,RTC 内核可以非常简单,因为它不需要管理多个栈和所有相关的记录。通过要求所有任务运行至完成并强制执行固定优先级调度,RTC 内核可以使用机器的自然栈协议来管理所有上下文信息。当一个低优先级任务向高优先级任务发布事件时,RTC 内核使用常规的 C 函数调用在被抢占任务的上下文之上构建高优先级任务的上下文。当一个中断抢占一个任务并且中断向高优先级任务发布事件时,RTC 内核使用已经建立的中断栈帧,同样使用常规的 C 函数调用在其上构建高优先级任务的上下文。这种简单的上下文管理形式是足够的,因为每个任务和每个 ISR 一样,都会运行至完成。由于抢占任务也必须运行至完成,因此在抢占任务(以及可能抢占它的任何更高优先级任务)完成并返回之前,低优先级上下文将不会被需要。此时,被抢占的任务自然会位于栈顶,准备恢复执行。

许多优先中断控制器(如 PC 内的 8259A、Atmel 的基于 AT91 的 ARM MCU 中的 AIC、ARM Cortex - M3 中的 NVIC 等)在硬件中实现了与 RTC 内核在软件中为任务实现的相同的异步调度策略。具体来说,任何优先中断控制器只允许更高优先级的中断抢占当前正在服务的中断。所有中断必须运行至完成且不能“阻塞”。所有中断都嵌套在同一个栈上。这种相似性有助于理解 RTC 内核的操作,因为它基于硬件设计中广泛使用和记录的相同原理。

  • 非阻塞内核 :简单的栈管理策略的一个明显后果,也是 RTC 内核最严重的限制,是 RTC 任务不能阻塞。内核不能将高优先级任务的上下文留在栈上,同时恢复低优先级任务。除非高优先级任务完成,否则低优先级任务的上下文在栈顶将不可访问。当然,不能阻塞使得 RTC 内核不适合用于传统的顺序编程范式,因为该范式主要是在任务代码的各个点进行阻塞和等待事件。但对于事件驱动的活动对象来说,在 RTC 步骤中间不能阻塞并不是一个真正的问题,因为它们无论如何都不需要阻塞。换句话说,活动对象计算模型可以从 RTC 内核的简单性和出色性能中受益,同时不受这种内核限制的影响。

  • 同步和异步抢占 :作为一个完全抢占式的多任务内核,RTC 内核必须确保 CPU 始终执行准备运行的最高优先级任务。幸运的是,只有两种情况会导致更高优先级任务准备好运行:

    • 同步抢占 :当低优先级任务向高优先级任务发布事件时,内核必须立即暂停低优先级任务的执行并启动高优先级任务。这种类型的抢占称为同步抢占,因为它与向任务的事件队列发布事件同步发生。
    • 异步抢占 :当一个中断向比被中断任务更高优先级的任务发布事件时,在 ISR 完成后,内核必须启动更高优先级任务的执行,而不是恢复低优先级任务。这种类型的抢占称为异步抢占,因为它可以在中断未明确锁定的任何时间异步发生。

以下是同步抢占的流程 mermaid 流程图:

graph LR
    A[低优先级任务执行] --> B[低优先级任务发布事件到高优先级任务]
    B --> C[调度器检测高优先级任务就绪]
    C --> D[调度器调用高优先级任务函数]
    D --> E[高优先级任务运行并发布事件到低优先级任务]
    E --> F[调度器检查无更高优先级任务,返回高优先级任务]
    F --> G[高优先级任务运行完成]
    G --> H[高优先级任务返回调度器]
    H --> I[调度器检查无更高优先级任务,返回低优先级任务]
    I --> J[低优先级任务继续执行]

异步抢占的流程如下:
1. 低优先级任务正在执行且中断未锁定。
2. 异步事件中断处理器,中断立即抢占任何正在执行的任务。
3. 中断服务例程(ISR)执行 RTC 内核特定的入口,将被中断任务的优先级保存到基于栈的变量中,并将 RTC 内核的当前优先级提升到 ISR 级别(高于任何任务)。
4. ISR 继续执行一些工作,并向高优先级任务发布或发布事件。发布事件会触发 RTC 调度器,由于没有任务的优先级高于当前优先级,调度器立即返回。
5. ISR 继续执行,最后执行 RTC 内核特定的出口。
6. RTC 内核特定的 ISR 出口向中断控制器发送中断结束(EOI)指令,将保存的被中断任务的优先级恢复到当前优先级,并调用 RTC 调度器。
7. RTC 调度器检测到高优先级任务准备好运行,启用中断并调用高优先级任务。注意,RTC 调度器不会返回。
8. 高优先级任务运行至完成,除非它也被中断。
9. 完成后,高优先级任务自然返回调度器,此时调度器在任务优先级级别执行,因为在步骤 5 中向中断控制器发出的 EOI 指令降低了硬件优先级。注意,即使中断返回指令尚未执行,系统优先级也处于任务级别。
10. 原始中断执行中断返回(IRET)指令。IRET 恢复低优先级任务的上下文,该任务一直被异步抢占。注意,中断返回与步骤 2 中的中断抢占相匹配。
11. 最后,低优先级任务继续执行并最终运行至完成。

需要指出的是,从概念上讲,中断处理在 RTC 内核特定的中断出口处结束,即使中断栈帧仍然保留在栈上且 IRET 指令尚未执行。中断结束是因为向中断控制器发出了 EOI 指令。在 EOI 指令之前,中断控制器只允许优先级高于当前中断的中断。

5. 同步与异步抢占示例分析

为了更好地理解同步和异步抢占,下面结合具体示例进行深入分析。

5.1 同步抢占示例

假设存在一个低优先级任务 TaskLow 和一个高优先级任务 TaskHigh TaskLow 负责一些常规的数据处理工作,而 TaskHigh 则负责处理紧急的系统事件。

graph LR
    A[TaskLow 执行常规数据处理] --> B[TaskLow 检测到紧急情况,发布事件到 TaskHigh]
    B --> C[RTC 调度器检测到 TaskHigh 就绪]
    C --> D[调度器调用 TaskHigh 函数]
    D --> E[TaskHigh 处理紧急事件并发布事件到 TaskLow]
    E --> F[调度器检查无更高优先级任务,返回 TaskHigh]
    F --> G[TaskHigh 完成紧急事件处理]
    G --> H[TaskHigh 返回调度器]
    H --> I[调度器检查无更高优先级任务,返回 TaskLow]
    I --> J[TaskLow 继续常规数据处理]

在这个示例中, TaskLow 在执行过程中检测到紧急情况,向 TaskHigh 发布事件。调度器立即响应,暂停 TaskLow 的执行,启动 TaskHigh TaskHigh 处理完紧急事件后,调度器再恢复 TaskLow 的执行。整个过程体现了同步抢占的特点,即与事件发布同步进行任务切换。

5.2 异步抢占示例

假设系统中有一个低优先级任务 TaskA 正在运行,同时系统存在一个中断源,当该中断发生时,会触发对高优先级任务 TaskB 的事件发布。

graph LR
    A[TaskA 执行正常工作,中断未锁定] --> B[异步事件触发中断]
    B --> C[中断服务例程(ISR)执行入口操作]
    C --> D[ISR 向 TaskB 发布事件]
    D --> E[ISR 执行出口操作,调用调度器]
    E --> F[调度器检测到 TaskB 就绪,启用中断并调用 TaskB]
    F --> G[TaskB 执行任务]
    G --> H[TaskB 完成任务,返回调度器]
    H --> I[调度器恢复 TaskA 上下文]
    I --> J[TaskA 继续执行]

在这个示例中, TaskA 正在正常执行,一个异步事件触发了中断。中断服务例程在执行过程中向 TaskB 发布事件。中断结束后,调度器检测到 TaskB 就绪,启动 TaskB 的执行。 TaskB 完成后,调度器再恢复 TaskA 的执行。这就是异步抢占的过程,它可以在中断未锁定的任意时刻发生。

6. RTC 内核的优势与局限性总结

RTC 内核在实时系统开发中具有独特的优势,但也存在一定的局限性。下面通过表格进行总结:
|类别|详情|
| ---- | ---- |
|优势| - 简单高效:不需要管理多个栈和复杂的上下文记录,使用机器自然栈协议管理上下文,降低了系统开销。
- 与事件驱动模型匹配:适合事件驱动的活动对象计算模型,能有效处理离散的事件反应。
- 优先级管理清晰:在时间域上解耦高优先级和低优先级任务,确保高优先级任务能及时执行。|
|局限性| - 任务不能阻塞:RTC 任务无法在执行过程中阻塞,不适合传统的顺序编程范式。
- 资源共享敏感:对任务之间的资源共享较为敏感,需要严格控制资源访问。|

7. 实际应用中的考虑因素

在实际应用中,选择 RTC 内核需要综合考虑多个因素。

7.1 应用场景适配

不同的应用场景对内核的要求不同。对于控制型应用,如工业自动化、航空航天等领域,需要高优先级任务能够及时响应,抢占式的 RTC 内核是一个不错的选择。而对于一些对实时性要求不高、任务相对简单的应用,非抢占式内核可能更合适。

7.2 资源管理

由于 RTC 内核不适合任务之间的资源共享,在设计应用时需要特别注意资源的分配和管理。可以采用将资源访问集中在特定活动对象的方式,避免资源竞争和优先级反转问题。

7.3 性能优化

在开发过程中,可以通过合理设置任务优先级、优化事件队列和事件池的大小等方式来提高系统性能。例如,在开发初期使用较大的队列和池,后期根据实际情况进行调整。

8. 总结与展望

通过对 QF 应用集成、QP 应用开发以及 RTC 内核的深入分析,我们了解到实时系统开发需要综合考虑多个方面的因素。QF 应用的系统集成强调了事件驱动范式和设备访问的管理,QP 应用开发注重代码的可移植性和资源的合理使用。而 RTC 内核作为一种独特的内核类型,为事件驱动系统提供了简单高效的解决方案。

在未来的实时系统开发中,随着技术的不断发展,RTC 内核有望在更多领域得到应用。同时,如何进一步优化 RTC 内核的性能、提高其对复杂应用场景的适应性,将是研究和实践的重点方向。开发者需要不断探索和创新,以满足日益增长的实时系统需求。

总之,实时系统开发是一个复杂而又充满挑战的领域,选择合适的开发方法和内核类型对于系统的性能和可靠性至关重要。希望本文能够为开发者在实时系统开发中提供有益的参考和指导。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值