将 fnOS 从 eMMC/TF 卡无损迁移至外部存储(NVMe/USB/SATA/TF)的完整方案 —— 适用于瑞芯微 RK 系列平台(含小容量盘适配)
将 fnOS 从 eMMC 无损迁移至 NVMe SSD日常用 ARM 设备总习惯把固件刷进 eMMC 或者 TF 卡。eMMC 读写慢寿命有限用久了总觉得差口气。我手头有块 NanoPC-T4给它刷了 Arm 飞牛固件简单体验了一下就琢磨要是能把系统挪到 NVMe 上应该会爽很多。NanoPC-T4 正好有个 NVMe M.2 插槽把系统装到外部存储上就能绕过 eMMC 这个瓶颈。我去翻了友善官方的eflasher-multiple-os固件说明它确实可以把根文件系统写到 NVMe 或 USB 设备里但我不确定它能不能兼容飞牛的 rootfs。而且官方固件内核太老了4.19新特性和 Docker 完整支持都享受不到。好在 ARM 设备的引导思路是相通的自己研究了一下把飞牛系统迁移到了 NVMe 硬盘上。下面就是完整的折腾记录。先搞清楚 ARM 板子是怎么启动的以 RK3399 为例芯片内部固化了一段不可更改的启动 ROMBootROM。上电后BootROM 会按顺序扫描可启动设备一般是 SD 卡 → eMMC → SPI Flash找到有效的 U-Boot 就加载执行。U-Boot 负责初始化内存、时钟等然后从某个设备通常是 eMMC 或 SD 卡的分区加载内核kernel和设备树dtb。内核跑起来之后再根据root参数去挂载根文件系统。关键点RK3399 的 BootROM 不认识 NVMe。U‑Boot 和内核镜像必须放在 eMMC 或 SD 卡上但根文件系统可以放在任何内核能驱动的地方——当然也包括 NVMe。顺着这个思路整个方案可以拆成两步准备 rootfs在 NVMe 上建好分区把 eMMC 上的根文件系统完整复制过去并处理好 UUID 冲突。修改 PARTUUID修改 eMMC 上的内核引导参数让系统从 NVMe 的 PARTUUID 启动。基于这个原理我们决定用“eMMC 引导 NVMe 系统”的分离架构eMMC只放引导文件U‑Boot、内核、DTB几乎只读寿命无限NVMe放完整的根文件系统所有读写都在这儿享受高速低延迟这个方案既能绕过 RK3399 的引导限制又能把 eMMC 的寿命省下来同时把 NVMe 的性能用满。而且这种“引导介质与 rootfs 分离”的思路并不局限于 NVMe——如果 eMMC 损坏了或者板子根本没有 eMMC甚至你想把系统挪到 USB 3.0 硬盘、SATA 盘、另一张 TF 卡上本质都是一样的只要 BootROM 能从某个介质比如 TF 卡把 U‑Boot 和内核拉起来内核就能从任何它认得到的设备上挂载 rootfs。后面你会看到我们只改了一个rootPARTUUID参数这个参数指向哪里系统就从哪里启动。我手里的东西开发板NanoPC-T4RK3399引导盘板载 eMMC14.6 GB系统盘Intel Optane M10 16GB NVMe实际可用只有 13.4 GB系统fnOS 1.1.24内核Linux 6.12.41文件系统Btrfs引导方式U-Boot 脚本boot.scrfnEnv.txt注意本文所有操作都假设 NVMe 设备名为nvme0n1。你的可能是nvme1n1或别的请用lsblk确认后替换。一上来就碰了个硬钉子量了一下傲腾 M10 虽然标称 16GB实际可用只有 13.4 GB而 eMMC 上的根分区占了 14.1 GB。用dd直接克隆肯定不行目标盘比源盘还小得换个思路——不能用块设备克隆只能用文件级复制挑着有用的文件搬过去。一步步开干1. 先看看设备lsblk我的输出rootNanoPC-T4:/# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS mmcblk2179:0014.6G0disk ├─mmcblk2p1179:10285M0part /boot └─mmcblk2p2179:2014.1G0part / mmcblk2boot0179:3204M1disk mmcblk2boot1179:6404M1disk zram0252:001.9G0disk[SWAP]nvme0n1259:0013.4G0disk └─nvme0n1p1259:1013.4G0part └─md09:0013.4G0raid1 └─trim_08a0334e_a652_40cb_a25e_151bd2290a7e-0253:0013.4G0lvm注意我的傲腾 M10 之前在飞牛上挂载过残留了 LVM 和 RAID 的配置得先清理干净。2. 清理 NVMe 上的旧配置如果不清理干净后面分区格式化会出各种奇怪问题——比如明明已经分区了但lsblk还是能看到旧的分区结构。# 强制卸载所有可能相关的挂载点根据你的实际情况改umount-l/vol12/dev/null||trueumount-l/dev/md02/dev/null||trueumount-l/dev/nvme0n1p12/dev/null||true# 停止 LVM 和 RAID 服务替换成你实际的 LVM 名称lvchange-an-fftrim_08a0334e_a652_40cb_a25e_151bd2290a7e-02/dev/null||truemdadm--stop/dev/md02/dev/null||truedmsetup remove trim_08a0334e_a652_40cb_a25e_151bd2290a7e-02/dev/null||true# 擦除分区表头扇区让内核认为这盘是空的ddif/dev/zeroof/dev/nvme0n1bs512count100convfsync# 通知内核重新读取分区表partprobe /dev/nvme0n1执行完后lsblk应该只看到一个光秃秃的nvme0n1rootNanoPC-T4:/# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS mmcblk2179:0014.6G0disk ├─mmcblk2p1179:10285M0part /boot └─mmcblk2p2179:2014.1G0part / mmcblk2boot0179:3204M1disk mmcblk2boot1179:6404M1disk zram0252:001.9G0disk[SWAP]nvme0n1259:0013.4G0disk3. 确认 eMMC 上的文件系统类型blkid /dev/mmcblk2p2我得到rootNanoPC-T4:/# blkid /dev/mmcblk2p2/dev/mmcblk2p2:LABELrootfsUUID48b0a7cb-68bc-4337-b9ad-fc605dbb31bfUUID_SUBf29b6a03-37ca-4e05-987e-a2b5d5e05154BLOCK_SIZE4096TYPEbtrfsPARTUUIDafe747ae-13c5-4540-8cd0-94fb975662e5是 Btrfs记下这个 UUID后面要用。4. 给 NVMe 分区第一步准备 rootfs 的开始# 彻底清一下残留元数据wipefs-a/dev/nvme0n1 partprobe /dev/nvme0n1# 创建 GPT 分区表parted/dev/nvme0n1 mklabel gpt# 输入 yes 确认# 创建 Boot 分区285MB和 eMMC 保持一致parted/dev/nvme0n1 mkpart primary ext4 1MiB 286MiB# 创建 Root 分区占满剩余空间(此时建立了一个ext4分区,后面会格式化成btrfs)parted/dev/nvme0n1 mkpart primary ext4 286MiB100%# 刷新分区表partprobe /dev/nvme0n1# 检查分区结果lsblk /dev/nvme0n15. 格式化# Boot 分区用 ext4兼容性最好mkfs.ext4 /dev/nvme0n1p1# Root 分区用 btrfs和源系统一致mkfs.btrfs-f-Lrootfs_nvme /dev/nvme0n1p2格式化完顺手记一下新分区的 UUID 和 PARTUUIDblkid /dev/nvme0n1p26. 挂载并复制数据继续准备 rootfs# 创建临时挂载点mkdir-p/mnt/dst_boot /mnt/dst_root# 挂载目标分区mount/dev/nvme0n1p1 /mnt/dst_bootmount/dev/nvme0n1p2 /mnt/dst_root# 检查空间够不够df-h/# 源已用空间df-h/mnt/dst_root# 目标可用空间# 必须确保 源已用 目标可用确认空间足够后开始复制。因为目标盘比源盘小不能用dd只能用rsync做文件级复制。# 复制 Boot 分区rsync-avHAX/boot/ /mnt/dst_boot/# 复制根文件系统排除虚拟文件系统和外部挂载点rsync-avHAX\--exclude/proc/*\--exclude/sys/*\--exclude/dev/*\--exclude/run/*\--exclude/mnt/*\--exclude/tmp/*\--exclude/lostfound/*\--exclude/vol*/\--exclude/media/*\/ /mnt/dst_root/等几分钟看到类似这样的输出就成功了sent5,244,244,907 bytes received1,572,046 bytes62,080,674.00 bytes/sec total size is5,241,423,994 speedup is1.007. 修改 NVMe 分区的 UUID关键步骤属于准备 rootfs 的收尾现在遇到一个新问题我们用rsync把文件原样复制过去了但 Btrfs 文件系统的 UUID 也被原样复制了。也就是说NVMe 上的新 rootfs 和 eMMC 上的旧 rootfs 有完全相同的 UUID。内核在挂载时看到两个相同 UUID 的 Btrfs 卷就会混乱——它不知道该用哪一个。所以必须给 NVMe 上的 rootfs 生成一个新的 UUID。先卸载 root 分区btrfstune要求分区不能处于挂载状态umount/mnt/dst_root然后生成新 UUIDbtrfstune-u/dev/nvme0n1p2工具会问你是否确认输入yNew fsid: ff743428-987f-4649-b87e-3a94a65c94c6 Set superblock flag CHANGING_FSID Change fsidinextent tree Change fsidinchunk tree Clear superblock flag CHANGING_FSID Fsid change finished记录下新旧 UUID 和 PARTUUIDOLD_UUID$(blkid-sUUID-ovalue /dev/mmcblk2p2)NEW_UUID$(blkid-sUUID-ovalue /dev/nvme0n1p2)NEW_PARTUUID$(blkid-sPARTUUID-ovalue /dev/nvme0n1p2)echo旧 UUID (eMMC):$OLD_UUIDecho新 UUID (NVMe):$NEW_UUIDecho新 PARTUUID (NVMe):$NEW_PARTUUID我的输出旧 UUID(EMMC): 48b0a7cb-68bc-4337-b9ad-fc605dbb31bf 新 UUID(NVMe): ff743428-987f-4649-b87e-3a94a65c94c6 新 PARTUUID(NVMe): 871c986b-5385-45ae-a241-2e875a3ecc438. 修改 NVMe 里的 /etc/fstab让新 rootfs 引用自己的 UUIDNVMe 上的 rootfs 里还有一个/etc/fstab文件它里面写的是旧的 UUID指向 eMMC。如果不改即使内核从 NVMe 启动了后续挂载/时又会因为 UUID 不匹配而出错。所以得把它也换成新的 UUID。重新挂载 NVMe 的 root 分区mount/dev/nvme0n1p2 /mnt/dst_root备份并替换 UUIDcp/mnt/dst_root/etc/fstab /mnt/dst_root/etc/fstab.baksed-is/$OLD_UUID/$NEW_UUID/g/mnt/dst_root/etc/fstab验证一下cat/mnt/dst_root/etc/fstab应该看到/分区已经指向新的 UUIDrootNanoPC-T4:/# cat /mnt/dst_root/etc/fstab.......UUIDff743428-987f-4649-b87e-3a94a65c94c6 / btrfs defaults,noatime,errorsremount-ro01UUIDcf2ecdac-946c-4790-a894-19c10b526a1a /boot ext4 defaults,noatime,errorsremount-ro02tmpfs /tmp tmpfs defaults,nosuid00确认无误后卸载umount/mnt/dst_rootumount/mnt/dst_boot至此第一步“准备 rootfs”完成NVMe 上已经有了一个独立、可用的根文件系统UUID 也改好了。9. 修改 eMMC 上的引导配置第二步修改 PARTUUID现在NVMe 上的系统已经准备好了但内核还不知道要去 NVMe 上找 root。因为 RK3399 的 BootROM 只认识 eMMC/SD 卡所以我们还是从 eMMC 启动内核但要让内核把根文件系统挂载到 NVMe 上。这就需要修改内核启动参数。另外如果直接使用文件系统的 UUID 来指定 root可能会遇到问题在早期引导阶段内核不一定能正确解析 Btrfs 的 UUID尤其当有多个相同类型的文件系统时。更可靠的办法是用 PARTUUID——这是分区表的属性在分区创建时就固定了不会因为文件系统格式化而改变而且内核在很早期的阶段就能识别它。编辑 eMMC 上的/boot/fnEnv.txtvim/boot/fnEnv.txt原来的内容大概是这样verbosity1bootlogofalseconsolebothextraargscma256Mfdtfilerockchip/rk3399-nanopc-t4.dtbkernelfilevmlinuz-6.12.41-trim我们需要在extraargscma256M后面追加rootPARTUUIDxxxx-xxxx-xxxx-xxxx。注意PARTUUID后面不要加双引号cma256M和root...之间必须有一个空格。修改后verbosity1bootlogofalseconsolebothextraargscma256MrootPARTUUID871c986b-5385-45ae-a241-2e875a3ecc43fdtfilerockchip/rk3399-nanopc-t4.dtbkernelfilevmlinuz-6.12.41-trim为什么用 PARTUUID 而不是 UUIDPARTUUID 是分区表的属性不会因为文件系统重新格式化而改变在早期引导阶段更可靠。而且这个rootPARTUUID参数是通用的——如果你的 eMMC 坏了你可以把同样的参数指向另一张 TF 卡上的根分区如果你想用 USB 3.0 硬盘启动只要内核能识别该硬盘改一下 PARTUUID 就行。整个方案的精髓就在这一行。10. 重启验证建议再检查一遍/boot/fnEnv.txt里的 PARTUUID 是否和blkid /dev/nvme0n1p2输出一致。syncreboot验证一下是否真的跑在 NVMe 上重启后登录系统执行df-hT/ lsblkmount|grep / 我的结果可以看到/已经挂载在/dev/nvme0n1p2上了eMMC 的mmcblk2p2没有被使用。性能测试傲腾到底有多快Intel Optane M10 是一块很有意思的小硬盘。它的容量不大16GB但用的是 3D XPoint 介质延迟极低读写磨损均衡做得特别好。普通 TLC 固态的 4K 随机读写通常只有几万 IOPS而傲腾可以轻松跑到六位数。在 NanoPC-T4 上虽然 PCIe 2.0 x4 的带宽限制了顺序读写理论上限约 1.6 GB/s实际更少但傲腾真正的杀手锏——4K 随机读写和超低延迟——几乎不受影响。跑数据库、Docker 容器、频繁读写小文件的场景这块小傲腾能带来脱胎换骨的体验。先装fiosudoaptupdatesudoaptinstallfio-y然后跑几个测试# 4K 随机读fio--namerandread--ioenginelibaio--iodepth1--rwrandread--bs4k--direct1--size256M--numjobs1--runtime60--group_reporting--filename/tmp/fio_test_read# 4K 随机写fio--namerandwrite--ioenginelibaio--iodepth1--rwrandwrite--bs4k--direct1--size256M--numjobs1--runtime60--group_reporting--filename/tmp/fio_test_write# 混合读写70% 读 30% 写典型服务器负载fio--namemixed--ioenginelibaio--iodepth4--rwrandrw--rwmixread70--bs4k--direct1--size256M--numjobs2--runtime60--group_reporting--filename/tmp/fio_mixed# 清理rm/tmp/fio_test_* /tmp/fio_mixed_*2/dev/null我的测试结果测试项目速度/性能延迟评价顺序写入611 MB/s-吃满 PCIe 2.0 带宽顺序读取800 MB/s-含内存缓存加速4K 随机读137,000 IOPS6.16 μs企业级性能4K 随机写135,000 IOPS6.13 μs读写几乎一致混合读写读 181k / 写 77.8k IOPS29 μs低负载依然极低延迟傲腾在 RK3399 上跑出了 13 万 IOPS延迟只有 6 微秒——系统响应飞快Docker 容器启动也明显流畅了。这个方案能走多远回过头看这次迁移的核心其实就两步先在目标盘上准备好一个独立的根文件系统再改一下 eMMC 上fnEnv.txt里的rootPARTUUID参数。这个思路并不绑定 NVMe也不依赖 eMMC 必须完好。引导介质可以是 eMMC也可以是 TF 卡。万一哪天 eMMC 彻底写坏了只要把同样的引导文件U‑Boot、内核、dtb和fnEnv.txt拷到一张 TF 卡上插上去就能照样启动。而rootPARTUUID后面那个参数你想指向哪里就指向哪里——NVMe 可以USB 3.0 硬盘可以SATA 盘可以甚至另一张 TF 卡也可以。只要内核能驱动那个设备系统就能从那儿跑起来。所以这套方法不仅救活了我这块小容量傲腾也适用于任何想把瑞芯微 ARM 设备的系统从慢速存储迁到高速外部介质的场景。如果你的 eMMC 还没坏它可以作为提速方案如果已经坏了它更是一条复活路径。LECREATE2026 年 3 月 22 日
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2511647.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!