1. RTOS基础概念:实时操作系统定义、任务调度机制与基站场景要求

大家好,我是老张。做基站嵌入式开发十几年了,今天咱们聊聊RTOS最基础的东西。说实话,很多新手一上来就追着问「哪个RTOS最好用」,其实先把这些基础概念吃透了,后面移植优化才能得心应手。

1.1 什么是实时操作系统?

实时操作系统,说白了就是「在规定时间内必须完成任务」的操作系统。跟Linux、Windows那种「尽量快就行」的通用系统不一样,RTOS讲究的是确定性——你给它一个任务,它必须在截止时间前完成,晚一毫秒都不行。

我在项目中遇到过一件事:有个同事用Linux做基站控制面处理,结果偶尔会出现几十毫秒的抖动。基站里这问题可大了,用户直接掉话。后来换成RTOS,抖动控制在微秒级,问题就解决了。

实时系统的核心特征:

  • 确定性:任务执行时间可预测,不是越快越好,而是「准时」最好
  • 响应时间有上限:最坏情况下的响应时间必须满足要求
  • 优先级驱动:高优先级任务能抢占低优先级任务

实时性又分两种:

  • 硬实时:错过截止时间 = 系统故障。基站里的物理层处理就是硬实时,你想想看,OFDM符号周期是固定的,处理不完下一帧就来了,直接丢数据。
  • 软实时:偶尔超时还能接受,但性能会下降。比如基站的管理面任务,晚几毫秒问题不大。

1.2 任务调度机制:抢占式 vs 协作式

这是RTOS的核心中的核心。我习惯把任务调度比作「会议室分配」——谁先发言、谁可以打断别人。

抢占式调度

这是基站场景最常用的方式。高优先级任务可以随时打断低优先级任务。比如:

  • 一个低优先级的日志打印任务正在运行
  • 突然来了一个高优先级的基站帧中断处理任务
  • RTOS立刻暂停日志任务,切换到帧处理任务
  • 帧处理完成后,再回来继续打印日志

嗯,这里要注意:抢占式调度必须处理好临界区保护,否则会出现资源竞争。我曾经在调试一个基站项目时,就因为忘了加互斥锁,两个任务同时操作同一个缓冲区,结果数据全乱了,查了整整两天才找到问题。

协作式调度

协作式就简单多了——任务自己主动让出CPU。你不让,别人就等着。这种模式在基站里基本不用,因为一旦某个任务写了个死循环,整个系统就挂了。但有些超低功耗场景会用,比如传感器节点。

特性 抢占式 协作式
响应时间 可预测,微秒级 不可预测,依赖任务主动让出
实现复杂度 较高,需要处理临界区 简单,无需复杂同步
基站适用性 ✅ 必须使用 ❌ 不适用

1.3 内核对象:任务、队列、信号量、互斥锁

这些是RTOS的「积木块」,每个基站项目都离不开它们。

任务(Task)

任务就是一段独立执行的代码。在基站里,每个功能模块通常对应一个或多个任务:

  • 物理层处理任务(最高优先级)
  • MAC层调度任务
  • 网络协议栈任务
  • 管理面监控任务(最低优先级)

我个人习惯给每个任务分配独立的栈空间,大小要留够。有一次我为了省内存,把栈设得太小,结果任务一运行就栈溢出,系统随机崩溃。后来我养成了习惯:先设大一点,运行稳定后再逐步调小。

队列(Queue)

队列是任务间通信的「邮局」。一个任务往队列里发消息,另一个任务从队列里取消息。基站里用得特别多:

  • 物理层处理完一帧数据,通过队列发给MAC层
  • 上层控制命令通过队列下发到底层驱动
// FreeRTOS 队列使用示例
QueueHandle_t xPhyDataQueue;

// 创建队列,每个消息大小256字节,深度10
xPhyDataQueue = xQueueCreate(10, 256);

// 发送任务
void vPhyTask(void *pvParameters) {
    FrameData_t xFrame;
    // ... 处理帧数据 ...
    xQueueSend(xPhyDataQueue, &xFrame, portMAX_DELAY);
}

// 接收任务
void vMacTask(void *pvParameters) {
    FrameData_t xFrame;
    while(1) {
        if(xQueueReceive(xPhyDataQueue, &xFrame, pdMS_TO_TICKS(10))) {
            // 处理接收到的帧
        }
    }
}

信号量(Semaphore)

信号量用来做同步。二值信号量像是一个「事件通知」,计数信号量像是「资源计数器」。

基站里典型的用法:

  • DMA传输完成后,在中断中释放信号量,唤醒等待的任务
  • 多个任务共享一个硬件外设,用信号量控制访问权限

我的经验:中断服务程序里尽量只做最少的处理,比如释放信号量或发消息,然后把繁重的处理放到任务里。这样能减少中断关闭时间,保证系统实时性。

互斥锁(Mutex)

互斥锁和信号量有点像,但有个关键区别——互斥锁支持优先级继承,能解决优先级反转问题。

什么是优先级反转?举个例子:

  1. 低优先级任务拿到了互斥锁
  2. 高优先级任务来了,想拿锁,但拿不到,只能等
  3. 中优先级任务开始运行,它不需要锁,但因为它优先级比低任务高,所以一直抢CPU
  4. 低优先级任务被中优先级任务抢占,锁一直释放不了
  5. 高优先级任务被活活饿死

我曾经在基站项目里就踩过这个坑。一个高优先级的帧处理任务被一个低优先级的配置任务拖死,导致基站周期性掉帧。后来加上互斥锁的优先级继承,问题才解决。

⚠️ 注意:不要在中断里使用互斥锁!中断上下文不能阻塞等待。如果中断需要同步,用信号量或者直接发消息到队列。

1.4 基站场景对RTOS的实时性要求

基站不是普通的嵌入式设备,它对实时性的要求非常苛刻。我列几个关键指标:

  • 中断响应时间:从硬件中断触发到ISR开始执行,必须控制在1微秒以内。5G NR的时隙周期只有125微秒,留给处理的时间窗口非常窄。
  • 任务切换时间:上下文切换开销要尽量小,一般要求小于5微秒。FreeRTOS在ARM Cortex-M上能做到1-2微秒,RT-Thread也差不多。
  • 调度抖动:同一个任务每次被调度的时间偏差,要控制在微秒级。抖动大了,物理层的时序就乱了。
  • 临界区最大关闭时间:关中断的时间不能超过10微秒,否则会丢失无线帧。

我记得有一次做基站性能优化,发现任务切换时间占了总处理时间的5%。后来通过优化上下文保存/恢复的汇编代码,把切换时间从3微秒降到了1.5微秒,整体性能提升了将近3%。

所以你看,RTOS的基础概念不是纸上谈兵。理解透了,后面做移植优化才能知道「为什么这么改」、「改完有什么影响」。下一章咱们就动手,看看怎么把FreeRTOS移植到ARM Cortex-M平台上。