第4章 RK SDK编译入门:整体编译流程
好,咱们今天来聊聊RK平台SDK的编译。说实话,我第一次接触RK的编译系统时,也被那一堆脚本和配置文件搞得有点懵。但摸清楚套路后,你会发现它其实挺清晰的。
这一章,我会带着你从整体到局部,把编译这件事彻底搞明白。
4.1 整体编译流程:./build.sh
RK SDK的编译入口,就是根目录下的 build.sh 脚本。你想想看,一个项目里那么多模块——u-boot、kernel、rootfs、甚至还有分区表,总不能一个个手动去编译吧?build.sh 就是那个总指挥。
最简单的用法,直接在SDK根目录执行:
./build.sh
它会按照默认配置,把整个固件从头到尾编译一遍。我个人习惯在第一次编译时,先不加任何参数,看看能不能完整跑通。这就像新拿到一块开发板,先点个灯确认硬件没问题一样。
但实际开发中,我们很少需要全量编译。比如你只改了驱动,就没必要重编整个rootfs。build.sh 支持很多子命令,我列几个常用的:
| 命令 | 作用 | 我常用的场景 |
|---|---|---|
./build.sh uboot |
只编译u-boot | 修改了DDR参数或启动时序后 |
./build.sh kernel |
只编译内核 | 调试驱动、修改设备树时 |
./build.sh rootfs |
只编译文件系统 | 添加应用层库或修改init脚本 |
./build.sh firmware |
打包固件(不重新编译) | 只调整分区大小或打包参数时 |
./build.sh clean |
清理所有编译产物 | 遇到奇怪的编译错误,先clean再重来 |
./build.sh help,它会列出所有可用的子命令。这个比翻文档快多了。
4.2 选择编译目标:BoardConfig
你可能会问:同一个SDK,怎么知道我要编译的是RK3588还是RK3566?答案就在 BoardConfig 文件里。
BoardConfig 本质上是一个环境变量配置文件。它定义了芯片型号、DDR容量、屏幕分辨率、分区表等关键参数。SDK里通常会预置很多现成的配置,放在 device/rockchip/ 目录下。
举个例子,假设你要编译RK3588 EVB1开发板的固件:
./build.sh device/rockchip/rk3588/rk3588-evb1-lp4-v10-linux.mk
这条命令会加载对应的BoardConfig,然后开始编译。加载完成后,你可以用 source 命令查看当前生效的配置:
source device/rockchip/rk3588/rk3588-evb1-lp4-v10-linux.mk
echo $RK_TARGET_PRODUCT
echo $RK_KERNEL_DTS
我曾经在项目里踩过一个坑:同事A用EVB1的配置编译了固件,同事B用EVB2的配置编译,结果两人烧录后,一块板子屏幕亮了,另一块死活不亮。最后发现是BoardConfig里配置的MIPI屏幕参数不一样。所以,拿到新板子第一件事,确认BoardConfig是否匹配。
RK_KERNEL_DTS,它指定了内核设备树文件。如果这个值设错了,内核可能无法正确初始化外设,甚至无法启动。我建议你在修改BoardConfig后,先 echo 一下关键变量确认。
4.3 编译u-boot
u-boot 是系统上电后第一个运行的软件。它的任务很简单:初始化硬件、加载内核到内存、然后跳转过去。但简单不代表不重要——u-boot 一旦出问题,板子就是一块砖。
编译u-boot的命令很直接:
./build.sh uboot
执行后,脚本会进入 u-boot/ 目录,执行 make 命令。编译产物通常包括:
- uboot.img:u-boot 本体镜像
- trust.img:ARM Trusted Firmware(ATF)镜像,负责安全启动
- loader.bin:一级引导程序,由Rockchip的miniloader生成
我个人习惯在修改u-boot源码后,只编译uboot模块,然后用烧录工具单独烧写 uboot.img 来验证。这样比全量编译快得多,调试效率高。
这里有个经验:如果你改了DDR频率或时序,一定要重新编译trust.img。因为ATF里包含了DDR初始化代码,光更新uboot.img是不够的。我曾经在这个问题上浪费了半天,后来才反应过来。
4.4 编译kernel
内核编译是开发中最频繁的操作。毕竟驱动、设备树、内核配置,这些都在kernel里。
编译内核同样简单:
./build.sh kernel
它会自动配置内核(使用BoardConfig里指定的defconfig),然后编译内核镜像和设备树。编译产物在 kernel/ 目录下:
- boot.img:包含内核镜像和根文件系统ramdisk
- resource.img:包含设备树文件(.dtb)和logo图片
你想想看,如果只改了设备树,其实没必要重新编译整个内核。我通常的做法是:
# 只编译设备树
make dtbs
# 然后手动打包resource.img
./mkimage.sh resource.img
这样能节省大量时间。特别是调试阶段,改一次设备树就要等3分钟全量编译,那效率太低了。
boot.img 和 resource.img 是否生成。如果编译过程中报错,先检查 kernel/ 目录下的 .config 文件是否与BoardConfig匹配。不匹配的话,执行 ./build.sh kernel-config 重新配置。
4.5 编译rootfs
rootfs 就是根文件系统,它包含了系统运行所需的所有库、工具和配置文件。RK SDK支持多种rootfs类型:Buildroot、Debian、Yocto等。默认情况下,BoardConfig里会指定用哪一种。
编译rootfs:
./build.sh rootfs
这个命令会根据BoardConfig里的配置,调用对应的构建系统。比如Buildroot会下载源码包、交叉编译、然后打包成 rootfs.img。
我第一次编译Buildroot时,等了将近一个小时。后来发现是因为网络问题,下载源码包太慢。我的建议是:第一次编译rootfs前,先把所有依赖包下载好。SDK里通常有个 dl/ 目录,你可以手动把源码包放进去,避免编译时卡在下载环节。
另外,rootfs编译产物很大,动辄几百MB。如果你只是修改了某个应用层程序,完全没必要重编整个rootfs。我通常的做法是:
- 在Buildroot配置中启用
BR2_PACKAGE_HOST_SDK,这样编译出来的工具链可以单独使用 - 修改应用后,用交叉编译工具链单独编译,然后通过NFS挂载或adb push到板子上测试
- 确认没问题后,再重新打包rootfs
嗯,这里要注意:不同rootfs类型的打包方式不一样。Buildroot生成的是 rootfs.img(ext4格式),Debian生成的是 rootfs.ext4。但最终烧录时,它们都会被写入到对应的分区。
4.6 本章知识体系
为了让你更直观地理解整个编译流程,我画了一张图。它展示了从执行 build.sh 到生成最终固件的完整链路:
从这张图可以看得很清楚:build.sh 先加载BoardConfig确定目标,然后并行编译u-boot、kernel、rootfs三个模块,最后通过 mkimage.sh 打包成完整的 update.img。每个模块都可以单独编译和调试,这也是RK SDK设计得比较灵活的地方。