上篇聊了实时系统的基础概念——硬实时、软实时、WCET、抖动。这些概念告诉你"什么是实时"。这篇聊具体的实现——RTOS(实时操作系统),特别是FreeRTOS,它是嵌入式机器人中最常用的实时操作系统。

RTOS和普通操作系统的区别

普通操作系统(Windows、普通Linux)的设计目标是吞吐量最大化——尽可能多地完成任务。实时操作系统的设计目标是确定性最大化——保证每个任务在确定的时间内完成。

这个设计目标的差异导致了根本性的架构差异。普通Linux内核中有很多不可抢占的区域(比如持有spin_lock时),高优先级任务可能被低优先级任务阻塞几毫秒甚至更长。RTOS的内核几乎完全可抢占,高优先级任务能在微秒级别获得CPU。

普通Linux的调度器(CFS)追求公平性——每个任务按权重分配CPU时间。RTOS的调度器追求优先级——最高优先级的就绪任务必须立即运行,不管其他任务等了多久。

中断处理也有根本性的差异。普通Linux的中断处理分为上半部(关中断,快速响应)和下半部(开中断,延迟处理)。这种设计提高了吞吐量,但引入了不确定性——下半部的执行时间不可预测。RTOS的中断处理通常更简单直接——ISR尽可能短,需要延迟处理的工作交给高优先级的任务来做。

内存管理方面,普通Linux用虚拟内存和分页机制,支持按需分配和内存超售。RTOS通常没有MMU,内存分配是静态的或者用简单的池分配器。FreeRTOS推荐在系统启动时一次性分配所有需要的内存(静态分配),运行时不做动态分配。这避免了malloc的不确定性——你不知道malloc什么时候会因为内存碎片而失败。

启动时间也是一个差异。普通Linux启动可能需要几十秒(初始化各种驱动和服务),RTOS启动通常在毫秒级别。对于需要快速启动的机器人应用(比如上电后立即可用的安全系统),这个差异很重要。

FreeRTOS的核心特性

FreeRTOS是目前最流行的开源RTOS,特别适合资源受限的嵌入式平台。它的内核非常小(几千行代码),可以在几十KB RAM的微控制器上运行。

任务管理是FreeRTOS的核心。每个任务有自己的栈空间,可以设置优先级(0到configMAX_PRIORITIES-1)。FreeRTOS使用抢占式调度——高优先级任务就绪后立即抢占低优先级任务。同优先级的任务可以用时间片轮转。

// FreeRTOS任务创建示例
void motor_control_task(void *param) {
    TickType_t last_wake_time = xTaskGetTickCount();
    while (1) {
        read_sensor_data();
        compute_control_output();
        send_to_motor_driver();
        // 精确的周期性延迟
        vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(1));
    }
}
// 创建任务: 优先级10, 栈大小512字
xTaskCreate(motor_control_task, "motor_ctrl", 512, NULL, 10, NULL);

信号量(Semaphore)和互斥锁(Mutex)用于任务同步。FreeRTOS的Mutex支持优先级继承——当高优先级任务等待一个被低优先级任务持有的Mutex时,低优先级任务的优先级会被临时提升,避免优先级反转问题。

队列(Queue)是FreeRTOS中任务间通信的主要方式。一个任务往队列里写数据,另一个任务从队列里读。队列内部有拷贝机制,发送者和接收者不需要共享内存。

软件定时器(Software Timer)允许你在不创建完整任务的情况下定时执行回调函数。适合周期性的轻量级操作(比如LED闪烁、看门狗喂狗)。

FreeRTOS在机器人中的应用

在机器人中,FreeRTOS通常运行在底层的MCU上(比如STM32),负责最基础的控制任务。

电机控制:FOC(磁场定向控制)算法需要10kHz到20kHz的执行频率,周期50到100微秒。这种高频控制只有RTOS能保证。

传感器采集:编码器计数、IMU数据读取、ADC采样等需要精确定时的操作。

通信协议处理:CAN总线消息的收发、EtherCAT从站协议栈等。

安全监控:过流保护、过温保护、看门狗等安全相关的功能。

上层的感知和规划通常运行在另一个处理器上(比如ARM Cortex-A系列跑Linux),两者之间通过UART、SPI或者以太网通信。

任务栈大小的确定是FreeRTOS开发中的常见问题。栈太小会导致溢出(任务崩溃或者数据被破坏),栈太大浪费RAM。FreeRTOS提供了uxTaskGetStackHighWaterMark()函数,可以查询任务运行以来栈的剩余空间。开发时先设一个较大的栈,运行各种场景后查看水位线,然后加上20%的余量作为最终值。

中断优先级和任务优先级的关系也要搞清楚。FreeRTOS中中断优先级和任务优先级是两套独立的系统。在ARM Cortex-M上,中断优先级用NVIC配置(数值越小优先级越高),任务优先级用FreeRTOS的API配置。一个常见的错误是中断优先级设得太低,导致FreeRTOS的API在中断中调用时出问题——FreeRTOS的API通常只能在中断安全的版本(带FromISR后缀的)中从中断上下文调用。

空闲任务(Idle Task)是FreeRTOS自动创建的最低优先级任务。当没有其他任务就绪时,CPU执行空闲任务。空闲任务可以做两件事:清理被删除任务的内存、执行低功耗操作(WFI指令让CPU进入睡眠状态)。如果你用了动态内存分配,空闲任务的栈要足够大来处理内存回收。

面试要点

FreeRTOS的任务调度机制。面试官可能问:FreeRTOS怎么保证高优先级任务的实时性?答案是抢占式调度+优先级继承。高优先级任务就绪后立即抢占CPU,Mutex的优先级继承防止优先级反转。

FreeRTOS和Linux的选择。什么时候用FreeRTOS,什么时候用Linux?资源受限(RAM小于1MB)、需要硬实时保证、功能简单——用FreeRTOS。需要复杂的网络栈、文件系统、图形界面——用Linux。很多机器人两者都用——MCU跑FreeRTOS做底层控制,MPU跑Linux做上层计算。

FreeRTOS的局限性。FreeRTOS不支持MMU(内存管理单元),所以没有进程隔离——一个任务的bug可能破坏另一个任务的数据。不支持虚拟内存——所有任务共享同一个物理地址空间。这些限制在资源受限的MCU上是必要的取舍,但在复杂应用中可能不够用。支持MMU的FreeRTOS+MPU可以提供一定程度的隔离,但不如Linux的进程模型完善。

给你的建议

学习FreeRTOS,最好的方式是买一块开发板(STM32 Nucleo或者ESP32都很便宜),安装FreeRTOS的开发环境,跑通官方的示例代码。重点理解任务创建、调度、同步和通信的机制。

然后尝试写一个简单的机器人控制程序——比如用FreeRTOS管理三个任务:一个读编码器、一个跑PID控制、一个处理通信。体会RTOS在多任务管理中的优势。

调试FreeRTOS程序时,栈溢出是最常见的问题。开启configCHECK_FOR_STACK_OVERFLOW配置(设为2),FreeRTOS会在栈溢出时调用回调函数。在回调函数中放一个断点或者打印错误信息,能帮你快速定位问题。另一个常见的坑是中断中调用了非ISR安全的API——这会导致难以复现的随机崩溃。记住:在中断中只能用带FromISR后缀的API。

除了FreeRTOS,还有其他值得了解的RTOS。Zephyr是Linux基金会维护的,社区活跃,支持的硬件平台非常多。VxWorks是商业RTOS,在航空航天领域用得很多,实时性能最好但价格昂贵。RT-Thread是国产的开源RTOS,中文文档和社区做得好,适合国内开发者。面试时能对比几种RTOS的特点,会展示你的视野。

面试时聊FreeRTOS,核心要展示的是你对嵌入式实时系统的理解。不只是知道API怎么用,还要知道调度器怎么工作、优先级继承的原理、任务栈大小怎么确定。能讲清楚你在实际项目中遇到的FreeRTOS问题和解决方案,比背API文档有用得多。


上一篇:第314篇 实时系统基础

下一篇预告:第316篇 Linux PREEMPT_RT实时补丁详解

 

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐