给RK3399跑Linux,大多数人习惯把系统直接烧进eMMC或者NVMe,但SD卡启动依然是我做底层调试时最高频的方案。可插拔、可备份、一张卡烧坏了直接换下一张,成本低到可以忽略,而且每次改内核或根文件系统都不用反复擦写板载存储,开发效率能提升一大截。这篇文章记录了我从编译U-Boot、准备Linux内核到生成根文件系统,最终让RK3399在SD卡上完整启动的过程。重点拆解三块:U-Boot相关镜像如何填进SD卡的固定偏移、内核和设备树怎么组织、根文件系统怎么构建并正确挂载。如果你手里正好有RK3399开发板,想绕过SDK图形化烧写工具、自己掌握底层启动链路,这篇应该能帮你少踩一大半的坑。
1. 项目整体设计与启动流程拆解
1.1 为什么选SD卡而不是eMMC或者SPI Flash
RK3399本身支持从多种介质启动,包括eMMC、SD卡、SPI NOR Flash等。在正式项目里,量产板子多半会用eMMC或者SPI Flash,因为可靠性、读写速度和体积都有优势。但做开发调试时,eMMC有个很烦的问题:烧录一次系统要接USB线、装工具、等传输,变砖之后还要进Maskrom模式去恢复。SD卡就简单多了,读卡器插电脑,整个Linux系统就是一个普通文件镜像,随便格式化、重新分区、拷贝文件,出问题大不了重新做一张。
另外,SD卡天然适合做系统备份和多版本切换。比如我调试内核时,会把一个稳定版本放在eMMC里保底,把实验版放到SD卡上,启动时靠拨码开关或者直接拔卡切换介质。这种工作流在纯eMMC方案上很难做到。SD卡还能用一条命令做成可启动镜像,给测试团队分发,极大减少环境差异带来的麻烦。所以,围绕SD卡手工搭建一个完整Linux系统,不只是“能用”,更是嵌入式开发中绕不开的一项基础技能。
1.2 RK3399从按下电源到出现命令行的完整链路
很多初学者卡在同一个问题上:系统文件都放进SD卡了,为什么上电后完全没有反应?原因在于RK3399启动不是简单地从某个分区读取BootLoader,而是分阶段的。
上电后,芯片内部固化在ROM里的BootROM代码会先运行。它会根据启动引脚的电平状态,决定优先从哪个介质读取第一段引导代码。对SD卡这种MMC设备来说,BootROM会到固定的物理偏移位置去找idbloader。这段代码由TPL和SPL组成,主要负责初始化DDR内存、时钟,再跳转到下一级引导程序。RK3399的DDR初始化比较复杂,很多第三方的SPL并不完全可用,所以官方SDK会把TPL和SPL打包成idbloader,放在存储介质的特定偏移处。
之后会加载U-Boot主体和ATF固件。U-Boot负责建立基本外设驱动、读取分区、设置环境变量,根据bootcmd把内核镜像和DTB设备树加载到内存,最后跳转执行Linux内核。内核接管硬件后,会挂载根文件系统的分区,执行init进程,最终弹出登录提示符。这就是一条完整的启动链路:BootROM → idbloader(TPL/SPL) → U-Boot/Trust → Linux内核 → rootfs。
明白这条链路后,做SD卡系统就变成了一件很有条理的事:准备各阶段镜像、按正确偏移写入、准备好文件系统,三者缺一不可。市面上很多“刷了固件还是没反应”的案例,十有八九是引导阶段文件缺失或者偏移错误,而不是内核或rootfs的问题。
2. 硬件连接、工具链与源码准备
2.1 开发板与串口调试基础
做这个项目之前,先把开发环境备齐。我建议用手头任意一款RK3399开发板,连接好以下部件:
- 一张质量可靠的SD卡,容量8GB以上,读写速度至少C10。
- 一个USB转TTL的串口模块,接到开发板的调试串口引脚上。
- 一根USB线用于供电,最好再准备一个支持数据通信的USB口方便后面挂载大容量存储。
- 一台运行Linux的电脑主机,用于编译和烧写。
串口是整个调试环节里最重要的工具。RK3399默认调试串口一般是ttyS2,波特率1500000,具体引脚定义需要看板卡原理图和丝印标注。建议上电前先用串口线连接并打开minicom或picocom,设置波特率到1500000,这样从BootROM开始的所有启动日志都能完整看到。如果波特率设置成115200,日志会变成乱码,这个细节非常容易踩坑。
注意:串口只有用对波特率才能看到完整启动过程,默认的1500000,不是常见的115200。
2.2 交叉编译工具链怎么选
RK3399是AArch64架构,必须在x86主机上用交叉编译器编译ARM64的U-Boot、内核和应用程序。工具链的选择看起来五花八门,实际只有两大类。
第一类是基于Linaro或Arm官方维护的裸机与Linux工具链,例如aarch64-linux-gnu-系列。这类工具链更新频率高,适合编译内核和U-Boot,使用起来比较直接:
export CROSS_COMPILE=aarch64-linux-gnu- export ARCH=arm64第二类是基于Buildroot或Yocto SDK生成的工具链。这类工具链会与具体系统的glibc版本、交叉编译头文件强绑定,适合编译应用层程序,但不适合反客为主去编译内核引导链。如果是首次上手,直接用发行版仓库里的gcc-aarch64-linux-gnu包就行,ubuntu和debian都有现成包。
安装好工具链后,用一行命令验证:
aarch64-linux-gnu-gcc -v能正常输出版本号就说明环境OK,最好再编译一个空的main函数确认链接器工作正常。工具链有时候装了但PATH不对,shell执行时会提示找不到命令,这时候先检查PATH和软链接。
2.3 需要准备哪些源码与固件
核心源码有三个:U-Boot、Linux内核、根文件系统构建工具,另外还需要ATF固件和可选的内核模块。如果是从零开始,我建议直接获取RK3399厂商SDK,它已经把所有源码放到一个目录结构里,U-Boot、kernel、buildroot、device/rockchip目录都在其中,并且各个组件的版本匹配关系也预先测试过。这样能避免自己拼凑不同版本源码时出现的兼容性问题。
如果想用上游主线代码,那就要分别拉取U-Boot官方仓库、Linux内核官方仓库,再单独处理ARM Trusted Firmware的构建。主线的好处是代码干净、社区活跃,但对应RK3399的配置可能没有厂商SDK那么完整,尤其是一些板级外设如HDMI音频、MIPI DSI的初始化节点,需要自己手动补。对新手来说,先用厂商SDK跑通,再迁移到主线是比较合理的路线。
拿到SDK后,先检查里面是否包含需要的固件输出目录。编译脚本通常会在指定目录生成idbloader.img、uboot.img、trust.img、boot.img、rootfs.img等产出物。这些镜像是后面装配SD卡的原材料,先确认能正常编译出来再继续。
3. U-Boot编译与引导部署细节
3.1 U-Boot源码编译的主要步骤
U-Boot的编译在RK3399上相对固定。进入U-Boot源码目录,先切换到目标板对应的配置,再编译:
cd u-boot make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rk3399_defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8编译完成后,会在目录下生成idbloader.img、uboot.img、trust.img等镜像文件。这里要注意,idbloader.img是由TPL和SPL共同打包生成的,如果编译时缺少ATF的二进制,trust.img可能不会生成。缺少trust.img最直接的后果是U-Boot跳转内核前无法完成ARM Trusted Firmware的初始化,系统会在启动早期卡住。
编译过程中最常见的两个错误,一是交叉编译器不匹配导致链接access,二是BL31相关文件缺失。前者切换工具链版本即可,后者需要回到SDK的bin目录或者单独构建ATF,把bl31.elf放到U-Boot可以找到的位置。厂商SDK一般会在编译脚本中自动处理,但自己手动构建时很容易漏掉。
提示:Rocker 平台的ATF参与引导,trust.img必须存在。如果日志停在上电初段不放,先确认SD卡里是否写入了trust.img。
3.2 写入SD卡的起始偏移与分区规划
SD卡不是随便格式化就能启动的。BootROM不识别FAT或者ext4文件系统,它按物理扇区地址去查找idbloader。瑞芯微平台常见的偏移约定是:idbloader写在扇区64的位置,也就是距离卡开头32KB处;uboot.img和trust.img也有固定的偏移量,具体数值看SDK脚本或者parameter分区表。
实际操作中,我习惯先用一张空白的SD卡,用dd把镜像逐个写入:
sudo dd if=idbloader.img of=/dev/sdX bs=512 seek=64 conv=fsync sudo dd if=uboot.img of=/dev/sdX bs=512 seek=16384 conv=fsync sudo dd if=trust.img of=/dev/sdX bs=512 seek=24576 conv=fsync这里/dev/sdX是读卡器映射出来的设备节点,一定要确认是SD卡本身而不是某个分区。seek值代表跳过多少扇区,写入过程中如果跳过位置错误,后续阶段的引导程序就找不到依赖的下一级镜像。
写完引导程序后,再对SD卡进行分区。推荐直接用fdisk或者sgdisk,按照MBR/GPT任意一种都可以,关键是确保第一个分区的起始位置不要覆盖引导程序区域。下面是一个合理的分区方案:
| 分区 | 建议大小 | 文件系统 | 用途 |
|---|---|---|---|
| 未分区预留区 | 至少32MB | 无 | 放置idbloader/uboot.img/trust.img |
| /dev/sdX1 | 512MB | ext4或FAT32 | 存放内核镜像、DTB、uEnv.txt |
| /dev/sdX2 | 剩余全部 | ext4 | 根文件系统 |
第一分区我用ext4比较多,因为U-Boot自带ext4读写驱动,可以读取boot分区里的内核镜像,同时Linux下挂载修改也方便。如果板上的U-Boot版本比较老,对ext4的支持有bug,那再换成FAT32也不迟,用mkfs.vfat格式化即可。
3.3 U-Boot环境变量与启动命令
引导程序烧录到位后,U-Boot运行时还需要知道去哪找内核。这里有两种方式:一是把启动命令写在U-Boot默认环境变量中;二是用uEnv.txt或extlinux.conf这类外部配置文件,让U-Boot启动时动态加载。
我推荐优先使用uEnv.txt放在boot分区。它的好处是改启动参数不重编U-Boot,直接替换SD卡里的文本文件就行。一个典型的uEnv.txt内容如下:
kernel_addr=0x02080000 fdt_addr=0x01f00000 bootargs=console=ttyS2,1500000 root=/dev/mmcblk1p2 rw rootwait loadkernel=load mmc 1:1 ${kernel_addr} Image loadfdt=load mmc 1:1 ${fdt_addr} rk3399.dtb bootcmd=run loadkernel; run loadfdt; booti ${kernel_addr} - ${fdt_addr}如果U-Boot版本不支持uEnv.txt自动解析,可以进U-Boot命令行手动设置并保存:
setenv bootargs 'console=ttyS2,1500000 root=/dev/mmcblk1p2 rw rootwait' setenv bootcmd 'load mmc 1:1 0x02080000 Image; load mmc 1:1 0x01f00000 rk3399.dtb; booti 0x02080000 - 0x01f00000' saveenv boot内存加载地址不是随便填的,需要避开U-Boot自身占用区域,同时满足内核解压要求。RK3399平台上0x02080000和0x01f00000是SDK里常用的取值,如果后续遇到内核启动崩溃,优先检查地址是否被其他数据覆盖。
4. Linux内核编译与启动文件组织
4.1 内核配置的两种来源
内核源码准备好后,首先要选配置。RK3399的官方内核在arch/arm64/configs目录下会有rockchip_linux_defconfig这一类的完整配置,它把需要用到的驱动和核心子系统都开好了,适合直接拿来编译。
如果想精简内核,可以在这个配置基础上用menuconfig裁剪。RK3399的常见外设驱动包括USB 3.0、Type-C、PCIe、GMAC以太网、MIPI DSI、HDMI、I2C、SPI等,调试阶段建议全部保留,等系统稳定了再逐个关闭。裁剪内核不是在SD卡系统这个阶段该做的事情,优先保证能启动、能打印日志、能挂载rootfs。
配置完成后编译:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_linux_defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image dtbs modules -j8三条编译产物:Image是内核镜像,dtbs会在arch/arm64/boot/dts/rockchip目录下生成对应的.dtb文件,modules则生成可安装的内核模块。如果板子上的外设不多,模块可以选择不安装,先把必要的驱动编进内核。
4.2 如何确认DTB设备树匹配
SD卡方案有个容易忽略的坑:同一个RK3399芯片,不同板卡的设备树文件并不通用。因为GPIO复用、电源管理、外部PHY型号、屏幕接口都可能不同,直接套用某个开发板的DTB,轻则某些外设无法工作,重则内核在启动早期就panic。
通常的做法是查看SDK中与板卡对应的dts文件,例如rk3399-xxx-board.dts,编译后生成同名dtb。把dtb文件改名为rk3399.dtb放进boot分区是个人习惯,只要U-Boot加载时指定改名后的文件即可。如果对板卡对应的DTB不熟悉,可以用设备树源文件里的model字段来识别,比如在dts开头看到“Model:xxx RK3399 Board”字样,就能对号入座。
提示:内核崩在earlycon阶段时,先确认DTB里的chosen、psci、serial节点是否正常,尤其检查serial的reg地址与板卡实际串口是否一致。
4.3 boot分区里到底要放哪些文件
第一次整理boot分区时,我总想把所有文件都放进去,结果反而因为命名混乱导致U-Boot加载不到。后来我形成了固定目录结构,只用几个必要文件:
- Image:编译出来的内核镜像。
- rk3399.dtb:实际生效的设备树文件。
- uEnv.txt或extlinux/extlinux.conf:启动配置。
- 可选ramdisk:当根文件系统需要initramfs时使用,大多数SD卡场景直接挂载块设备可以不用。
如果内核里某些驱动被编译成模块,还需要把模块安装目录lib/modules放到根文件系统的对应位置,而不是放在boot分区。boot分区只做启动早期引导数据存放,根文件系统里的/lib/modules才是内核模块的完整归宿。
5. 根文件系统的构建与系统初始化
5.1 Buildroot与发行版Rootfs的取舍
根文件系统是SD卡Linux系统中最灵活也最容易出问题的部分。操作思路分两种方向,一种是用Buildroot定制最小文件系统,另一种是直接把通用发行版rootfs解压到SD卡分区。
Buildroot的优势是可控、体积小。我可以只选择需要的busybox命令、网络工具和运行时库,最终生成一个几十MB的根文件系统,特别适合功能明确的产品原型。缺点是编译耗时,碰到需要调试但如果某些软件包配置错误,从头再跑一遍比较费时。
发行版rootfs的优势是软件生态完整,包管理器、编译工具链、常用服务都现成。做开发板时,我常常直接使用Debian或Ubuntu的ARM64 rootfs压缩包,解压出来就是一个能用的Linux环境。缺点是体积大,首启动初始化过程偶尔依赖systemd特性,对老旧内核会有兼容问题。
我的建议是:调试内核早期阶段用Buildroot,搞应用开发和跑复杂软件时用发行版rootfs。构建系统本质上服务于开发效率,不用一根筋。
5.2 生成一个可用最小Rootfs
以Buildroot为例,配置RK3399架构并编译生成rootfs.ext4:
make rockchip_rk3399_defconfig make -j8完成后输出目录下会生成rootfs.ext4等一系列文件。这个rootfs的最小内容已经包含/bin/sh、词法工具、网络工具等,可以保证系统进入shell后还能做基本操作。
如果手头没有Buildroot,也可以直接下载现有发行版ARM64 Rootfs压缩包。解压步骤是:
sudo mkdir -p /mnt/sdroot sudo tar -xzf ubuntu-base-arm64.tar.gz -C /mnt/sdroot解压时注意目录权限,tar命令如果不加sudo,很容易导致etc目录下某些配置文件属主错误,后续系统初始化和用户管理会出现莫名问题。
解压完成后,必须做几个初始化配置:
- 设置root密码。
- 配置/etc/fstab。
- 启用串口getty。
- 配置网络接口。
其中/etc/fstab是启动后能否顺利挂载文件系统的关键,至少包含:
/dev/mmcblk1p2 / ext4 defaults 0 1 /dev/mmcblk1p1 /boot ext4 defaults 0 2如果根文件系统分区不是mmcblk1p2,在生产环境里可以用UUID或者PARTUUID来替代设备节点,防止插入不同读卡器后设备名变动导致挂载失败。
5.3 把Rootfs写入SD卡分区
rootfs准备好后,不能简单地把文件直接cp到分区了事,要做成文件系统镜像再写入更稳妥,这样文件所有者和inode信息都不会丢。
先查看SD卡的第二个分区是否已经格式化:
sudo mkfs.ext4 -L rootfs /dev/sdX2然后挂载这个分区,把rootfs内的所有文件同步进去:
sudo mount /dev/sdX2 /mnt/sdroot sudo cp -a /path/to/rootfs/. /mnt/sdroot/ sudo umount /mnt/sdroot这里cp的-a参数非常关键,它保留符号链接、权限和扩展属性。如果不用-a,后续启动时会看到各种“permission denied”,尤其是在动态链接器和用户名认证阶段,排查起来非常痛苦。
如果Buildroot生成的是rootfs.ext4镜像文件,则可以直接写块设备:
sudo dd if=rootfs.ext4 of=/dev/sdX2 bs=1M conv=fsync写完rootfs后,再把内核模块用同样的方式拷入:
sudo mount /dev/sdX2 /mnt/sdroot sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_install INSTALL_MOD_PATH=/mnt/sdroot sudo umount /mnt/sdroot这一步会让/lib/modules下生成与内核版本一致的模块目录,后续插入USB网卡、WiFi模块等设备时,modprobe才找得到驱动。
6. 一张可启动SD卡的完整装配记录
6.1 从空卡到启动盘的逐条命令
下面这段是整套流程的汇总版本,可以当作检查清单。实际操作时,我会把每步用到的镜像文件和rootfs路径提前准备好:
# 1. 清空SD卡分区表 sudo wipefs -a /dev/sdX # 2. 写入引导程序,偏移必须从扇区64开始 sudo dd if=idbloader.img of=/dev/sdX bs=512 seek=64 conv=fsync sudo dd if=uboot.img of=/dev/sdX bs=512 seek=16384 conv=fsync sudo dd if=trust.img of=/dev/sdX bs=512 seek=24576 conv=fsync # 3. 创建分区 sudo fdisk /dev/sdX # fdisk内操作:新建分区1起始位置203264扇区,大小512MB;剩余空间建分区2 # 写入分区表类型w退出 # 4. 格式化 sudo mkfs.ext4 -L boot /dev/sdX1 sudo mkfs.ext4 -L rootfs /dev/sdX2 # 5. 写入boot分区文件 sudo mount /dev/sdX1 /mnt/boot sudo cp Image rk3399.dtb uEnv.txt /mnt/boot/ sync sudo umount /mnt/boot # 6. 写入rootfs sudo mount /dev/sdX2 /mnt/root sudo cp -a /path/to/rootfs/. /mnt/root/ sync sudo umount /mnt/root这里分区起始位置定在203264扇区,也就是大约100MB的位置,这个数值比引导程序所需空间大出很多,给后续镜像偏移调整留了余量。如果你的SDK引导程序包含其他loader或reserved分区,可以先看看parameter文件,把分区起始位置往后挪。
完成之后,把SD卡从读卡器拔出再重新插上,用磁盘管理工具确认分区表正常,然后插入开发板,连接串口,上电观察日志。
6.2 首启动日志验证的关键点
一张卡能不能正常启动,不需要等系统起来,看串口日志就足够了。理想情况下,启动日志应该依次出现以下几个阶段:
- BootROM阶段:显示DDR初始化相关信息,对应idbloader执行成功。
- U-Boot阶段:出现U-Boot版本号和编译时间,然后加载uEnv.txt或执行bootcmd。
- Kernel阶段:出现Linux版本信息、内存分布、设备树载入信息。
- Rootfs阶段:出现VFS挂载信息和init进程启动日志,最终弹出登录提示。
如果第一个阶段都过不去,问题大概率在引导程序写入偏移或镜像完整性上,而不是分区和rootfs。如果卡在U-Boot等待内核加载,则检查boot分区文件命名、U-Boot命令中的设备mmc 1:1是否对应当前插入的SD卡,以及内存地址是否可用。
有个小经验:串口日志看不到时,先测一下串口模块是否接对了TXD和RXD,波特率设为1500000后用手短接TXD和RXD,看能否收到回显。这个操作能帮助区分硬件链路问题和软件引导问题。
7. 常见问题、定位思路与细节优化
7.1 BootROM阶段反复重启或直接没反应
这个阶段失败,屏幕上通常只有重复的DDR初始化信息或者干脆没有任何输出。原因主要集中在以下三个方向:
| 现象 | 可能原因 | 处理思路 |
|---|---|---|
| 无任何串口输出 | 没有供电、串口接错、idbloader缺失 | 核实串口接线,检查dd的seek值 |
| 反复打印DDR初始化失败 | DDR布线不稳或TPL配置与实际内存型号不匹配 | 检查SDK默认内存配置,换成支持的DDR频率 |
| BootROM提示找不到启动设备 | SD卡接触不良、卡格式损坏 | 重新插拔,wipefs后再写入 |
这类问题根因大多是idbloader没有正确写到扇区64。检查方法很简单:把SD卡插到Linux主机,运行dmesg确认设备节点,再用hexdump查看偏移处的二进制内容是否有明显的镜像头:
sudo dd if=/dev/sdX bs=512 skip=64 count=16 | hexdump -C能看到GUID分区表信息是正常的,如果这里是全零或者文件系统魔数,那基本就写错位置了。
7.2 U-Boot能启动,但内核无法加载或启动即崩溃
U-Boot成功进入命令行后,首先执行bootcmd。如果日志停在Loading kernel或者跳转内核后无任何输出,优先排查几项。
检查boot分区里的Image文件是否完整,可以用md5sum对比源文件。U-Boot从SD卡读取时如果报错,很可能是分区文件系统损坏,重新拷入并做sync。另一个隐蔽问题是内存加载地址错误。U-Boot实际可用内存范围以日志中DRAM size为准,有些板卡内存大小不同,加载地址在裁剪内存之上会直接导致引导失败。
内核启动后崩溃,多半是DTB和设备不匹配。举个例子,从某个开发板拷贝的rk3399.dtb用在自己的板卡上,内核初始化到GPIO或PMIC时会卡在类似“Unable to handle kernel NULL pointer dereference”的位置。此时用dts和官方硬件原理图核对,确认电源管理单元、串口、PMIC这些启动早期必须初始化的节点是否与板卡一致。
7.3 Kernel起来了,但挂载不了Rootfs
日志卡在“VFS: Unable to mount root fs”或反复打印“Waiting for root device”,这是SD卡系统制作里另一个高频场景。
首先要检查内核命令行里的root参数,常见的错误是把/dev/mmcblk0p2写成/dev/mmcblk1p2,或者反过来。RK3399平台内嵌eMMC和SD卡被分配的主设备号不同,eMMC通常是mmcblk0,SD卡是mmcblk1。如果从eMMC启动习惯了,切到SD卡后很容出错。
其次是确认rootfs分区的文件系统没有损坏,用fsck检测一遍。如果内核里对应的ext4驱动没有编译进去,同样无法挂载。这时可以把rootfs制作成initramfs挂载方式调试,或者用核心自带initramfs加载后手动mount排查。
排障建议:在bootargs里临时加上init=/bin/bash跳过系统初始化,如果这样能进入shell并手动mount成功,说明rootfs本体没问题,问题出在初始化脚本或systemd配置。
7.4 进入Shell后串口没有登录提示符
这种情况最迷惑人,因为系统已经起来了,内核日志都在,但就是没有login提示。多数原因不是内核问题,而是没有启用串口终端服务。
在systemd环境下,创建软链接启用对应的串口服务:
ln -s /lib/systemd/system/serial-getty@.service \ /etc/systemd/system/getty.target.wants/serial-getty@ttyS2.service对于采用busybox init的系统,检查/etc/inittab是否包含对应条目:
ttyS2::respawn:/sbin/getty -L ttyS2 0 vt100修改完记得sync并重做rootfs写入。这个操作很容易遗漏,因为我第一次做RK3399 SD卡系统时,内核日志一切正常,却卡在“没有登录提示”这一步,白白排查了很久。
7.5 引导稳定后,让SD卡系统更快更可靠
系统能稳定启动之后,可以做一些优化。第一是把ro参数加到内核命令行,根文件系统以只读方式挂载,对防止异常断电导致的文件系统损坏很有帮助。调试阶段不建议这样做,但一旦进入正式验证,ro是标配。第二是在/etc/fstab中使用UUID而不是设备节点,这样无论SD卡被识别成mmcblk0还是mmcblk1。
第三,给boot分区做一个备份镜像。SD卡系统里,uEnv.txt和内核镜像经常被修改,在主机上保留一个boot.tar.gz非常必要:
sudo tar -czf boot-backup.tar.gz -C /mnt/boot .真遇到SD卡损坏,插一张新卡,分区后重新解压引导文件,整个过程不超过五分钟。相比重新编译U-Boot和内核,体验完全不一样。
结尾
做了几张RK3399的SD卡启动系统之后,我最大的感受是,嵌入式底层开发最忌讳“认为某个环节理所当然”。U-Boot要写在哪一个偏移、内核用什么加载地址、rootfs里缺了哪个服务,每一个细节都会让整条链路失效。建议大家第一次做的时候,别急着用一键烧写脚本,而是手动敲一遍dd和分区命令,亲眼看看每个阶段日志的变化,这样即使后面换成其他平台,也能快速迁移思路。最后留一句提醒:SD卡介质寿命有限,频繁读写会掉速,编译内核时多做几个boot分区备份,真正碰到启动卡顿的时候,节省下来的是连续几个小时的排查时间。