### 常见的模式切换
在实时系统开发中，**模式切换（Mode Switch）是一个需要特别关注的问题。模式切换是指线程从实时模式切换到非实时模式的过程**。这种切换通常会引入不可预测的延迟，严重影响系统的实时性能。本文将深入探讨常见导致模式切换的操作，以及如何通过优化代码和使用合适的工具来避免不必要的模式切换。

---
### 访问驱动程序

**问题描述**

在实时线程中，调用一些标准的系统调用，如 `open`、`read`、`write`、`ioctl`、`socket`、`connect`、`sendto`、`recvfrom` 等，可能会导致模式切换。这是因为这些系统调用通常涉及到与 `Linux` 内核的交互，可能会阻塞或引入延迟，迫使实时线程切换到非实时模式，从而影响系统的实时性。

**原因分析**

- 阻塞操作：标准的驱动程序可能在 I/O 操作中发生阻塞，等待硬件响应或资源可用。
- 非确定性：这些系统调用的执行时间可能不可预测，取决于系统状态、硬件性能等因素。
- 内核空间交互：涉及到与 `Linux` 内核的交互，而非实时的内核服务可能无法满足实时性的要求。

**解决方案**

为了解决上述问题，可以采用以下方法：

**使用基于 `Cobalt` 的驱动程序框架**

`RTDM（Real-Time Driver Model）` 是 `Cobalt` 提供的实时驱动程序模型，旨在简化实时驱动程序的开发。

**待完善**

---
### 读取文件

**问题描述**

在实时线程中，从文件读取数据也可能导致模式切换。这是因为文件 I/O 操作通常涉及到磁盘访问、文件系统缓存等非实时的操作，会引入不可预测的延迟，影响实时性能。

**原因分析**

- 磁盘 I/O 延迟：磁盘读取速度相对较慢，存在机械延迟或 NAND 闪存的访问延迟。
- 页面缺页异常：读取文件时，可能触发页面缺页异常，导致线程阻塞。
- 文件系统锁：文件系统操作可能涉及全局锁，阻塞其他线程的执行。

**解决方案**

使用 mmap 服务

`mmap` 可以将文件映射到进程的地址空间，使得文件内容可以像内存一样被访问。

- 优势：
    - 预加载文件内容：配合 `mlockall`，可以将文件内容预先加载并锁定到内存中，避免在实时线程中发生磁盘 I/O 操作。
    - 减少系统调用：使用内存访问替代频繁的 `read` 系统调用，降低系统调用开销。

- 实现步骤：
  - 1. 调用 `mlockall`：在程序初始化阶段(由`Cobalt`自动调用)，使用 `mlockall(MCL_CURRENT | MCL_FUTURE)` 锁定所有当前和未来的内存页面，防止内存被换出到交换空间。
  ```c
    if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
        perror("mlockall failed");
        exit(1);
    }
  ```
  - 2. 映射文件到内存：使用 mmap 将文件映射到进程的地址空间。
  ```c
    int fd = open("data_file.txt", O_RDONLY);
    if (fd < 0) {
        perror("open failed");
        exit(1);
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        perror("fstat failed");
        exit(1);
    }

    size_t file_size = sb.st_size;

    void *file_data = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (file_data == MAP_FAILED) {
        perror("mmap failed");
        exit(1);
    }

    close(fd);
  ```

  - 3. 预读文件内容：通过访问文件映射的内存区域，确保数据被实际加载到内存中。
  ```c
    volatile char temp;
    for (size_t i = 0; i < file_size; i += sysconf(_SC_PAGESIZE)) {
        temp = ((char *)file_data)[i];
    }
  ```

- 注意事项：
    - `mmap` 的调用时机：由于 `mmap` 可能导致模式切换，应在非实时的初始化阶段调用。
    - 内存占用：大型文件的映射和锁定可能占用大量内存，需要确保系统有足够的可用内存。
    - 文件更新：如果文件内容在运行期间会更新，需要考虑映射的同步问题。

**其他方法**

- 避免实时线程直接读取文件：将文件读取操作放在非实时线程或进程中，实时线程通过共享内存或消息队列获取所需数据。
- 使用 `RAM` 磁盘：将文件存储在 `RAM` 磁盘（tmpfs）中，减少磁盘 I/O 带来的延迟。

---
### 动态分配

**问题描述**

在实时线程中使用动态内存分配函数，如 `malloc`、`calloc`、`realloc`、`posix_memalign` 等，可能导致模式切换。这是因为这些函数可能涉及系统调用、内存分页或内存锁的争用，导致线程阻塞。

**原因分析**

- 内存不足：当堆空间不足时，内存分配函数需要向操作系统请求更多内存，可能触发页面缺页异常或系统调用。
- 内存碎片整理：分配器可能需要整理内存碎片，涉及复杂的算法和锁，增加执行时间的不确定性。
- 锁竞争：多线程环境下，内存分配器内部的锁竞争可能导致阻塞。

**解决方案**

启动时预分配

- 原理：在程序启动时，预先分配应用程序所需的全部内存，避免在实时线程中进行动态内存分配。
- 实施方法：
    - 静态分配：使用全局变量或静态数组，提前分配内存。
    - 内存池：实现一个内存池，启动时分配一大块内存，实时线程从内存池中分配内存。
- 示例：

```c
#define POOL_SIZE 1024 * 1024 // 1MB
char memory_pool[POOL_SIZE];
size_t pool_offset = 0;

void *my_malloc(size_t size) {
    void *ptr = NULL;
    if (pool_offset + size <= POOL_SIZE) {
        ptr = &memory_pool[pool_offset];
        pool_offset += size;
    }
    return ptr;
}
```

**使用栈上分配**

- 优势：栈上的内存分配速度快，不涉及堆内存分配器，避免了动态内存分配的开销和不确定性。
- 注意事项：
    - 栈空间限制：需要确保线程的栈空间足够大，防止栈溢出。
    - 不可分配过大数据：栈上的大数组可能导致栈溢出，应谨慎使用。
- 示例：

```c
void realtime_task() {
    char buffer[1024]; // 在栈上分配 1KB 的缓冲区
    // 处理逻辑
}
```

**自定义分配器**

- 为 STL 容器实现自定义分配器：
    - 原因：标准的 `STL` 容器（如 `std::vector`、`std::map`）在内部使用动态内存分配。
    - 解决方法：实现符合分配器接口的自定义分配器，使用预先分配的内存池。
- 示例：

```cpp
template <typename T>
class StaticAllocator {
public:
    using value_type = T;

    StaticAllocator() noexcept {}
    template <typename U>
    StaticAllocator(const StaticAllocator<U>&) noexcept {}

    T* allocate(std::size_t n) {
        // 从预先分配的内存池中分配内存
        // 实现线程安全的分配逻辑
    }

    void deallocate(T* p, std::size_t n) noexcept {
        // 不执行实际操作，或将内存返回到池中
    }
};

// 使用自定义分配器的容器
std::vector<int, StaticAllocator<int>> my_vector;
```

**避免动态内存分配**

- 代码审查：确保实时线程中没有隐含的动态内存分配，如使用了字符串操作、异常处理等。
- 编译器选项：禁用 RTTI、异常等可能导致动态内存分配的特性。
- 使用固定大小的数据结构：使用固定大小的数组、环形缓冲区等数据结构。

### I/O 多路复用与 select

**问题描述**

在实时线程中使用 `select`、`poll` 等 I/O 多路复用函数，存在不兼容情况。在 `Cobalt` 内核与 `Linux` 内核不能兼容实时文件描述符与非实时描述符的混用。

**待完善**