20. 多核RTOS插补:AMP与SMP架构、核间通信、负载均衡

各位同学,今天我们来聊聊多核RTOS下的插补实现。说实话,这个话题在几年前还算是个“高阶玩法”,但现在随着芯片越来越便宜,双核、四核甚至更多核的MCU遍地都是,你不想用多核都不行。

我个人习惯把多核插补分成两个大方向来理解:一个是架构选型,另一个是通信与调度。架构选型决定了你的核怎么干活,通信与调度决定了它们能不能好好配合。咱们一个一个说。

20.1 AMP与SMP:两种截然不同的“分工”哲学

先问大家一个问题:你手上有两个CPU核,你想让它们一起做插补运算,你会怎么分配任务?

答案其实就两种:要么每个核各干各的,互不干扰——这叫AMP(非对称多处理);要么两个核共享内存和操作系统,由内核统一调度——这叫SMP(对称多处理)

核心区别一句话总结:

  • AMP:每个核跑独立的RTOS实例,甚至不同的RTOS。核间通过硬件机制通信。
  • SMP:所有核跑同一个RTOS实例,共享内核对象(任务、队列、信号量等)。

我在项目中遇到过这样一个场景:一个六轴机械臂的控制器,要求同时做直线插补和圆弧插补,还要实时处理IO和通信。当时我选了AMP方案——让Core0跑主控逻辑和通信,Core1专门跑插补算法。为什么?因为插补算法对实时性要求极高,我不想被其他任务打断。AMP的好处就是“隔离”,坏处嘛……核间通信比较麻烦,后面会讲。

而SMP呢?更适合那些任务量均衡、不需要严格隔离的场景。比如一个三轴运动平台,插补、IO、显示三个任务,用SMP让内核自动分配负载,开发起来省心很多。

对比维度 AMP SMP
RTOS实例数 每个核独立(可不同OS) 所有核共享一个OS
任务调度 各核独立调度 全局统一调度
内存共享 需显式配置共享内存 默认共享所有内存
实时性隔离 优秀(互不干扰) 一般(可能被其他核任务抢占)
开发难度 较高(需处理核间通信) 较低(像单核编程)
典型应用 高实时插补、安全关键系统 通用运动控制、多任务并行

20.2 核间通信:AMP架构的“命门”

如果你选了AMP,那核间通信就是你绕不开的坎。说白了,两个核之间怎么传数据?总不能靠眼神交流吧。

常用的方式有这么几种:

  • 共享内存 + 原子操作:最简单,但要注意缓存一致性问题。我建议用volatile关键字加内存屏障(如__DSB()__DMB())。
  • 硬件Mailbox:很多MCU(如STM32H7、i.MX RT)内置Mailbox外设,通过中断通知对方核。适合小数据量、高实时场景。
  • 核间中断(IPI):一个核触发另一个核的中断,常用于同步或唤醒。

我曾经踩过一个坑:用共享内存传插补点数据,没加内存屏障,结果Core1读到的数据总是“半新半旧”——高字节是新的,低字节还是旧的。查了两天才发现是缓存一致性问题。嗯,这里要注意:多核共享内存一定要用原子操作或锁机制,别偷懒。

下面给一个简单的共享内存通信示例(伪代码):

// 定义共享内存结构体(放在特定地址,如0x24000000)
typedef struct {
    volatile uint32_t cmd;      // 命令标志
    volatile float    target_pos[3];  // 目标位置
    volatile uint32_t ready;    // 数据就绪标志
} SharedMem;

// Core0(主核)发送数据
void core0_send_data(float x, float y, float z) {
    SharedMem *shm = (SharedMem *)0x24000000;
    shm->target_pos[0] = x;
    shm->target_pos[1] = y;
    shm->target_pos[2] = z;
    __DMB();  // 数据内存屏障,确保写入完成
    shm->ready = 1;
    // 触发IPI通知Core1
    trigger_ipi_to_core1();
}

// Core1(插补核)接收数据
void core1_isr_handler(void) {
    SharedMem *shm = (SharedMem *)0x24000000;
    if (shm->ready) {
        __DMB();  // 读取前也要屏障
        float x = shm->target_pos[0];
        float y = shm->target_pos[1];
        float z = shm->target_pos[2];
        // 开始插补运算...
        shm->ready = 0;
    }
}

警告: 共享内存的地址必须是非缓存区域(如DTCM或SRAM),否则缓存一致性问题会让你怀疑人生。很多MCU有专门的“非缓存”内存区域,记得查手册。

20.3 负载均衡:SMP架构的“艺术”

SMP架构下,所有任务由RTOS统一调度到各个核上。但问题来了:如果插补任务和通信任务都挤在Core0上,Core1却闲着,那多核的优势就没了。

负载均衡的核心目标就一个:让每个核的利用率尽量接近,避免“一个累死,一个闲死”

我常用的策略有这几条:

  • 任务优先级与亲和性绑定:高实时任务(如插补)绑定到特定核,避免被迁移导致缓存失效。低优先级任务可以自由迁移。
  • 分阶段负载分配:比如插补预处理(粗插补)放在Core0,精插补和输出放在Core1。这样两个核流水线作业。
  • 动态负载监控:在RTOS中加一个监控任务,定期统计每个核的CPU占用率,如果发现某个核过载,就手动迁移一些任务过去。

你想想看,如果两个核都在做插补运算,但一个核要处理1000个点,另一个只处理100个,那整体速度就被慢的那个拖死了。所以任务切分要均匀

小技巧: 在FreeRTOS中,可以用vTaskCoreAffinitySet()设置任务亲和性。在RT-Thread中,可以用rt_thread_control()配合RT_THREAD_CTRL_BIND_CPU。别让插补任务到处乱跑,缓存命中率会下降很多。

20.4 知识体系图:多核插补的核心逻辑

下面这张图是我自己总结的,把AMP和SMP两条路线以及核间通信、负载均衡的关系画清楚了。你一看就明白。

多核RTOS插补核心逻辑 多核插补架构 AMP非对称多处理 SMP对称多处理 独立RTOS实例 核间通信 硬件隔离 共享RTOS内核 负载均衡 任务亲和性 核间通信方式 • 共享内存 + 原子操作 • 硬件Mailbox • 核间中断(IPI) 负载均衡策略 • 任务亲和性绑定 • 分阶段流水线 • 动态负载监控 选型依据:实时性 vs 开发效率

20.5 实际项目中的选型建议

说了这么多,到底怎么选?我个人的经验是:

  • 如果你做的是高端伺服驱动器、多轴联动插补,对实时性要求苛刻(微秒级抖动),选AMP。把插补算法锁在一个核上,谁也别来打扰。
  • 如果你做的是通用运动控制器、机器人控制器,任务种类多但实时性要求没那么变态,选SMP。开发效率高,后期维护也方便。
  • 如果你用的是双核MCU(如STM32H7、i.MX RT1170),其实可以混合使用:一个核跑SMP模式做应用,另一个核跑裸机或独立RTOS做硬实时插补。这种“AMP+SMP混合架构”在工业界越来越流行。

避坑指南: 我曾经在一个项目里,为了追求“绝对隔离”,把两个核都用AMP模式,结果核间通信的延迟反而成了瓶颈。后来改成“一个核跑SMP做管理,一个核跑裸机做插补”,性能反而上去了。所以别迷信某种架构,实测数据才是王道

好了,关于多核RTOS插补的AMP与SMP架构、核间通信和负载均衡,今天就聊到这里。这些内容在实际项目中非常实用,尤其是当你面对多核芯片时,选对架构能少走很多弯路。