news 2026/9/28 15:42:17

RK3568 AMP双系统实战:Linux+RT-Thread一芯双核,3分钟搞定实时性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RK3568 AMP双系统实战:Linux+RT-Thread一芯双核,3分钟搞定实时性

做嵌入式这几年,凡是碰过工业控制、机器人、实时通信的项目,迟早都会面临同一个尴尬:主控芯片性能足够强,但Linux的实时性就是差点意思。尤其是在RK3568这种四核A55的平台上,跑个EtherCAT主站、运动控制算法,或者要处理微妙级的中断响应,纯Linux方案总让人心里不踏实。我这次要分享的,就是把RK3568做成一个“一芯双系统”的玩法——通过AMP(非对称多处理)架构,让Linux和RT-Thread在同一颗芯片上各司其职,Linux负责上层业务、网络、人机界面,RT-Thread去扛硬实时任务。整个过程熟练之后,从编译RT-Thread镜像到启动双系统,3分钟真的够用。这篇文章会从AMP方案设计、Linux侧remoteproc配置、RT-Thread移植,到双系统通信和调试技巧,全部串起来讲一遍,适合手里有RK3568开发板、又不想加第二颗MCU的朋友。

先说清楚一个前提,所谓“3分钟搞定”不是玄学,指的是在交叉编译工具链、Linux内核源码、RT-Thread BSP都准备好的情况下,改设备树、编镜像、加载启动这一套纯操作耗时。第一次做的人肯定要踩几个坑,花半天到一天很正常,不过把流程捋顺之后,后面就是流水线作业。

1. AMP双系统方案的整体设计思路

1.1 为什么是AMP而不是SMP

很多人一听到“四核A55跑Linux”,第一反应是SMP(对称多处理),四个核统一调度,跑一个操作系统。但做实时控制的人很快会发现,SMP模式下Linux的调度器、中断处理、自旋锁,都会带来不可控的延迟。哪怕打了PREEMPT_RT补丁,也依然绕不开内核里那些关中断的临界区。

AMP的思路则是把芯片当成“一栋楼里的两户人家”:Linux住客厅,RT-Thread住卧室,各有各的门牌号(CPU核心),各有各的厨房(内存区域),互不打扰。RK3568恰好适合这么玩,四核Cortex-A55本身就支持把不同核心分配给不同操作系统,每个核还有独立的通用定时器,中断控制器GIC也支持按核路由中断。

在这种架构下,Linux侧偶尔卡顿、跑个高负载任务,完全不影响RT-Thread那一侧的中断响应和任务切换。这就是工业现场最看重的东西:确定性。

1.2 为什么选RT-Thread作为实时核

既然要AMP,副核跑什么系统就得选型。裸机方案最简单,但写起业务逻辑来太痛苦,尤其涉及网络协议栈、文件系统、复杂状态机的时候,裸机开发效率低得让人抓狂。

RT-Thread的好处是它本身就是一个轻量级实时操作系统,调度器是优先级抢占式的,任务切换时间确定,中断响应快。更关键是生态,RT-Thread的驱动框架、设备模型、IPC机制(信号量、消息队列、事件集)都现成,而且支持POSIX接口的一大部分,从裸机迁移过来成本很低。我用的RT-Thread版本是5.x,BSP里已经带了RK3568的板级支持,包含串口驱动、GIC中断初始化、定时器驱动,省了最底层的移植功夫。

另外还有一个很现实的因素:成本。RT-Thread开源免费,商用也没有授权费,这对产品落地很重要。你要是用QNX或者VxWorks,光授权费就能让老板脸色变三变。

1.3 核与内存资源怎么分配

RK3568一共四个核,我采用的是Linux占CPU0和CPU1,RT-Thread占CPU2和CPU3。为什么不是Linux占3个核、RT-Thread只占1个?因为实时控制任务通常有多个:EtherCAT周期通信一个任务、运动规划一个任务、安全逻辑一个任务,都堆在一个核上虽然也能跑,但耦合度高,调试起来头大。两个核给RT-Thread,一个管通信,一个管算法,逻辑清晰。

内存划分上,DDR总共4GB,我给RT-Thread预留了64MB,在0x10000000起始地址。这个地址不是拍脑袋定的,需要考虑Linux内核/rootfs的内存布局,预留区域必须落在Linux不会乱碰的区域。常见做法是在设备树里用reserved-memory声明,Linux内核会自动将这个区域排除出页表管理。

外设分配就更讲究了。EtherCAT从站控制器(ESC)、PWM输出、编码器接口这些实时性要求高的外设,统统挂在RT-Thread那一侧。而显示、USB、网卡、WiFi这些业务相关外设,继续留在Linux侧。

资源类型Linux侧RT-Thread侧
CPU核心CPU0、CPU1CPU2、CPU3
内存其余DDR0x10000000起的64MB
定时器通用定时器各核私有定时器
典型外设显示、USB、网卡、WiFiEtherCAT、PWM、编码器
主要任务人机界面、网络服务、数据存储实时通信、运动控制、安全逻辑

这样划分之后,两个系统几乎没有资源竞争,实时核的每一个中断都有确定的响应路径。

2. 环境准备与镜像编译

2.1 硬件与软件清单

先说硬件,我手上的板子是正点原子的ATK-DLRK3568,核心板加底板的组合,不过这套流程在野火、飞凌这些RK3568板卡上也都通用,区别只在于设备树里的串口、GPIO编号不同。

核心硬件清单就四样:

  • RK3568开发板一套(含电源、网线)
  • USB转串口模块两个(双系统独立日志必备,后面调试技巧会讲)
  • Ubuntu 20.04/22.04主机一台,用于交叉编译
  • SD卡或USB烧录工具,用于更新Linux侧rootfs

软件侧需要准备的东西多一些,我列个清单:

  • RK3568 Linux SDK(包含内核源码、设备树、根文件系统构建工具)
  • aarch64-linux-gnu交叉编译工具链
  • RT-Thread源码,建议直接git clone官方仓库
  • RT-Thread env工具或scons构建工具

2.2 RT-Thread BSP编译的若干关键点

RT-Thread的RK3568 BSP在源码bsp/rockchip目录下,不同版本目录名可能有差异,我用的版本里是直接支持RK3568的。进入BSP目录,先用menuconfig做配置,这一步有讲究,不能一路默认。

需要特别确认的是“AMP支持”这个配置项。RT-Thread的RK3568 BSP默认可能是跑在全部核心上的SMP版本,做AMP时要切到单核/双核启动模式,让RT-Thread不被Linux的启动流程干扰。

// menuconfig中需要确认的配置 RT-Thread Kernel → [*] Enable SMP (2) The number of cores [*] AMP mode support

这里有个坑,如果RT-Thread跑双核,那么RT-Thread自身的SMP也要使能,并且要确认它不会去启动CPU0和CPU1——那两个核归Linux管。BSP里的启动汇编代码通常会有CPU ID判断,只有CPU2和CPU3才进入RT-Thread的启动流程,这个逻辑在board/startup相关代码里。

配置完成后执行scons编译,产物是rtthread.elf,这个elf文件就是之后交给Linux remoteproc加载的镜像。注意不要编译成bin格式,elf里包含了加载地址和入口地址信息,remoteproc框架能自动解析。

2.3 Linux内核配置

Linux侧需要确保以下内核选项打开,否则remoteproc和rpmsg都会缺东西:

CONFIG_REMOTEPROC=y CONFIG_ROCKCHIP_REMOTEPROC=y CONFIG_RPMSG_VIRTIO=y CONFIG_RPMSG_CHAR=y

CONFIG_REMOTEPROC是remoteproc框架的总开关,ROCKCHIP_REMOTEPROC是瑞芯微平台的实现驱动,RPMSG_VIRTIO则是基于virtio的核间通信层。这几个选项通常在rockchip默认内核里已经开启,但如果你的内核是裁剪过的,一定要回头确认。

另外还有一个容易被忽略的选项:CONFIG_HOTPLUG_CPU,如果这个开关打开,Linux会在系统初始化时把所有核都纳入CPU管理,即使设备树里标记了某些核为disabled,系统也可能通过cpu_up把它拉起来,导致和remoteproc抢核。我做AMP时直接在内核配置里把这个选项关了。

3. Linux侧AMP框架与设备树配置

3.1 remoteproc框架到底干了什么

remoteproc是Linux内核里管理远程处理器的标准框架,它的核心工作可以概括成四件事:把固件(就是RT-Thread的rtthread.elf)加载到指定内存;把目标CPU核从内核调度中摘除并启动到固件入口地址;管理远程处理器的生命周期,比如启动、停止、异常恢复;为上层提供RPMsg通信通道。

这个过程比较像给电脑插U盘,内核自动挂载文件系统。你只需要往/sys/class/remoteproc/remoteproc0/state这个虚拟文件里写start,remoteproc驱动就会自动完成从解析elf到启动RT-Thread的全流程。

3.2 设备树配置实录

设备树是ARM平台开发绕不过去的一环,AMP方案的成败有一半在这里。我的RK3568设备树里,核心是三个节点:预留内存、remoteproc设备、mailbox邮箱。

预留内存的写法如下,这段要放在root节点下:

reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; rproc_reserved: rproc@10000000 { no-map; reg = <0x0 0x10000000 0x0 0x04000000>; }; };

no-map属性很重要,它告诉内核这块内存不要建立页表映射,直接保留给RT-Thread独占。reg里的地址是0x10000000,大小256MB?不对,0x04000000是64MB,刚好是之前规划的内存大小。

remoteproc节点要放在soc节点下,并且要关联到预留内存:

rproc: remoteproc@10000000 { compatible = "rockchip,rk3568-rproc"; reg = <0x0 0x10000000 0x0 0x04000000>; memory-region = <&rproc_reserved>; interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>; interrupt-parent = <&gic>; clocks = <&cru CLK_GPU>; clock-names = "rproc"; status = "okay"; };

mailbox节点通常复用rk3568芯片自带的mailbox驱动,这里的interrupts要指定一个没有被Linux占用的中断号,RT-Thread侧也要用同一个中断号做接收通知。

设备树写完后,重新编译内核和设备树,烧录到板子上。这一步建议单独做一次,确认Linux还是正常启动的,再往下走AMP。

3.3 启动RT-Thread的标准流程

设备树就绪后,启动RT-Thread就变成了一条命令的事:

echo start > /sys/class/remoteproc/remoteproc0/state

如果一切正常,几秒钟后RT-Thread的启动日志就会通过它自己那路串口打出来。首次启动建议用dmesg看Linux侧日志确认加载过程:

dmesg | grep -i rproc

正常会看到elf解析成功、内存映射建立、CPU2和CPU3被释放、远程处理器启动之类的日志。

如果想开机自动启动,可以用systemd服务,在rootfs里建一个oneshot服务,开机执行上面的echo命令。我实际项目里就是这么干的,系统上电后全自动进入双系统状态,不需要人工干预。

4. RT-Thread侧移植与双系统通信

4.1 RT-Thread的内存与中断配置

RT-Thread这侧首先要确保链接脚本里的内存布局,和Linux设备树里预留的内存完全一致。具体来说,rtthread.elf里定义的RAM起始地址就是0x10000000,大小64MB。这个通常在BSP的链接脚本里配置,我用的版本是board/linker.lds或者board/scatter文件。

因为RT-Thread是运行在CPU2和CPU3上的,中断控制器GIC的初始化需要按当前核来路由。RT-Thread的GIC驱动一般会自动判断当前CPU ID,但有些BSP写死了CPU0,这在AMP模式下就会出问题,中断根本收不到。检查方法是看RT-Thread BSP里gic_dist_init和gic_cpu_init的调用,确保传给GIC的是实际CPU ID。

定时器这侧不用太纠结,ARM的通用定时器每个核都有私有的物理定时器,RT-Thread的arch timer驱动天然支持按核初始化。

4.2 共享内存与RPMsg通信的原理和配置

双系统通信的核心是RPMsg,但RPMsg的底层其实就两块:一块共享内存和一组用于通知的mailbox中断。数据通过共享内存交换,哪个系统往共享内存里写了新消息,就触发一次mailbox中断,通知对方来取。

共享内存的分配我建议直接用预留内存里的另一块区域,比如在64MB的RT-Thread专用内存里,再划分出头部4MB作为通信缓冲区,地址0x10000000到0x10400000。这部分区域RT-Thread和Linux都要映射,但映射方式要注意cache一致性,我吃过亏,一会儿在问题排查里细说。

Linux侧的RPMsg驱动通常会自动探测共享内存里的vring结构,RT-Thread侧则需要在BSP配置里打开RPMsg相关的组件和驱动。

4.3 一个简单的双向通信示例

我建议第一个AMP项目不要写复杂功能,先跑一个回环测试,把通信链路打通再说。Linux侧可以利用rpmsg_char接口写一个简单的测试程序:

#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> int main(void) { int fd = open("/dev/rpmsg0", O_RDWR); char buf[128]; while (1) { memset(buf, 0, sizeof(buf)); strcpy(buf, "hello rt-thread from linux"); write(fd, buf, strlen(buf) + 1); read(fd, buf, sizeof(buf)); printf("recv: %s\n", buf); sleep(1); } return 0; }

RT-Thread侧用rt_virtio_rpmsg驱动注册一个回调函数,收到消息后原样回传,一个回环测试就完成了。跑通之后,再替换成实际业务消息,比如EtherCAT主站的状态机命令、运动控制的目标位置等。

5. 调试技巧与常见问题排查实录

5.1 双路串口独立日志法

AMP调试最怕的就是两个系统打架,日志混在一起。我的做法是给Linux和RT-Thread各分配一个独立串口:Linux的console用UART0,RT-Thread的rt_kprintf输出到UART2。两个串口各接一个USB转串口模块,主机上开两个终端,分别看各自日志。

这样做的好处不用多说,两个系统互不干扰,RT-Thread哪怕死机了,Linux照样跑;反过来LGLinux再崩溃,RT-Thread的实时任务也不受影响。排查问题时对照两个终端的日志时间戳,能很快定位是哪一侧先出了问题。

5.2 状态查看与性能验证手段

Linux侧可以随时查看remoteproc的运行状态:

cat /sys/class/remoteproc/remoteproc0/state

能查到的状态包括offline、suspended、running等。RT-Thread侧则可以通过它自己的shell(msh)查询线程状态、信号量使用情况、CPU占用率。

要验证AMP方案的实时性,我最推荐的办法是GPIO引脚法:在RT-Thread的中断处理入口拉高一个GPIO,在出口拉低,用示波器看这个GPIO的脉冲宽度和抖动。我的实测结果,在Linux侧满负荷跑压力测试时,RT-Thread的中断响应时间抖动依然在微秒级别,这就是AMP的价值所在。

5.3 常见问题速查表

问题表象可能原因解决办法
RT-Thread启动后无日志链接脚本内存起始地址和设备树预留地址不一致核对rtthread.elf加载地址与reserved-memory的reg配置
Linux内核soft lockupCONFIG_HOTPLUG_CPU未关闭,Linux把RT-Thread的核抢了回去关闭内核配置,重新编译
共享内存数据总是读不对cache一致性问题,没有标记non-cacheable共享内存区域在设备树里加no-map或在内核页表里设为强序
两个系统串口日志互相干扰console串口和rt_kprintf串口复用规划不同UART作为各自日志输出
remoteproc加载elf失败固件格式不对或权限不足确认rtthread.elf有可读权限,dmesg查看具体错误码
CPU2/CPU3没被释放devicetree中cpu节点没有正确配置在cpu@2和cpu@3节点中确认status="disabled"

5.4 几个值得养成的操作习惯

AMP项目有个特殊的地方,设备树改动频繁,而设备树出错的症状往往很隐蔽。我建议每次修改设备树后,先用fdtdump或dtc反编译确认改动生效,再烧录进系统。

RT-Thread侧每改一次配置,重新生成rtthread.elf之后,最好用readelf检查加载段地址是否还在预留内存范围内:

readelf -l rtthread.elf

重点看LOAD段的VirtAddr和PhysAddr,有一个跑到预留内存外面,轻则启动失败,重则把Linux的内存踩了,系统直接panic。

再补一个容易忽略的问题:如果Linux侧开了看门狗,AMP模式下RT-Thread启动慢一点,看门狗超时把Linux重启了,那就很冤。调整看门狗超时时间,或者等RT-Thread启动后再喂狗,都是可行方案。

我个人在实际操作中的体会是,AMP混合部署真正的难点不在于编译和配置,而在于建立“分治”的思维——你要把一个原本单系统解决的问题,拆成两个系统协同解决。资源边界、通信协议、异常处理策略,必须在动手之前就想清楚,否则项目后期返工的成本远大于一开始的规划投入。目前这套方案我已经稳定跑了大半年,RT-Thread负责EtherCAT和运动控制,Linux负责Web配置界面和数据采集,双方配合得很顺。如果你也在做类似的实时控制项目,不妨从RT-Thread双核+LPEinux双核的划分开始尝试,后续还可以根据实际负载再微调。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:40:36

树莓派CM4物联网实战:4G模块与CSI摄像头远程监控开发全指南

我是一个把树莓派当“万能插座”折腾了快八年的老玩家&#xff0c;从最早的树莓派1代B型一路玩到CM4&#xff0c;踩过的坑比吃过的盐还多。这次收到一套树莓派CM4核心板&#xff0c;配的是微雪&#xff08;Waveshare&#xff09;CM4 IoT底板、4G模块和CSI摄像头&#xff0c;算是…

作者头像 李华
网站建设 2026/9/28 15:39:46

Cadence Allegro 17.4入门:从原理图工程到元件库完整流程

很多新手拿到Allegro 17.4的第一反应&#xff0c;是发现它跟网上老教程里那种界面完全不一样&#xff0c;菜单密密麻麻&#xff0c;还不知道该从哪个入口进。我自己带人的时候最深的感受是&#xff0c;真正劝退新手的并不是后面PCB Layout那些高深技巧&#xff0c;反而是最前面…

作者头像 李华
网站建设 2026/9/28 15:39:42

uniApp安卓串口通信实战:RS485与MODBUS协议解析指南

做安卓串口通信&#xff0c;在uniApp里绕不开一个现实问题&#xff1a;官方没有现成的串口插件&#xff0c;市场上能用的第三方模块又良莠不齐。半年前我接手一个农业环境监测项目&#xff0c;需要在安卓平板上通过RS485总线读取温湿度、光照、土壤墒情等多路传感器数据&#x…

作者头像 李华
网站建设 2026/9/28 15:38:50

Claude Code与Codex双工具实战:配置与第三方模型接入指南

先说个场景。我在一个项目里同时维护前端仓库和后端服务&#xff0c;平时写代码最烦的就是来回切工具、记各种命令。后来把 Claude Code 和 Codex 同时装进工作流之后&#xff0c;事情变得简单很多——一个负责代码库内的深度重构和长上下文理解&#xff0c;另一个负责快速生成…

作者头像 李华
网站建设 2026/9/28 15:38:25

2026分布式混合基础设施魔力象限解读:从评估逻辑到选型落地

拿到2026年Magic Quadrant for Distributed Hybrid Infrastructure&#xff08;也就是业内常说的分布式混合基础设施魔力象限&#xff09;的时候&#xff0c;我的第一反应是&#xff1a;这份报告已经不是单纯的“混合云厂商排行”了&#xff0c;它更像是整个基础设施市场的一次…

作者头像 李华
网站建设 2026/9/28 15:37:49

AI 接管已登录浏览器:开源浏览器智能体原理与本地实测

腾讯开源了一个让 AI 直接用你已经登录好的浏览器的项目&#xff0c;我最初看到这个描述时愣了一下&#xff0c;随后反应过来&#xff1a;这思路确实早就该有开源实现了。过去大半年我折腾过各种浏览器自动化方案&#xff0c;最折磨人的永远是登录态——要么让模型去识别验证码…

作者头像 李华