手把手教你把 Linux 系统从 x64 成功迁移到 ARM64:不只是“换个架构”那么简单
你有没有遇到过这样的场景?公司新项目要用树莓派 4 或者飞腾服务器跑边缘 AI 推理,但手头的代码和系统全是在 x86_64 上开发的。你想直接拷过去运行——结果连./a.out都报错:“无法执行二进制文件:可执行文件格式错误”。
这不是权限问题,也不是脚本没写对。这是架构鸿沟。
今天我们就来干一件“硬核”的事:把一个完整的 Linux 系统,从 x64 完整地移植到 ARM64 平台。这不仅是换个 CPU 跑程序那么简单,而是一次从编译工具链、内核配置、设备驱动到用户空间的全流程重构。
别担心,我会带你一步步走完这个过程,让你真正理解“为什么不能直接复制”,以及“到底该怎么正确迁移”。
为什么不能直接“复制粘贴”?
我们先打破一个常见的误解:很多人以为只要操作系统是 Linux,那在 x64 上能跑的程序,在 ARM64 上也能跑。毕竟都是“Linux”嘛。
错。
x86-64 和 ARM64 是两种完全不同的指令集架构(ISA):
- x86-64属于 CISC(复杂指令集),历史悠久,寄存器少,指令长度不固定。
- ARM64(AArch64)则是 RISC(精简指令集),设计现代,31 个通用 64 位寄存器,指令长度统一为 32 位。
这意味着:同一个 C 程序编译出来的机器码完全不同。你在 x64 上生成的.o文件或可执行文件,拿到 ARM64 设备上根本看不懂。
所以,跨架构迁移的本质不是“部署”,而是“重建”——我们需要用一套针对目标架构的工具链,重新构建整个软件栈。
第一步:搭建交叉编译环境 —— 在 x64 上“造”出 ARM64 的程序
既然不能直接运行,那就得提前“预编译”。这就是交叉编译(Cross Compilation)的核心思想:在宿主机(Host)上,生成能在目标机(Target)上运行的二进制文件。
我们的宿主机是 x86_64,目标平台是 aarch64。
安装工具链(以 Ubuntu 为例)
sudo apt update sudo apt install -y \ gcc-aarch64-linux-gnu \ g++-aarch64-linux-gnu \ binutils-aarch64-linux-gnu \ gdb-multiarch这些包提供了:
-aarch64-linux-gnu-gcc:专用于 ARM64 的 GCC 编译器
-gdb-multiarch:支持调试多种架构的 GDB
验证一下是否安装成功:
aarch64-linux-gnu-gcc --version你应该看到类似输出:
aarch64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0写个测试程序,看看能不能“跨架构”编译
// hello.c #include <stdio.h> int main() { printf("Hello from ARM64!\n"); return 0; }现在用交叉编译器来编译它:
aarch64-linux-gnu-gcc -o hello_arm64 hello.c然后检查这个文件的类型:
file hello_arm64输出应该是:
hello_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), ...✅ 成功!这是一个可以在 ARM64 上运行的可执行文件。
⚠️ 注意:你现在还不能在本地运行它(除非启用 QEMU 模拟)。但它已经具备了“去 ARM 板子上跑”的资格。
第二步:准备内核与设备树 —— 让系统真正“认得”你的硬件
有了用户态程序还不够,你还得有一个能启动的 Linux 内核。而 ARM 平台最大的特点就是:没有 BIOS,没有 ACPI 自动识别硬件。
怎么办?靠设备树(Device Tree)。
什么是设备树?
你可以把它想象成一张“硬件地图”。它告诉内核:“我在地址 0x7e200000 有个 UART 控制器,时钟频率是 48MHz,中断号是 55。”
如果没有这张图,内核就不知道怎么初始化串口、GPIO、I2C……自然也就没法启动。
获取并配置内核源码
我们使用主线稳定版内核:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git cd linux接下来进行默认配置(适用于大多数 ARM64 开发板):
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig这里的两个关键参数:
-ARCH=arm64:指定目标架构
-CROSS_COMPILE=:指定交叉编译前缀
(可选)定制设备树:添加一块 OLED 屏幕
假设你要在树莓派 4 上接一个 I2C 接口的 SSD1306 OLED 屏幕。你需要修改对应的.dts文件。
编辑arch/arm64/boot/dts/broadcom/rpi-4-b.dts,加入以下内容:
&i2c1 { status = "okay"; clock-frequency = <100000>; oled: oled@3c { compatible = "ssd1306"; reg = <0x3c>; width = <128>; height = <64>; }; };保存后,编译内核和设备树:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image dtbs modules -j$(nproc)编译完成后你会得到:
-arch/arm64/boot/Image:内核镜像
-arch/arm64/boot/dts/**/*.dtb:设备树二进制文件(如rpi-4-b.dtb)
这些文件将被 U-Boot 加载,并由 SoC 的 BootROM 启动流程引导执行。
第三步:构建根文件系统 —— 给系统“安家”
光有内核还不行,就像电脑只有 BIOS 和操作系统内核,没有硬盘上的/bin,/etc,/home,还是没法用。
我们需要一个完整的rootfs(根文件系统)。
方法一:使用 debootstrap 快速创建最小 Debian/Ubuntu 系统
推荐初学者使用debootstrap,它是 Debian 提供的官方工具,可以无痛构建基础 rootfs。
首先安装必要组件:
sudo apt install debootstrap qemu-user-static binfmt-support其中qemu-user-static允许我们在 x86 主机上模拟运行 ARM 程序,这对后续 chroot 配置至关重要。
开始构建 Ubuntu Focal 的 ARM64 根文件系统:
mkdir rootfs sudo debootstrap --arch=arm64 --foreign focal rootfs http://ports.ubuntu.com/--foreign表示这是跨架构构建,第一阶段只是下载包,第二阶段需要在目标架构环境下完成。
所以我们把 QEMU 的静态模拟器复制进去:
sudo cp /usr/bin/qemu-aarch64-static rootfs/usr/bin/然后进入 chroot 环境完成第二阶段安装:
sudo chroot rootfs /debootstrap/debootstrap --second-stage此时,rootfs目录下已经有了一个完整的 Linux 文件系统骨架。
配置基本服务
继续在 chroot 中设置主机名、网络、密码等:
sudo chroot rootfs /bin/bash << 'EOF' echo "arm64-system" > /etc/hostname echo "nameserver 8.8.8.8" > /etc/resolv.conf # 设置 root 密码 passwd root << PASSWD_INPUT your_password your_password PASSWD_INPUT # 更新源并安装常用工具 apt update && apt install -y openssh-server net-tools iproute2 sudo EOF这样你就拥了一个带 SSH 登录能力的基础系统。
第四步:制作可启动镜像 —— 把所有部件打包烧录
现在我们手里有三样东西:
1. 内核镜像Image
2. 设备树文件rpi-4-b.dtb
3. 根文件系统rootfs
下一步是把它们整合成一张 SD 卡可用的启动镜像。
创建 ext4 镜像文件
dd if=/dev/zero of=rootfs.img bs=1M count=2048 mkfs.ext4 rootfs.img mkdir mount_point sudo mount rootfs.img mount_point sudo cp -a rootfs/. mount_point/ sudo umount mount_point这张 2GB 的镜像就可以写入 SD 卡,作为根分区挂载。
SD 卡分区建议(典型布局)
| 分区 | 类型 | 内容 |
|---|---|---|
| 1 | FAT32 | 启动分区:存放Image,.dtb,config.txt |
| 2 | ext4 | 根文件系统 |
U-Boot 或 Raspberry Pi 的固件会先加载 FAT32 分区中的内核和设备树,再挂载 ext4 分区作为/。
常见问题与调试技巧
即使一切都按步骤来,也难免遇到启动失败的情况。以下是几个高频“踩坑点”及应对方法:
🔴 问题1:卡在 U-Boot,提示 “FDT not found” 或地址错误
原因:设备树加载地址不对,或者fdt_addr_r设置错误。
解决:
检查 U-Boot 环境变量:
printenv fdt_addr_r确保其值与你实际加载.dtb的地址一致,例如:
setenv fdt_addr_r 0x20000000 load mmc 0:1 ${fdt_addr_r} rpi-4-b.dtb🔴 问题2:Kernel Panic,提示 “VFS: Unable to mount root fs”
原因:内核找不到根分区。
检查项:
-root=参数是否正确?比如root=/dev/mmcblk0p2
- 文件系统类型是否匹配?ext4 还是 squashfs?
- 分区是否存在且已格式化?
启动参数示例:
console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootwait earlyprintk🔴 问题3:SSH 登不上,但系统似乎已启动
可能原因:
- SSH 服务未开机自启
- root 用户不允许远程登录
- 防火墙拦截
修复方式:
在 chroot 中启用 SSH 并允许 root 登录:
systemctl enable ssh sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config记得改完重启服务。
性能与生态对比:x64 vs ARM64,谁更适合你?
| 维度 | x86-64 | ARM64 |
|---|---|---|
| 指令集 | CISC,复杂指令,变长编码 | RISC,简洁高效,定长指令 |
| 通用寄存器 | 16 个 | 31 个 64 位寄存器 |
| 功耗效率 | 较高,适合高性能计算 | 极低,适合嵌入式/边缘节点 |
| 多核扩展性 | 成本高,功耗大 | 易实现百核规模(如 Ampere Altra) |
| 生态支持 | 极其成熟,几乎所有软件原生支持 | 快速追赶中,主流发行版均已支持 |
| 编译兼容性 | 默认目标平台 | 需显式指定aarch64-linux-gnu |
✅ 结论:如果你做的是云服务器、桌面应用、传统工控,x64 仍是首选;但若涉及边缘计算、AI 推理盒子、国产替代、低功耗网关,ARM64 已成为不可忽视的技术方向。
实战经验总结:移植成功的五个关键点
永远不要假设二进制兼容
所有程序都必须重新编译,包括你自己写的、第三方库、甚至 Python 模块(如果有 native extension)。设备树是灵魂
很多“外设不工作”的问题,根源都在.dts文件里某个节点status = "disabled"。务必仔细核对每一条总线、每一个引脚复用配置。善用 QEMU 模拟调试
在没有真实硬件时,可以用 QEMU 模拟 ARM64 环境:bash qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic \ -kernel Image -append "console=ttyAMA0" \ -dtb rpi-4-b.dtb -drive file=rootfs.img,format=raw,if=none,id=hd0 \ -device virtio-blk-device,drive=hd0
可大大加快前期验证速度。优先选择已有发行版镜像
初学者不必从零构建。可先用官方 Ubuntu Core for Raspberry Pi 镜像测试功能,再逐步替换内核或 rootfs。保留串口调试信息
使用 TTL 串口模块连接 UART0,观察从 BootROM 到 Kernel 的每一行输出。这是定位早期崩溃的唯一手段。
写在最后:ARM64 不是未来,它已经是现在
华为鲲鹏、飞腾、Ampere、Apple M 系列芯片……越来越多的关键基础设施正在转向 ARM 架构。这场变革不只是“换颗 CPU”,更是整个软件生态的重构。
掌握 Linux 从 x64 到 ARM64 的完整移植能力,意味着你能:
- 快速适配国产化硬件平台
- 构建轻量级边缘容器节点
- 优化嵌入式系统的启动时间与资源占用
- 深入理解操作系统与硬件交互的本质
技术浪潮不会等待任何人。当你还在犹豫要不要学的时候,第一批吃螃蟹的人早已完成了产品落地。
所以,不妨今天就动手试一次:找一块树莓派,搭个交叉编译环境,试着把你写的 Helloworld 移植过去。
你会发现,原来“跨架构”也没那么难。
如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。