4. 实时性基础:任务调度模型与时间确定性分析

各位同学,咱们今天聊点硬核的——实时性。说白了,就是你的系统能不能在规定时间内把活干完。我见过太多项目,功能调通了,一上压力就崩,原因就是实时性没搞明白。

实时性不是玄学,它有一套完整的分析方法和设计模型。这一章,咱们就把地基打牢。

4.1 任务调度模型:两种主流架构

嵌入式系统里,任务调度就两种主流玩法:前后台系统和实时操作系统(RTOS)。别觉得前后台就落后,选哪个得看场景。

4.1.1 前后台系统(Super-Loop)

前后台系统,也叫裸机系统。主循环里轮询各种标志位,中断负责置位。结构简单,资源开销小。

// 典型前后台系统结构
void main(void)
{
    System_Init();
    
    while(1)
    {
        // 后台:主循环轮询
        if(g_flag_timer_1ms)
        {
            g_flag_timer_1ms = 0;
            Task_1ms();
        }
        
        if(g_flag_timer_10ms)
        {
            g_flag_timer_10ms = 0;
            Task_10ms();
        }
        
        // 前台:中断服务
        // void TIM_IRQHandler(void) { g_flag_timer_1ms = 1; }
    }
}

核心特点:

  • 所有任务串行执行,一个任务卡住,后面的全等
  • 中断优先级固定,无法动态调整
  • 适合任务少、逻辑简单的场景

我曾经踩过的坑: 做一个智能家居网关,用了前后台系统。平时好好的,一旦Wi-Fi重连,主循环里等待网络响应,按键扫描就停了。用户按了没反应,投诉电话打爆了。后来才意识到,前后台系统对长耗时操作太敏感了。

4.1.2 实时操作系统(RTOS)

RTOS 的核心是任务调度器。它把 CPU 时间切成片,分配给不同的任务。每个任务都有自己的栈空间和优先级。

// RTOS 任务创建示例(FreeRTOS)
void Task_KeyScan(void *pvParameters)
{
    while(1)
    {
        Key_Scan();
        vTaskDelay(pdMS_TO_TICKS(10)); // 每10ms扫描一次
    }
}

void Task_Display(void *pvParameters)
{
    while(1)
    {
        Display_Update();
        vTaskDelay(pdMS_TO_TICKS(50)); // 每50ms刷新一次
    }
}

void main(void)
{
    xTaskCreate(Task_KeyScan, "KeyScan", 128, NULL, 2, NULL);
    xTaskCreate(Task_Display, "Display", 256, NULL, 1, NULL);
    vTaskStartScheduler();
}
对比项 前后台系统 RTOS
任务隔离 无,共享栈空间 每个任务独立栈
响应时间 依赖主循环周期 可预测,优先级抢占
资源开销 极低(几百字节RAM) 较高(每个任务需栈空间)
调试难度 简单,单线程思维 需处理竞态、死锁
适用场景 简单控制、传感器采集 复杂多任务、通信协议栈

4.2 时间确定性分析

时间确定性,就是你的系统能不能保证:每次执行同一个任务,耗时都差不多。我见过一些系统,正常跑着没问题,突然某个中断进来,任务执行时间翻了三倍。这种不确定性,在工业控制里是要命的。

4.2.1 影响时间确定性的因素

  • 中断抢占:高优先级中断随时可能打断当前任务
  • 任务调度:RTOS 的上下文切换需要时间
  • 缓存命中率:Cache miss 会导致执行时间剧增
  • 分支预测:条件跳转预测失败会多花几个时钟周期
  • 内存访问:DMA、总线仲裁都会引入延迟

我个人习惯: 在设计阶段,就把所有可能打断任务的中断源列出来。然后问自己一个问题——最坏情况下,这些中断会同时发生吗?如果会,你的任务还能在规定时间内完成吗?

4.3 最坏情况执行时间(WCET)

WCET,全称 Worst-Case Execution Time。它不是平均时间,不是典型时间,而是最坏情况下的执行时间。做实时系统,你只能信 WCET。

4.3.1 WCET 分析方法

分析 WCET 有三种主流方法:

  1. 静态分析:通过分析代码路径,计算最大执行时间。不依赖实际运行,但需要精确的处理器模型。
  2. 动态测量:在目标硬件上反复运行,记录最长执行时间。简单直接,但可能测不到最坏路径。
  3. 混合方法:静态分析确定路径,动态测量获取时间参数。工程上最常用。
// 一个需要分析 WCET 的函数示例
int Process_Sensor_Data(int *buffer, int len)
{
    int sum = 0;
    for(int i = 0; i < len; i++)
    {
        // 最坏情况:每次循环都进入 if 分支
        if(buffer[i] > 100)
        {
            sum += buffer[i] * 2;  // 耗时操作
        }
        else
        {
            sum += buffer[i];      // 简单操作
        }
    }
    return sum;
}

这个函数,最坏情况就是每次循环都走 if 分支。如果 len=100,WCET 就是 100 次乘法加法的总时间。

4.3.2 WCET 的工程实践

我在做汽车电子项目时,遇到过一个问题:一个 CAN 报文处理函数,平时跑 50μs,偶尔会飙到 200μs。查了很久才发现,是某个条件分支里调用了 memcpy,而 memcpy 的实现里有个循环,拷贝长度不同,时间差异很大。

避坑指南: 写实时代码时,尽量避免以下模式:

  • 循环次数不确定的 while 循环
  • 递归调用(栈深度不可控)
  • 动态内存分配(malloc/free 时间不确定)
  • 依赖硬件状态的轮询等待

4.4 知识体系总览

下面这张图,把本章的核心知识点串起来了。你想想看,从任务调度模型到时间确定性分析,再到 WCET,其实是一条线:先选架构,再分析确定性,最后量化最坏情况。

实时性基础:知识体系总览 任务调度模型 前后台系统 实时操作系统 (RTOS) 时间确定性分析 中断抢占 任务调度 缓存命中 分支预测 最坏情况执行时间 (WCET) 核心思路:选架构 → 分析确定性 → 量化最坏情况,三者缺一不可

4.5 本章小结

实时性不是跑得快,而是跑得稳。前后台系统简单可靠,但扛不住复杂场景;RTOS 灵活强大,但需要你理解调度机制。时间确定性分析帮你找出系统中的抖动源,WCET 则给你一个硬指标——你的系统到底能不能满足实时要求。