2. RTOS基础概念:实时操作系统定义、任务调度机制、中断管理、时钟与定时器

好,咱们进入第二章。这一章是RTOS的根基,说白了就是搞清楚RTOS到底是个什么东西,它凭什么能“实时”。我见过不少工程师,上来就撸代码,结果任务优先级设反了,系统跑起来跟蜗牛似的。嗯,咱们先把地基打牢。

2.1 实时操作系统定义:到底什么是“实时”?

很多人一听到“实时”,就以为是“快”。其实不是。实时系统的核心是确定性——在规定的时间内,必须完成规定的任务。晚了一毫秒,系统就算失败。

我在做智能手环项目时遇到过这么个事:心率监测模块每100ms要采集一次数据。如果系统调度不及时,漏掉一次采样,心率曲线就会出现毛刺。用户戴着跑步,心率突然从120跳到180,你说吓不吓人?这就是实时性没保证的后果。

实时系统分两种:

  • 硬实时:截止时间必须满足,否则系统崩溃或造成灾难。比如汽车安全气囊的控制。
  • 软实时:偶尔超时还能接受,但性能会下降。比如视频播放,偶尔卡顿一下还能忍。

可穿戴设备嘛,大部分场景是软实时。但像跌倒检测、紧急呼叫这种,就得按硬实时的标准来设计。

核心定义:RTOS是一个能在确定时间内响应外部事件、并完成任务调度的操作系统内核。它不追求“快”,追求的是“准时”。

2.2 任务调度机制:谁先跑,谁后跑?

任务调度,说白了就是CPU决定“接下来该干谁的活”。RTOS里最常用的调度策略是优先级抢占式调度

什么意思?每个任务都有一个优先级。高优先级的任务准备好了,立马打断低优先级的任务,抢过CPU开始跑。低优先级的任务只能乖乖等着。

我个人的习惯是:在项目初期就把所有任务的优先级列一张表,标清楚哪些是时间敏感的,哪些可以容忍延迟。比如:

任务名称 优先级 周期/触发方式 说明
心率采集 高(5) 100ms定时触发 硬实时,不能丢数据
屏幕刷新 中(3) 30ms定时触发 软实时,偶尔卡顿可接受
蓝牙通信 低(1) 事件触发 延迟几毫秒没关系

这里有个坑,我曾经踩过:优先级反转。低优先级任务占着某个资源不放,高优先级任务等着用,结果中间优先级的任务趁机跑起来,高优先级任务反而被“饿死”了。解决办法?用优先级继承协议,或者干脆把共享资源的访问用互斥锁保护起来。

避坑指南:我曾经在一个项目中,把三个任务的优先级设成了5、4、3,觉得挺合理。结果跑起来发现,优先级为3的任务几乎没机会运行。后来一查,优先级为4的任务一直在循环,把CPU吃满了。记住:优先级不是越多越好,够用就行。一般RTOS支持256级优先级,但可穿戴设备里,5-8级就足够了。

2.3 中断管理:别让CPU闲着

中断是RTOS的“神经末梢”。外部事件来了,比如按键按下、传感器数据就绪,CPU得立刻放下手里的活,去处理这个事件。

中断处理的原则是:快进快出。在中断服务函数(ISR)里,只做最紧急的事,比如读取数据、置一个标志位。复杂的处理逻辑,交给任务去干。

为什么?因为中断是关着的。你在ISR里待太久,其他中断就进不来。比如你在处理按键中断时,心率数据的中断来了,但进不去,数据就丢了。

我一般这样设计:

// 中断服务函数——只做最少的活
void EXTI0_IRQHandler(void) {
    // 1. 清除中断标志位
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 2. 读取按键状态
        uint8_t key_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
        // 3. 发送信号量给任务,让任务去处理
        xSemaphoreGiveFromISR(key_semaphore, NULL);
        // 4. 清除中断标志
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

// 任务函数——处理真正的逻辑
void Key_Task(void *pvParameters) {
    while(1) {
        // 等待信号量
        if(xSemaphoreTake(key_semaphore, portMAX_DELAY) == pdTRUE) {
            // 处理按键逻辑:消抖、响应、更新UI等
            Handle_Key_Press();
        }
    }
}

你看,ISR里就干了三件事:清标志、读数据、发信号量。剩下的全交给任务。这样中断占用的时间极短,系统响应就快。

注意:在ISR里调用RTOS的API时,一定要用“FromISR”结尾的版本,比如xSemaphoreGiveFromISR。普通版本会在内部切换任务上下文,这在中断里是禁止的。我曾经因为用了xSemaphoreGive,导致系统在中断里死锁,整个手环直接黑屏。嗯,从那以后我再也不敢马虎了。

2.4 时钟与定时器:系统的“心跳”

RTOS需要一个“心跳”来驱动调度。这个心跳就是系统时钟节拍(System Tick)。一般由硬件定时器产生,每隔固定的时间(比如1ms)触发一次中断。

每次节拍中断,RTOS内核都会检查:有没有任务到期了?有没有定时器超时了?然后决定是否切换任务。

节拍频率怎么选?

  • 频率太高(比如0.1ms一次):CPU大部分时间都在处理节拍中断,真正干活的时间少了。这叫“系统开销过大”。
  • 频率太低(比如10ms一次):任务切换不够及时,实时性变差。

我个人习惯:可穿戴设备一般用1ms或5ms的节拍。1ms适合对响应要求高的场景,5ms则更省电。你想想看,手环电池就那么点大,省一点是一点。

除了系统节拍,RTOS还提供软件定时器。这东西特别好用,比如你要每隔100ms采集一次心率,不用自己写一个任务去延时,直接创建一个软件定时器,到期了回调函数自动执行。

// 创建一个软件定时器,周期100ms,自动重载
TimerHandle_t heart_rate_timer = xTimerCreate(
    "HeartRateTimer",          // 名字
    pdMS_TO_TICKS(100),        // 周期:100ms
    pdTRUE,                    // 自动重载
    (void *)0,                 // 定时器ID
    HeartRate_Timer_Callback   // 回调函数
);

// 启动定时器
xTimerStart(heart_rate_timer, 0);

// 回调函数
void HeartRate_Timer_Callback(TimerHandle_t xTimer) {
    // 这里触发心率采集
    Trigger_HeartRate_Sample();
}

注意:软件定时器的回调函数是在定时器任务的上下文里执行的,不是在中断里。所以你可以放心地调用RTOS的API,不用担心中断的问题。但也要注意,回调函数里别做耗时操作,否则会阻塞其他定时器。

总结一下:时钟节拍是RTOS的“心跳”,软件定时器是“闹钟”。两者配合,就能精确控制任务的执行时机。我在做手环项目时,用系统节拍驱动调度,用软件定时器管理传感器采样和屏幕刷新,整个系统跑得稳稳的。

好了,这一章就到这里。下一章咱们聊聊任务间通信——怎么让任务之间高效地传递数据,而不打架。