4、编译系统入门:SDK一键编译脚本解析,单独编译Uboot、Kernel、Rootfs,输出产物说明。

说实话,刚拿到Rockchip SDK的时候,我第一反应是——这玩意儿怎么这么复杂?

一堆脚本,一堆目录,不知道从哪下手。后来做多了才发现,其实核心就三个东西:Uboot、Kernel、Rootfs。搞懂它们怎么编译,整个SDK就通了一半。

核心认知:Rockchip SDK 的编译系统本质是一个“脚本套脚本”的洋葱结构。最外层是 build.sh,它帮你把 Uboot、Kernel、Rootfs 的编译命令串起来。你不需要记住每个细节,但必须知道每个环节产出了什么。

4.1 SDK 一键编译脚本解析

先看最常用的命令:

./build.sh lunch        # 选择板型
./build.sh uboot        # 编译 Uboot
./build.sh kernel       # 编译 Kernel
./build.sh rootfs       # 编译 Rootfs
./build.sh all          # 一键全编译

这个 build.sh 其实是个“调度员”。它读取 device/rockchip/ 下的板级配置,然后调用各个子模块的编译脚本。我个人习惯先跑 ./build.sh lunch,确认板型选对了,再分步编译。为什么?因为全编译出错了,你根本不知道是哪个环节挂了。

来看看 build.sh 的核心逻辑:

# 简化后的 build.sh 流程
function build_uboot() {
    cd u-boot
    make ${UBOOT_DEFCONFIG}
    make -j$(nproc)
    cd -
}

function build_kernel() {
    cd kernel
    make ${KERNEL_DEFCONFIG}
    make -j$(nproc) Image dtbs
    cd -
}

function build_rootfs() {
    cd buildroot
    make ${ROOTFS_DEFCONFIG}
    make -j$(nproc)
    cd -
}

嗯,这里要注意:build.sh 里每个函数都独立执行,互不依赖。这意味着你可以单独编译 Uboot,然后单独编译 Kernel,最后单独打包 Rootfs。我在项目中遇到过好几次这种情况——Kernel 改了驱动,但 Uboot 和 Rootfs 完全不用动,重新编译 Kernel 就行。

我的习惯:第一次编译用 ./build.sh all 打通流程,之后只编译改动的模块。这样每次能省 5-10 分钟。

4.2 单独编译 Uboot

Uboot 是第一个上电的程序。它负责初始化硬件、加载 Kernel 到内存。

单独编译 Uboot 的命令:

cd u-boot
make rk3399_defconfig     # 以 RK3399 为例
make -j8
# 产物在 u-boot/ 目录下

编译完成后,你会看到这些文件:

产物文件 说明 烧录位置
uboot.img Uboot 主镜像,包含 SPL 和 Uboot 本体 SD 卡/ eMMC 的 0x4000 扇区
trust.img ATF(ARM Trusted Firmware)镜像 0x6000 扇区
rk3399_loader_v1.xxx.bin 一级 loader,芯片出厂固件 0x0 扇区
idbloader.img SPL + DDR 初始化代码 0x2000 扇区

我曾经踩过一个坑:只更新了 uboot.img,没更新 trust.img,结果板子启动到一半就卡住了。后来才发现 ATF 和 Uboot 版本要匹配。所以我的建议是——Uboot 编译完,把 uboot.img 和 trust.img 一起烧录。

4.3 单独编译 Kernel

Kernel 是操作系统的核心。Rockchip 的 Kernel 编译比 Uboot 稍微复杂一点,因为要生成多个文件。

cd kernel
make ARCH=arm64 rockchip_linux_defconfig
make ARCH=arm64 -j8 Image dtbs modules
# 产物在 kernel/arch/arm64/boot/ 下

编译完你会得到:

产物文件 说明 用途
Image 压缩后的内核镜像 Uboot 加载到内存并启动
*.dtb 设备树二进制文件 描述硬件信息,Kernel 解析
*.ko 内核模块 动态加载的驱动

这里有个细节:make dtbs 会编译所有设备树文件。但实际你只需要板子对应的那个 .dtb。比如 RK3399 的 EVB 板,就是 rk3399-evb.dtb。我一般会单独指定:

make ARCH=arm64 rk3399-evb.dtb

这样编译快很多,尤其是你只改了一个 dts 文件的时候。

注意:Kernel 的 .config 文件不要手动改。用 make menuconfig 或者 make savedefconfig 来管理。我见过有人直接改 .config,结果下次 make clean 后配置全丢了。

4.4 单独编译 Rootfs

Rootfs 是根文件系统。Rockchip SDK 默认用 Buildroot 来生成。编译命令:

cd buildroot
make rockchip_rk3399_defconfig
make -j8
# 产物在 buildroot/output/images/ 下

编译完你会看到:

产物文件 说明 大小参考
rootfs.ext4 ext4 格式的根文件系统镜像 约 256MB ~ 1GB
rootfs.squashfs 只读压缩文件系统 约 50MB ~ 200MB
rootfs.cpio.gz initramfs 格式 约 10MB ~ 50MB

你想想看,为什么有这么多格式?

  • ext4:可读写,适合开发调试。我平时都用这个。
  • squashfs:只读,适合量产。体积小,安全性高。
  • cpio.gz:内存文件系统,适合紧急启动或 recovery 模式。

我个人习惯在开发阶段用 rootfs.ext4,因为可以随时挂载修改。量产时再切到 rootfs.squashfs。

4.5 输出产物总览与烧录关系

把所有产物放在一起看,烧录顺序是这样的:

Rockchip 编译产物与烧录流程 Loader (idbloader.img) Uboot (uboot.img) Trust (trust.img) Kernel (Image) 设备树 (rk3399-evb.dtb) Rootfs (rootfs.ext4) eMMC / SD 卡 按扇区地址烧录 分区表决定位置 💡 烧录顺序:Loader → Trust → Uboot → Kernel + DTB → Rootfs 每个环节的产物独立编译,互不依赖。改 Kernel 不需要重编 Uboot。

这张图把整个流程串起来了。你从左边开始,依次编译每个模块,最后烧录到右边的 eMMC 或 SD 卡。每个箭头代表一个独立的编译动作。

我的建议:第一次接触时,先跑一遍 ./build.sh all,确认环境没问题。然后分别单独编译 Uboot、Kernel、Rootfs,看看每个环节的产物。这样你对整个系统就有了“手感”。

4.6 常见问题与避坑

  • 编译 Uboot 报错:检查交叉编译器版本。Rockchip 官方推荐 gcc 7.5 或 8.3。我用 gcc 9 也试过,但偶尔会出奇怪的问题。
  • Kernel 编译后找不到 dtb:确认 ARCH 环境变量设置正确。我经常忘记设 ARCH=arm64,结果编译出 x86 的 dtb。
  • Rootfs 编译特别慢:第一次编译 Buildroot 要下载很多包,建议用代理或者提前下载好源码包放到 dl/ 目录下。
  • 烧录后板子不启动:先检查串口日志。如果卡在 DDR init,那就是 loader 或 trust 的问题。如果卡在 Starting kernel,那就是 Kernel 或 dtb 的问题。

嗯,编译系统入门其实就这些。说白了,你只要记住三个独立模块、各自的产物、以及烧录顺序,剩下的就是多练。我在 RK3399、RK3568、RK3588 上都走过同样的流程,套路一模一样。