# 未预期的模式切换

在实时系统开发中，模式切换（Mode Switch）是一个关键的概念，它涉及到线程从实时模式切换到非实时模式的过程。这种切换可能会引入不可预测的延迟，影响系统的实时性能。为了确保应用程序的高效运行，开发者需要深入理解未预期的模式切换的原因、类型以及如何避免。

### 什么是未预期的模式切换？

未预期的模式切换是指线程在运行过程中，因某些操作或事件的发生，导致从实时模式切换到非实时模式的现象。这种切换通常是不希望发生的，因为它会打破实时系统的时间确定性，可能导致任务错过截止时间。

未预期的模式切换主要分为两种类型：

- 可重现的实时模式切换到非实时模式
- 偶然的实时模式切换到非实时模式

#### 可重现的实时模式切换到非实时模式

原因

这类模式切换通常由于以下原因导致：

- 用了 `Linux` 系统调用：当实时线程调用了非实时的 `Linux` 系统调用，如 `open`、`read`、`write`、`ioctl` 等，可能会触发模式切换。
- 异常情况：例如，不支持的非对齐内存访问，或者由于浮点计算错误引起的 FPU 异常。

特点

- 可重现性：每次执行相同的操作都会触发模式切换，行为具有一致性。
- 易于检测：由于其可重现性，可以通过测试和调试手段较容易地发现。

解决方法

- 避免在实时线程中调用非实时系统调用：将非实时操作放在非实时线程或进程中。
- 使用标准的 Linux 系统调用, 能够明确如 `open`、`read`、`write`、`ioctl`操作的是支持实时的设备
- 处理异常情况：确保代码中没有非法的内存访问，正确处理浮点运算，避免触发异常。

#### 偶然的实时模式切换到非实时模式

原因

这类模式切换发生的原因更为隐蔽，可能包括：

- 动态内存分配：例如，`malloc`、`calloc`、`realloc` 等函数，大部分时间不会触发系统调用，但在需要向操作系统申请更多内存时，可能会导致模式切换。
- 缓存失效：某些函数在缓存命中时不会导致模式切换，但在缓存失效时可能触发系统调用。
- 资源竞争：系统资源紧张时，某些操作可能需要等待资源，导致模式切换。

特点

- 难以预测：发生的时间和条件不确定，可能在系统运行一段时间后才出现。
- 难以检测：由于其偶发性，传统的测试方法可能无法捕捉到。

解决方法

- 预分配资源：在程序初始化时，预先分配所需的内存和资源，避免运行时的动态分配。
- 使用内存池：实现自定义的内存分配器，避免使用标准的 `malloc` 等函数。
- 监控工具：使用实时系统提供的模式切换检测工具，如 `PTHREAD_WARNSW`，在发生模式切换时得到通知。

### 优先级反转的特殊情况

什么是优先级反转？

优先级反转是指高优先级线程被低优先级线程阻塞的现象，导致高优先级线程无法按时执行。虽然优先级反转并非真正的模式切换，但其带来的影响与模式切换类似，都会引入额外的延迟，破坏系统的实时性。

发生原因

当多个线程共享数据并使用互斥锁等同步机制保护共享数据时，可能发生优先级反转：

- 情景描述：
  - 线程 1（低优先级）持有互斥锁并进入关键区。
  - 线程 1 在关键区内发生模式切换或被 Linux 内核抢占。
  - 线程 2（高优先级）尝试获取互斥锁，但由于线程 1 持有锁而被阻塞。
- 结果：高优先级的线程 2 被低优先级的线程 1 阻塞，直到线程 1 释放互斥锁。这期间，线程 2 的执行被延迟，影响了实时性。

解决方法

- 优先级继承协议(`Cobalt`已经默认启用)：使用互斥锁的优先级继承属性，使得低优先级线程在持有互斥锁时提升到高优先级线程的优先级。

    pthread_mutexattr_t mutex_attr;
    pthread_mutexattr_init(&mutex_attr);
    pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_INHERIT);
    pthread_mutex_init(&mutex, &mutex_attr);

### 如何检测和避免未预期的模式切换

使用模式切换检测工具

- `PTHREAD_WARNSW`：启用线程的模式切换警告，当发生模式切换时，线程会收到 SIGXCPU 信号。

    #include <pthread.h>
    #ifdef __XENO__
    pthread_set_mode_np(0, PTHREAD_WARNSW);
    #endif

- 信号处理：编写信号处理函数，记录模式切换发生的位置和原因。

    void sigxcpu_handler(int sig) {
        printf("Mode switch detected in thread %ld\n", pthread_self());
    }

代码审查和测试

- 代码审查：仔细检查实时线程中的代码，避免使用可能导致模式切换的函数。
- 压力测试：在高负载和极端条件下测试应用程序，捕捉偶然发生的模式切换。

设计良好的系统架构

- 实时和非实时任务分离：将非实时操作放在独立的非实时线程或进程中，与实时线程通过安全的通信机制交互。
- 预处理数据：在非实时线程中处理数据，实时线程只负责时间敏感的操作。