☰
RK3568 AMP双系统混合部署:Linux与RT-Thread协同实战
2026/9/28 15:42:01 网站建设 项目流程

干嵌入式这些年,Linux和RTOS的搭配一直是绕不开的话题。这次要聊的RK3568开发板AMP双系统混合部署,就是把一颗四核Cortex-A55处理器拆成两个阵营:Linux拿3个核跑应用、显示和网络,RT-Thread独占1个核跑硬实时任务。两边共享同一块DDR内存,用核间通信交换数据,互不干扰又紧密协作。

这种玩法最典型的场景就是工业控制器。一台设备里既要有HMI界面、文件系统、EtherCAT主站协议栈、网络调试,又要有微秒级响应的伺服控制、高速IO采集和运动控制周期任务。Linux生态确实香,但普通桌面Linux在负载上来之后,调度延迟抖到几毫秒甚至更差,拿去做伺服控制就是灾难。RT-Thread实时性好,但让它去搞复杂文件系统、跑图形界面、接一堆外设驱动也不现实。AMP方案正好取长补短。

如果你手上有RK3568板子,想跑Linux+RT-Thread混合部署,或者纯新手想搞明白AMP到底怎么落地,这篇博文可以直接照着做。我不会给那种“官方手册翻译版”的内容,全部是按我实际开发经验梳理出的完整步骤:从方案选型、环境准备、3分钟快速部署,到设备树内存预留、CPU隔离、RT-Thread端适配,再到调试技巧和常见坑,一条链路讲清楚。

1. 为什么要在RK3568上做AMP双系统:项目需求与方案选型

1.1 什么样场景逼我做AMP:Linux太慢,裸机太单薄

先说结论:只有当你同时需要“Linux的生态”和“RTOS的确定性”时,才值得上AMP。否则单跑Linux或者单跑RT-Thread省事得多。

我之前做过一个八轴运动控制器,需求很典型:上位机通过网口或USB下发G代码,系统要解析路径、做插补运算,同时通过EtherCAT总线给伺服驱动器发周期同步指令。EtherCAT周期任务通常要求125微秒到1毫秒的同步周期,抖动必须控制在几十微秒以内,不然电机就会抖。这个任务单靠Linux进程调度很难保证,就算打RT-Preempt补丁,在核多、中断频繁的情况下,抖动依然不可控。另一个痛点是人机交互:触摸屏HMI、实时曲线显示、配方管理、远程升级,这些用Linux做太成熟了,你根本没必要自己造轮子。

如果不用AMP,常见的替代方案有两种。一种是外挂MCU跑实时任务,Linux和MCU之间走串口、SPI或者并口通信。这种方案问题也很明显:通信延迟不稳定,数据量大时容易成为瓶颈,而且增加一颗MCU既是成本也是故障点。另一种是Linux直接上RT-Preempt补丁,把内核变成抢占式实时内核。但工业现场对确定性要求高的任务,尤其像EtherCAT这种需要独立时间戳和高速总线控制的任务,还是不够“硬”。

AMP方案就是在同一颗SoC里,把实时任务放到独立的一个核上,用轻量级RTOS跑到最底层的控制逻辑。RK3568有4个A55核,性能在嵌入式SoC里属于能上台面的水平,拿1个核跑RT-Thread,3个核跑Linux,操作系统的负载压力都不大,核间通信靠共享内存和消息队列,延迟是纳秒级到微秒级,比外部MCU通信快两三个数量级。

1.2 为什么选RK3568而不是ZYNQ/STM32MP157

真正做选型的时候,我对比过ZYNQ-7010、STM32MP157、全志T113和RK3568。各有优劣,但从实际落地效率来看,RK3568在性能和资料生态之间平衡得最好。

平台核心资源AMP支持程度落地成本适合场景
STM32MP157双核A7 + M4原生支持OpenAMP,资料丰富低小规模实时控制、入门教学
ZYNQ-7010双核A9 + FPGA(内部无RTOS核,需外挂逻辑)可做异构,但需要FPGA开发能力高高性能信号处理、自定义硬件逻辑
全志T113双核A7 + RISC-V/RClass?生态较弱,适配难度高中低功耗场景
RK3568四核A55 + G52 GPU + 0.8T NPURockchip官方remoteproc/rpmsg方案,RT-Thread侧有适配中低工业控制器、机器人、边缘计算

RK3568最吸引我的点有三。第一,4核A55足够拿来“挥霍”,分出1个核给RT-Thread后,剩下3个核跑Linux应用照样很顺畅,不像双核A7那样分一个核出来Linux就剩半条命。第二,Rockchip的SDK里已经有AMP和remoteproc的底层支持,不需要自己从零写底层调度逻辑,设备树配好,镜像编译好,部署路径很清晰。第三,板卡价格已经被各开发板厂商打得很低了,几百块就能买到带全套SDK和原理图的板子,工程上试错成本几乎为零。

当然,ZYNQ这种FPGA+ARM的异构方案在高级运动控制场景也有优势,如果你想在FPGA里做硬件插补或者自定义编码器接口,它依然是更好的选择。但如果你只想“快速把RTOS和Linux跑起来”,ZYNQ的学习曲线和工具链复杂度会让你崩溃,而RK3568是开箱即用的路线。

1.3 AMP、SMP、BMP三者的区别

做AMP的人很多,真正把概念讲清楚的少。我顺便把多核处理的三种典型模式一次性说透。

SMP(对称多处理)是一个操作系统管理所有CPU核,任务由内核调度器动态分配到任意核上。普通Linux就是这样,你看到4个核都显示有负载,但其实只有一个内核镜像在统一调度。SMP优点是整体CPU利用率高,缺点是单任务的实时性不确定,因为你不知道它下一次会被调度到哪个核,也不知道会不会被其他任务挤掉。

BMP(捆绑多处理)是SMP的变种,它允许不同核跑不同的任务域,但仍然是同一个内核统一管理。比如用Linux的isolcpus参数把核3隔离开,专门跑某个实时进程,本质上还是Linux内核在管这个核,只是其他任务不让往里放。这个方案可以用来做软实时,但硬实时场景还是不够彻底。

AMP(非对称多处理)是两个或更多个独立操作系统各自管理自己的核,每个OS有独立的内核镜像、独立的内存空间、独立的中断处理。RK3568的AMP方案中,Linux管理0/1/2核,RT-Thread管理3号核,两边通过共享内存和核间中断通信。AMP的优势是实时任务完全不受Linux调度器影响,劣势是资源静态划分,不能像SMP那样动态负载均衡。但对实时控制器来说,静态划分换来确定性,这是值得的。

2. 3分钟快速部署:从编译固件到双系统跑通的完整链路

2.1 准备材料与镜像结构

先倒个冷水:标题里的“3分钟”是指从拿到已经配置好的SDK镜像和RT-Thread固件开始算,到你看到两个系统同时跑起来,确实可以做到3分钟。但第一次接触板卡、自己从头编译整个SDK,那肯定不止3分钟,那是另一码事。所以这个快速部署路径,本质上是在你手里已经有可用材料的前提下,用最短路径验证“AMP双系统”的效果。

你需要准备的材料如下:

  • 一块RK3568开发板,正点原子、讯为、飞凌等常见板卡均可,SDK差别不大。我用的开发板带4GB LPDDR4,如果你的是2GB版本,内存预留地址和大小需要相应调整。
  • 一条USB转串口调试线,115200波特率,用于看Linux和RT-Thread的日志输出。
  • 确认板卡的启动方式,一般SD卡启动或EMMC烧录。快速迭代建议SD卡,不用反复擦写EMMC。
  • 安装了Ubuntu的编译主机,用于编译Linux内核、设备树和RT-Thread固件。
  • 官方SDK里已经编译好的boot分区镜像、kernel镜像、rootfs镜像,以及一个编译好的RT-Thread固件。RT-Thread固件可以先用官方demo的elf,跑通链路后再改成你自己的工程。

整体镜像结构上,Linux侧照常使用U-Boot、boot.img、kernel、dtb、rootfs,RT-Thread固件要么放在rootfs的/lib/firmware目录下,由Linux侧的remoteproc框架在运行时加载;要么由U-Boot在启动阶段直接加载到预留内存地址。这两种方式我后面都会讲到,快速部署阶段建议用第一种,调试最方便。

2.2 编译Linux侧固件和RT-Thread固件

Linux侧不用多说,RK3568的SDK编译流程是标准化的,一般一条命令全自动编完:

cd SDK ./build.sh lunch # 选择对应板卡配置 ./build.sh uboot ./build.sh kernel ./build.sh rootfs ./build.sh updateimg

这里啰嗦一句,如果你不希望重新编整个Linux系统,只想改设备树和内核,那编译完kernel后把boot.img重新打一下包就行,不用碰rootfs。

RT-Thread固件编译前需要确认两件事:一是bsp是否对应RK3568,二是OpenAMP/共享内存的配置是否正确。以RT-Thread标准BSP为例,在环境里执行:

cd bsp/rockchip/rk3568 scons --menuconfig scons -j8

编译产物是rtthread.elf和rtthread.bin。rtthread.elf是带符号表的镜像,调试时看栈回溯很关键,remoteproc加载固件时也推荐用elf格式,因为elf里包含了加载地址信息。rtthread.bin是纯二进制镜像,适合在U-Boot环境下直接加载。

RT-Thread侧重点要配置内存地址,尤其是Vring、资源表和共享内存地址,这三个地址必须和Linux侧设备树里预留的内存完全对得上。具体配置项我放到第4章详细讲,你第一步只要会用默认demo跑起来,先别改动。

2.3 3分钟部署实操:加载RT-Thread固件的最小步骤

假设你手上已经有一块能正常启动Linux的RK3568板卡,rootfs里已经放好了rtthread.elf,那么完整的最小部署路径如下:

第一步,板卡上电,进入Linux系统。记住,Linux端一定要已经放好对应板卡配置的dtb,并且内核开启了remoteproc支持。一般情况下Rockchip的默认内核配置里都已经编译进去了,不需要你折腾。

# 确认remoteproc设备是否注册成功 ls /sys/class/remoteproc/ # 应该看到一个或多个remoteprocX目录 cat /sys/class/remoteproc/remoteproc0/name # 输出类似 rockchip,rk3568-remoteproc

第二步,把RT-Thread固件路径写入sysfs,并启动:

# 将固件复制到/lib/firmware目录 cp /path/to/rtthread.elf /lib/firmware/ # 告诉remoteproc要加载哪个固件 echo rtthread.elf > /sys/class/remoteproc/remoteproc0/firmware # 启动remoteproc echo start > /sys/class/remoteproc/remoteproc0/state # 观察内核日志 dmesg | tail -30

正常情况下,你会看到remoteproc框架解析elf、加载镜像到预留内存、将核3从Linux中hot-unplug出来、启动RT-Thread的过程。RT-Thread自己的打印信息会输出到调试串口(或者你配置的另一个UART口),看到类似[RT-Thread] Starting amp demo这种日志,就说明双系统已经跑起来了。

第三步,验证两个系统之间的通信。如果你跑的是官方demo,Linux端通常有个rpmsg用户空间测试程序,执行一下能收到RT-Thread发过来的消息。没有现成程序的话,用devmem命令直接读共享内存区域也能判断RT-Thread是否正常工作:

# 比如共享内存基地址在0xA0000000,RT-Thread启动后会写入一个magic devmem 0xA0000000 32 # 如果输出不是0xFFFFFFFF,说明RT-Thread已经活了

整个过程用不了3分钟。如果你用的是开发板厂商出厂就带了AMP demo的镜像,那更简单,串口控制台敲两三条命令就完事。这个快速路径的价值在于,它让你先在“黑盒可用”的状态下建立信心,再去深入理解配置原理。

假设你已经跑通了上面的步骤,接下来才是真正的“硬核”部分:搞懂为什么这样配,以及将来怎么改成自己的工程。

3. Linux侧核心配置:内存预留、CPU隔离与设备树修改

3.1 预留内存:reserved-memory节点写法

在Linux侧做AMP的第一步,是把一块物理内存从Linux的内存管理器中隔离出来。这块内存RT-Thread要用,Linux内核不能碰。如果忘了隔离,Linux的页面分配器极有可能把这块内存分配出去,然后RT-Thread往里写数据,接着就是各种神仙打架般的崩溃,最恐怖的是这种崩溃还不是必现的,偶尔跑几个小时才崩一次,排查起来非常头疼。

设备树里预留内存的标准写法如下,我以一块从0xA0000000开始、大小32MB的预留区域为例:

/ { reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; amp_rtthread: amp-rtthread@a0000000 { compatible = "shared-dma-pool"; reg = <0x0 0xA0000000 0x0 0x02000000>; no-map; }; }; };

关键点就是reg里的起始地址和大小,以及no-map属性。no-map的意思是这块区域不建立页表映射,Linux侧在MMU层面完全看不见这32MB,RT-Thread物理地址直接访问就行,双方不会由于页表缓存导致一致性问题。

大小怎么算?你需要覆盖四块内容:RT-Thread的代码段和数据段(一般8MB到16MB足够)、RT-Thread内核堆栈和线程堆栈、共享内存区域、OpenAMP需要的Vring缓冲区(一般是4KB到64KB)。总之,32MB是一个比较稳的尺寸,想省内存给Linux的话,16MB也能跑通,但需要仔细排布,别到时候RT-Thread堆空间不够导致malloc溢出。

地址怎么选?RK3568的DDR地址空间从0x00000000开始,外设寄存器映射在0xFDC00000以上。开发板常见的DDR配置有1GB/2GB/4GB/8GB,如果板子是4GB内存,那物理地址0x80000000到0xFFFFFFFF是高位内存区域。工程上建议把预留内存放在Linux常用内存对应的低位区间(比如0x08000000到0x80000000之间),或者放在DDR靠后的高位区间,取决于你的内存大小。以4GB板子为例,我习惯用0xA0000000,避免和内核的线性映射区挤在一起,也方便U-Boot加载固件时做地址换算。

3.2 CPU隔离:让一个A55核完全交给RT-Thread

内存隔离好了,接下来是CPU隔离。我们要让Linux完全不知道核3的存在,不对它做调度,不让普通任务跑上去,同时也要让内核不要往核3发定时器中断、处理器间中断和RCU回调。

最简单的做法是在内核启动参数里加一行:

maxcpus=3

这会让内核在启动过程中最多只初始化3个CPU,核3在Linux视角里直接不存在。这样最干净,但我必须提醒你,maxcpus=3会导致CPU核编号在启动早期就固定下来,某些CHAINED中断控制器或者GIC驱动会在启动时假设CPU掩码,个别SDK可能会有兼容性问题。实际测试下来,大部分RK3568 SDK没问题,但如果你遇到内核启动崩溃,可以先查是不是maxcpus太激进了。

更温和的隔离做法是保留4核,但把核3从调度器中摘出去:

isolcpus=3 nohz_full=3 rcu_nocbs=3

isolcpus=3告诉进程调度器默认不要往核3上放任务,nohz_full=3让核3上的内核时钟事件尽量延后,减少周期性tick中断,rcu_nocbs=3把RCU回调线程从核3上挪走。这套组合拳能显著降低核3上的内核活动。但它依然存在一个问题:别的核发起reschedule IPI时,目标核如果没有任务,一般不会触发,但系统级的事件(比如全局定时器、性能计数器中断)还是会进入到核3。如果你想追求确定性,推荐在RT-Thread启动后,Linux侧再用cpu hotplug方式将核3下线:

echo 0 > /sys/devices/system/cpu/cpu3/online

这样核3就从Linux的online集合里彻底消失了。remoteproc框架在启动RT-Thread的过程中其实会自动执行类似操作,所以你手动操作的时候要注意:别在RT-Thread已经跑起来后,又把核3重新online成Linux核,那会直接把RT-Thread的系统状态冲掉。

我个人的建议是:开发阶段先用isolcpus+rcu_nocbs这套组合,不彻底下线核3,这样Linux系统不会因为少了一个核而行为异常;产品阶段再将核3通过hot-unplug下线,固化和RT-Thread的边界。

3.3 remoteproc/rpmsg框架与设备树节点

remoteproc是Linux内核里专门用于管理远程处理器的框架,它负责加载固件、启动远程处理器、通知远程处理器停止,以及处理优雅停机。rpmsg则是建立在remoteproc之上的消息通信框架,通过共享内存和Vring实现端到端消息传递,对用户来说就像操作一个虚拟串口。

在RK3568的设备树中,remoteproc节点通常是系统默认存在的,但默认状态可能是disabled,需要由板级dts显式启用并配置内存地址。下面以常见RK3568 SDK写法为例,不同SDK细节会有差异,但思路一致:

&remoteproc { compatible = "rockchip,rk3568-remoteproc"; firmware-name = "rtthread.elf"; memory-region = <&amp_rtthread>; status = "okay"; };

firmware-name就是默认的固件文件名,memory-region指向你第3.1节里预留的内存节点。如果SDK里remoteproc节点由某个平台驱动直接管理,这里可能还会多一项vring配置:

vring0 = <&amp_vring0>; vring1 = <&amp_vring1>;

这些Vring缓冲区的地址必须和RT-Thread编译时的配置严格一致。我见过太多人在这里栽跟头:Linux侧设备树里预留的是0xA0000000,RT-Thread编译时OpenAMP配置却写的是0xA1000000,结果两边消息永远收不到,还找不到原因,因为日志显示两个系统都“正常”跑着,只是互相不说话。

改完设备树后,重新编译内核和dtb,烧录启动。启动后重点检查一下:

cat /proc/iomem | grep -A1 reserved

如果看到类似a0000000-bfffffff : amp_rtthread的打印,说明预留内存生效了。还要确认remoteproc设备树节点状态变成了okay。

到了这一步,Linux侧的骨架就搭完了:内存隔离开、CPU隔离开、remoteproc框架接管固件加载。接下来就看RT-Thread那边怎么配合了。

4. RT-Thread端适配:从纯净工程到可通信的AMP核

4.1 RT-Thread侧OpenAMP移植

RT-Thread作为AMP的远端处理器,需要把自己编译成能独立运行在单核上的完整RTOS镜像。RT-Thread本身对OpenAMP有原生支持,在menuconfig里找到OpenAMP相关的软件包,启用rproxy/rpmsg配置后,再把板级地址配置改成和Linux侧预留的内存一致就行。

配置项中最关键的几个参数:

配置项典型值说明
CPU ID3表示RT-Thread运行在第3号核,用于核间中断和目标核识别
Memory Base0xA0000000与Linux侧reserved-memory的reg地址一致
Memory Size32MB与Linux侧预留大小一致,超过会踩到Linux的内存
VRING0/VRING10xA0000000 + offset必须落在预留内存内,不能和代码段、堆重叠
Resource Table0xA0000000 + offset存放资源表,remoteproc启动时依赖它协商Vring

RT-Thread侧的具体修改点通常在bsp的board.c或link.lds里,你需要把RT-Thread的镜像加载地址设置为预留内存的起点(或略偏移一点),把堆空间也分配在预留内存里。工程实践中有个习惯:把RT-Thread镜像放在预留内存的低地址区域,比如0xA0000000,共享内存放在中间偏移,Vring放在最后。这样做的好处是内存布局直观,两边一旦对不上,用串口读一下地址内容就能定位。

编译确认无误后,会重新生成rtthread.elf。这里有个小建议:正式开发时编译出来elf后,先用readelf检查一下节区地址是否落在了你预设的预留范围内,别等到加载时才发现溢出了。

4.2 共享内存与核间通信

OpenAMP的rpmsg适合传输“消息”,数据量小、格式灵活。但在实际工业控制里,大部分场景是“RT-Thread周期采样,Linux周期下发参数”,比如RT-Thread每1毫秒采集一次编码器数据并写入共享内存,Linux从共享内存中读到最新值去刷新HMI界面;Linux下发一条速度指令,RT-Thread读到后立刻执行。这种场景用自定义共享内存结构体会更高效,不用每笔小数据都走一次rpmsg通道。

我常用的共享内存结构体大概长这样:

#define SHM_BASE 0xA1000000 #define SHM_MAGIC 0x47414D50 /* "GAMP" */ typedef struct { uint32_t magic; /* 魔数,用于握手 */ uint32_t cmd; /* Linux -> RT-Thread 的控制命令 */ uint32_t status; /* RT-Thread -> Linux 的状态反馈 */ int32_t joint_speed[8]; /* 速度指令 */ int32_t joint_pos[8]; /* 位置反馈 */ uint32_t heartbeat; /* RT-Thread心搏计数,Linux侧可用来判断RT-Thread是否活着 */ } amp_shm_t; #define AMP_SHM ((volatile amp_shm_t *)(SHM_BASE))

RT-Thread侧在初始化阶段把magic写入共享内存,Linux侧用户空间程序通过devmem或mmap读到magic非0后,确认RT-Thread已经上线,然后开始周期轮询status字段。RT-Thread侧则在周期任务里不断更新状态和位置数据,同时检查cmd有没有新指令。

Linux用户空间程序如果只是调试,用devmem命令最方便:

# 写入magic验证:如果RT-Thread能看到并回写,则说明共享内存通路正常 devmem 0xA1000000 32 0x47414D50 # 读回RT-Thread状态 devmem 0xA1000000 32

生产环境不建议直接用/dev/mem裸操作,更稳妥的是写一个UIO驱动或者普通字符设备驱动,把物理地址映射到用户空间,同时加一些访问权限控制。但原理完全一样,本质都是在共享内存上做无锁读写,配合内存屏障保证顺序。

这里必须提一个关键点:两个CPU访问同一块共享内存,怎么保证数据一致性?RK3568的A55四核共享同一个L2缓存,同一个簇内的缓存一致性是由硬件监听的,实测下来直接读写共享内存大部分时候没问题。但从工程严谨性出发,我仍然强烈建议把共享内存区域配置为non-cacheable,或者在每次读写后显式执行缓存清理指令(DSB/ISB)。否则某个深夜你可能会面对这样一个诡异bug:Linux侧写一个变量,RT-Thread侧读出来却是旧值,因为数据还留在cache里没写回DDR。这种问题用仿真器和示波器都很难抓,只能从机制上堵塞。

4.3 典型业务:用RT-Thread跑EtherCAT周期任务

聊完通信,我们落到一个实际业务上:EtherCAT。做工业控制的人对EtherCAT一定不陌生,它基于以太网,但和普通以太网不同,EtherCAT主站要求极其严格的时间同步和周期确定性。常见做法有两种流派。

一种是把IGH主站跑在Linux上,配合RT-Preempt补丁,让主站的周期任务在RapidIO上下文里运行。这种方案省事,因为IGH的主站逻辑、网卡驱动、邮箱协议都是现成的,你只需要在Linux里编写应用层,把控制指令和状态数据透传给运动规划模块。但缺点是抖动比较大,负载一高、网络一忙,周期就可能被挤掉,严重时EtherCAT分布式时钟会导致伺服报警。

另一种就是我们现在说的AMP方案:让RT-Thread独占一个核,在这个核上实现EtherCAT主站的核心周期逻辑,网卡中断由RT-Thread直接接管,周期任务通过RT-Thread的定时器调度,实测抖动通常能控制到个位数微秒;Linux核只负责跑界面、文件系统、远程调试。两边通过共享内存或者rpmsg交换PDO数据。这个结构下,IGH主站跑在Linux侧其实是作为“上位配置工具”存在的,真正的周期主站逻辑已经移到了RT-Thread侧,所以标题搜索结果里“适配RK3568的EtherCAT IGH主站驱动”实际上可能把两种路线都提到了,但真正要拿到高确定性,AMP是更合适的答案。

如果你真的要在AMP下跑IGH主站,还有一个变通方案:Linux侧跑IGH主站,但把网卡中断和发送周期任务绑定到RT-Thread独占的核上?这里要小心,因为网卡驱动是Linux的设备模型,RT-Thread无法直接接管它,跨系统共享一个网卡设备很不现实。所以我的建议是:周期任务的核心尽量放RT-Thread裸核上,Linux侧只做非周期应用,这样系统边界最干净,也好排查故障。

5. 调试技巧与踩坑实录:AMP项目里最常见的那些坑

5.1 怎么确认RT-Thread真的独占了一个核

跑通之后,第一个要确认的是:RT-Thread是不是真的“独占”了核3。光看日志说“I am running”不够,你要看Linux侧有没有往这个核塞过东西。

核3被remoteproc接管并hot-unplug后,在Linux侧执行:

cat /sys/devices/system/cpu/online # 正常情况下只会输出0-2,而不是0-3

另外多观察几次:

cat /proc/interrupts | grep CPU3

正常情况下CPU3那一列应该全部是0,或者CPU3直接不出现在表格里。如果你看到CPU3上还有大量Local timer中断、Rescheduling中断或者IPI中断,说明核3并没有被真正隔离干净,RT-Thread的实时性会受影响。

还有一个排查技巧:在Linux侧跑一个CPU烧录程序,同时打开RT-Thread的日志窗口,观察RT-Thread的心跳是否稳定。如果心跳间隔出现明显的毛刺,比如原本1ms的心跳抖到2ms,先用上面的方法查一下Linux侧是不是有中断打到了核3。干扰源通常来自高优先级全局定时器,或者未经过隔离配置的RCU线程。

5.2 内存冲突与缓存一致性

AMP排错时最费时间的往往不是逻辑错误,而是内存问题。我遇到过最典型的一个bug是这样的:Linux侧设备树里预留了内存,但U-Boot阶段忘了给U-Boot本身也做相同配置,导致U-Boot在启动Linux之前,把内核镜像解压到了预留内存区域。结果是RT-Thread的镜像被Linux内核镜像覆盖,RT-Thread永远启动不起来。这种问题不会在日志里报错,只会表现为“一切看起来正常,但RT-Thread就是卡死”。

排查方法其实很简单:Linux启动后用devmem读一下预留内存地址,确认里面的内容到底是RT-Thread镜像和解压后的内核数据,还是全F的空白区域。如果发现预留内存的内容被Linux内核覆盖了,基本可以断定是U-Boot阶段的布局没对齐。

缓存一致性问题多发生在Linux侧使用DMA外设的场合。比如你让GPU或VPU往共享内存区域写入数据,但这些硬件访问的是物理内存,跳过了CPU cache,如果共享内存区域不是non-cacheable,CPU读到的还是旧值。解决方案就是在设备树里把共享内存区域标记为no-map同时加上shared-dma-pool属性,或者让DMA使用cache maintenance操作,在每次DMA传输完成后执行dma_sync_single_for_cpu。

另外一点,写的共享内存代码一定要带volatile或者用原子操作包装,防止编译器优化导致数据只在寄存器里转圈。RT-Thread端和Linux端编译优化级别越高,越容易出现这种幽灵bug,我建议共享内存相关的代码文件统一用-O0或者-O1编译,虽然性能差点,但排错成本能降低一大截。

5.3 启动顺序与串口日志

AMP的启动顺序有两条路线,调试方式完全不同。

路线一是U-Boot先启动RT-Thread,再启动Linux。在U-Boot命令行环境里,把rtthread.bin加载到预留内存地址,然后跳转执行,等RT-Thread初始化完,再继续boot Linux。这种方式适合“上电后实时任务必须立即工作”的场景,缺点是RT-Thread先跑时,Linux还没起来,你只能通过独立串口看RT-Thread日志,不方便集中调试。

路线二是Linux先完全启动,再由remoteproc加载RT-Thread。这是最常用的开发调试路径,因为Linux环境健全,文件系统、网络、SSH都能用,加载固件、重启RT-Thread都只需要敲命令。缺点就是Linux启动花几秒钟,如果设备对“上电即响应”要求严格,就需要改走路线一。

串口日志是一个很容易被忽略的大坑。RK3568开发板一般只有一个UART调试串口,如果你让Linux和RT-Thread同时把日志打到同一个串口,会看到两者互相穿插,简直无法阅读。我的建议是:RT-Thread运行时把日志输出单独接到另一个UART口(或者复用某个空闲引脚,设备树里配置成uart功能),用另一根串口线去读。两台日志软件同步记录,时间戳对比起来就很方便。

如果板子实在没有多余UART,还有一个土办法:RT-Thread侧日志前统一加一个固定的[RTT]前缀,Linux侧日志不打印或加[LINUX]前缀,然后输出到同一串口,用文本过滤工具一拆就清晰了。虽然丑,但应急管用。

5.4 避坑速查表:从现象直接到解决方向

最后整理一个速查表,把AMP项目里最容易遇到的现象、可能原因和排查方向都列出来。这个表我强烈建议收藏,调试时直接对着找:

故障现象可能原因排查方向与命令
RT-Thread固件加载后无任何输出固件路径错误、地址被占、remoteproc没注册检查dmesg,确认/lib/firmware存在,确认/sys/class/remoteproc下有设备
RT-Thread起来几秒后崩溃预留内存被Linux覆盖用devmem读预留内存起始地址,比对镜像内容与RT-Thread符号表
Linux系统开机后频繁panicreserved-memory跟DDR初始化配置冲突检查U-Boot的memreserve,确保与设备树一致
两边日志严重穿插,无法阅读共用串口换独立UART,或用带前缀过滤的方式
rpmsg消息发不出去Vring地址不一致两端交叉确认Vring0/Vring1地址,重新编译
Linux核3出现大量中断CPU隔离没生效检查isolcpus/nohz_full参数,检查是否执行了hot-unplug
共享内存读到的值忽对忽错缓存一致性把共享内存区域设为non-cacheable,或增加内存屏障
RT-Thread实时任务抖动大核隔离不彻底,IPI干扰用/proc/interrupts统计CPU3中断,确认remoteproc已下线核3
U-Boot加载RT-Thread后Linux引导失败U-Boot没有为预留内存做memreserve在U-Boot配置中增加内存保留节点,地址匹配设备树
RT-Thread能启动但不更新共享内存数据没写明volatile或访问了错误地址检查编译优化级别,用readelf确认段地址

5.5 一个建议:先用官方demo跑通,再自己改

如果只能给一个建议,我会说:第一次做RK3568 AMP,千万不要一上来就自己从零搭工程。Rockchip的SDK里通常带着一份现成的AMP demo,里面有编译好的RT-Thread固件、设备树文件、Linux端测试程序,整个链路已经验证过是通的。先把这个demo原封不动地烧进去,确认你能看到两个系统跑起来,再进行定制。

定制顺序也不要有一步到位的想法。先把RT-Thread固件换成自己编译的版本,但设备树和Linux侧不变,验证是不是“只要换固件就能通”;再把共享内存区域换成自己的结构体,验证通信;最后再改CPU隔离、改U-Boot,每一步之间留好验证点。AMP的问题大部分是配置不同步造成的,分层验证能让问题隔离在一个很小的范围内。

我第一次在RK3568上调AMP时,花了整整一个下午排查一个“RT-Thread偶尔启动失败”的问题,最后发现是U-Boot的FDT加载地址和预留内存地址重合了,U-Boot启动Linux时把设备树写到了RT-Thread的镜像区域。这种地址重叠问题靠看日志完全看不出来,只有把内存布局打印出来一个个对比。后来我把开发板的U-Boot、内核、设备树、RT-Thread四个镜像的内存占用画在一张图里,才真正理解了AMP内存管理的核心——所有问题,最后都是内存布局问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询