# 实时任务中的关键技术

在当今的嵌入式和实时系统中，实时性是一个关键的性能指标。为了满足严格的时间约束，系统需要以确定性和低延迟的方式运行。本文将深入探讨 `SinssgyeMetaOS` 中的关键技术，特别是它的双内核模式、线程的两种运行模式、动态模式切换机制，以及如何优化应用程序以充分利用实时内核的能力。

### 双内核模式

`SinssgyeMetaOS` 采用了双内核架构，运行两个独立但协同工作的内核：

- Cobalt 实时核心：

  - 功能：作为实时调度器，专门用于调度需要严格实时响应的线程。
  - 特点：提供硬实时调度，确保任务在预定的截止时间内完成。
  - 优势：通过精确的调度策略，实现微秒级的响应时间，适用于工业控制、机器人等对实时性要求极高的领域。

- Linux 内核：

  - 功能：按照常规方式调度其线程，处理标准的非实时任务。
  - 特点：只有在没有更高优先级的实时线程需要运行时才活动。
  - 优势：提供丰富的操作系统功能和驱动支持，兼容大量的软件生态系统。

这种双内核模式的设计，使得系统能够同时满足实时任务和非实时任务的需求，实现了性能和功能的平衡。

### 线程的两种模式

1. 实时模式：
   - 定义：线程由 `Cobalt` 核心调度，享受硬实时调度的低延迟。
   - 特性：
     - 确定性：确保线程在严格的时间约束内执行。
     - 优先级：实时线程通常具有更高的优先级，优先于非实时线程执行。
     - 使用场景：适用于需要精确时间控制的任务，如传感器数据采集、运动控制等。
2. 非实时模式：
   - 定义：线程作为普通的 `Linux` 线程运行，由 `Linux` 内核调度。
   - 特性：
     - 灵活性：可以调用任何 `Linux` 服务，包括文件系统、网络、图形界面等。
     - 兼容性：与标准的 `Linux` 应用程序兼容，易于开发和维护。
     - 使用场景：适用于对实时性要求不高的任务，如日志记录、数据处理、用户界面等。

通过允许线程在两种模式下运行，系统能够充分利用实时核心的性能，同时保留 `Linux` 的丰富功能。

### 动态模式切换

线程可以根据调用的服务类型，动态地在实时模式和非实时模式之间切换。这种机制使得线程可以灵活地使用不同的服务，而不影响系统的实时性。

模式切换规则

- 从非实时模式切换到实时模式：

  - 触发条件：当非实时模式下运行的线程调用 `Cobalt` 的实时服务时。
  - 过程：
    - 线程请求实时服务，系统检测到这是一个实时调用。
    - 线程的调度从 `Linux` 内核切换到 `Cobalt` 实时核心。
    - 线程进入实时模式，享受低延迟的调度。

- 从实时模式切换到非实时模式：

  - 触发条件：当实时模式下运行的线程调用任何非实时的 `Cobalt` 服务或任何 `Linux` 服务（包括发生如页面错误这样的异常）时。
  - 过程：
    - 线程请求非实时服务，可能会导致阻塞或不可预知的延迟。
    - 为了不影响实时调度器的确定性，线程被切换回 `Linux` 内核。
    - 线程进入非实时模式，由 `Linux` 内核调度。

    graph TD;
        A[线程在非实时模式运行] --> B{线程调用 Cobalt 的实时服务?};
        B -- 是 --> C[线程请求实时服务，系统检测到这是一个实时调用];
        C --> D[线程的调度从 Linux 内核切换到 Cobalt 实时核心];
        D --> E[线程进入实时模式，享受低延迟的调度];
        B -- 否 --> F[继续在非实时模式运行];

        E --> G{线程调用非实时的 Cobalt 服务或任何 Linux 服务（包括页面错误）?};
        G -- 是 --> H[线程请求非实时服务，可能会导致阻塞或不可预知的延迟];
        H --> I[为了不影响实时调度器的确定性，线程被切换回 Linux 内核];
        I --> J[线程进入非实时模式，由 Linux 内核调度];
        G -- 否 --> K[继续在实时模式运行];

模式切换的影响

- 性能：模式切换可能带来一定的开销，但对于实时性要求严格的系统，这种开销是可接受的。
- 确定性：通过在实时模式下避免调用非实时服务，保证了实时线程的确定性。
- 灵活性：允许线程在需要时使用非实时服务，提高了系统的灵活性和功能性。

### 应用优化目标

为了充分利用 `Cobalt` 实时内核的能力，开发者需要对应用程序进行优化，确保关键的实时任务始终在实时模式下运行。

找到性能受限的循环

- 定义：应用中对性能和实时性要求最高的部分，通常是紧密循环或频繁执行的任务。
  - 方法：
    - 代码分析：查看代码，找出执行时间最长或调用最频繁的函数。
    - 性能剖析：使用工具如 `gprof、perf` 等，分析程序的性能瓶颈。

确保线程不退出实时模式

- 避免调用非实时服务：在关键的实时线程中，尽量避免调用可能导致模式切换的服务。
- 替换调用：
  - 使用实时替代品：如果需要某些功能，寻找 `Cobalt` 提供的实时替代品。例如，使用实时消息队列代替标准的 `Linux` 消息队列。
  - 重构代码：修改代码结构，避免在实时线程中进行文件 I/O、内存分配等非实时操作。
- 内存锁定（默认启用）：使用 `mlockall()` 等函数，防止内存分页，避免因页面错误导致的模式切换。

### 识别问题服务调用的方法

为了防止实时线程意外地退出实时模式，需要识别和消除导致模式切换的服务调用。

1. 静态代码分析

- 目的：在代码编译前，找出可能的问题调用。
  - 方法：
    - 代码审查：手动检查代码，寻找对非实时服务的调用。
    - 工具支持：使用静态分析工具，如 `cppcheck`、`clang-tidy`，设置规则检测不安全的调用。
  - 优点：
    - 提前发现问题：在运行前解决问题，减少调试时间。
    - 全面性：可以覆盖所有代码路径。
  - 缺点：
    - 可能遗漏动态行为：无法检测运行时才会发生的问题。

2. 运行时检查

- 目的：在程序运行时，检测哪些调用导致模式切换。
- 方法：
  - 日志记录：启用 `Cobalt` 的日志功能，记录模式切换事件。
  - 调试工具：使用调试器，设置断点或监视模式切换的函数。
- 优点：
  - 精确性：可以捕获实际发生的模式切换。
  - 动态性：能够检测到因特定输入或运行环境导致的问题。
- 缺点：
  - 需要运行程序：可能需要多次运行，覆盖不同的代码路径。
  - 可能影响性能：调试和日志可能增加系统开销。

最佳实践

- 结合使用：首先进行静态代码分析，找出显而易见的问题。然后，通过运行时检查验证修改的效果，捕获可能遗漏的动态问题。
- 持续集成：将这些检查纳入开发流程，定期分析和测试，确保实时线程的性能。