第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再重来
小技巧: 如果你不确定当前SDK支持哪些编译目标,直接执行 ./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是否匹配。

注意: 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 到生成最终固件的完整链路:

RK SDK 编译流程总览 ./build.sh 加载 BoardConfig 编译 u-boot uboot.img trust.img / loader.bin 编译 kernel boot.img resource.img (dtb+logo) 编译 rootfs rootfs.img (Buildroot/Debian) 打包固件 (mkimage.sh) update.img (最终固件)

从这张图可以看得很清楚:build.sh 先加载BoardConfig确定目标,然后并行编译u-boot、kernel、rootfs三个模块,最后通过 mkimage.sh 打包成完整的 update.img。每个模块都可以单独编译和调试,这也是RK SDK设计得比较灵活的地方。