从零构建OK3568-C Linux系统镜像:U-Boot、内核与Buildroot全流程实战
1. 项目概述从零开始构建OK3568-C的完整系统镜像对于嵌入式开发者而言拿到一块功能强大的开发板第一件想做的事情往往不是直接运行预编译的镜像而是亲手从源码开始构建一个属于自己的、完全可控的系统。这不仅是验证开发环境、熟悉板载资源的最佳途径更是后续进行深度定制和驱动开发的基石。飞凌嵌入式的OK3568-C开发板基于瑞芯微RK3568这颗性能与功耗平衡得相当不错的四核Cortex-A55处理器为开发者提供了丰富的软件资源支持。今天我就以一名嵌入式老鸟的视角带你走一遍在Ubuntu主机上为OK3568-C编译全套Linux系统U-Boot、Kernel、Buildroot根文件系统并打包成可烧录固件的完整流程。这个过程看似步骤繁多但每一步都环环相扣理解其背后的逻辑远比机械地执行命令更有价值。整个编译过程的核心是飞凌官方提供的一套基于Buildroot的构建系统。它通过一个统一的build.sh脚本封装了对不同组件如U-Boot、Kernel的编译、配置以及最终的镜像打包操作极大地简化了从零构建的复杂度。我们的目标不仅仅是让屏幕亮起来更是要搞清楚源码从哪里来、编译环境如何搭建、各个镜像组件的作用是什么、以及最终它们是如何被“组装”成一个完整的update.img供烧录使用的。我会在每一步中穿插我实际踩过的坑和总结出的技巧希望能帮你避开那些恼人的编译错误和环境问题。2. 编译环境搭建与源码准备在开始敲下任何编译命令之前一个稳定、纯净的编译环境是成功的一半。嵌入式Linux编译对主机环境有一定要求版本不匹配的库或工具链往往是各种诡异错误的根源。2.1 主机环境配置详解飞凌官方推荐使用Ubuntu 18.04作为编译主机这是一个经过大量验证的稳定选择。我强烈建议你使用一台物理机或配置足够的虚拟机来操作避免在资源不足的共享环境或WSLWindows Subsystem for Linux中编译后者可能会在文件系统权限、符号链接等方面遇到意想不到的问题。首先我们需要安装一系列基础的编译工具和依赖库。打开终端执行以下命令sudo apt-get update sudo apt-get install -y git-core gnupg flex bison gperf build-essential \ zip curl zlib1g-dev gcc-multilib g-multilib libc6-dev-i386 \ lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache \ libgl1-mesa-dev libxml2-utils xsltproc unzip python2.7 python3 \ device-tree-compiler u-boot-tools swig libncurses5-dev libssl-dev注意这里安装的python2.7是因为部分较老的构建脚本尤其是一些芯片原厂的工具可能仍依赖Python 2。虽然Python 2已停止维护但在嵌入式开发领域这种历史遗留问题仍很常见。确保系统中同时存在python指向Python 3和python2两个命令。接下来是交叉编译工具链。RK3568是ARM架构的处理器我们需要在x86_64的Ubuntu上生成ARM可执行的代码。飞凌的SDK通常已经内置了工具链但为了确保万无一失或者当你需要单独编译某个组件时知道工具链的位置和版本很重要。通常工具链会位于SDK目录下的prebuilts/gcc/linux-x86/aarch64之类的位置。你可以通过find命令查找aarch64-linux-gnu-gcc这个文件来定位它。2.2 源码获取与解压的实战技巧飞凌官方会将完整的SDK源码以压缩包形式提供给用户。如原文所述源码包OK3568-linux-source.tar.bz2可能因为体积过大而被分割成多个文件如OK3568-linux-source.tar.bz2.aa,OK3568-linux-source.tar.bz2.ab等这是为了解决FAT32等文件系统对单个文件4GB的大小限制。第一步创建工作目录并传输源码。我个人的习惯是在家目录下创建一个项目专用的文件夹这样路径清晰也便于管理。mkdir -p ~/projects/rk3568 cd ~/projects/rk3568将所有的分卷压缩文件OK3568-linux-source.tar.bz2.a*拷贝到这个目录。如果你使用的是虚拟机共享文件夹是最方便的方式。如果是物理机可以使用scp命令通过网络传输或者直接用U盘拷贝。确保所有分卷文件都在同一目录下。第二步合并与解压。这是关键一步。使用cat命令按顺序合并所有分卷文件还原出完整的压缩包cat OK3568-linux-source.tar.bz2.a* OK3568-linux-source.tar.bz2合并完成后你会得到一个完整的OK3568-linux-source.tar.bz2文件。接下来解压tar -xvf OK3568-linux-source.tar.bz2这里有一个非常重要的心得解压过程会持续较长时间可能十几到几十分钟取决于你的磁盘速度并且控制台会持续输出解压的文件列表。我建议你将其输出重定向到一个日志文件或者直接静默解压以便观察是否有错误发生。# 方式一静默解压只显示错误信息 tar -xf OK3568-linux-source.tar.bz2 # 方式二解压并记录日志 tar -xvf OK3568-linux-source.tar.bz2 extract.log 21 解压完成后进入源码目录你会看到一个结构清晰的目录树通常包含build.sh、device/、kernel/、u-boot/、buildroot/等关键目录。这个目录结构就是整个构建系统的骨架。3. 构建系统解析与编译配置进入源码根目录后我们面对的是一个高度集成的构建系统。理解这个系统的运作方式能让你在遇到问题时知道从哪里下手排查。3.1 构建脚本build.sh核心机制剖析./build.sh是这个SDK的入口点它本质上是一个分发器。你可以通过传递不同的参数给它来执行不同的构建阶段。不带任何参数运行./build.sh它会尝试执行“全自动编译”即按顺序编译U-Boot、Kernel、Recovery和根文件系统最后打包。但对于我们学习和排查问题来说分步编译是更好的选择。在分步编译之前必须先进行板级配置。这是告诉构建系统“我要为哪一块具体的板子编译”。配置命令如下./build.sh BoardConfig-ok3568.mk这条命令做了什么呢它会去device/rockchip/ok3568/目录下寻找名为BoardConfig-ok3568.mk的配置文件并将其中的变量如芯片型号、内存大小、存储介质类型、屏幕参数等导出到当前的环境或传递给Makefile。执行成功后通常不会有太多输出但会在后台生成一些必要的配置链接或文件。你可以通过ls -la查看是否生成了device/rockchip/.BoardConfig.mk之类的符号链接来验证。3.2 关键组件编译顺序与依赖关系整个系统的编译有一个隐含的依赖顺序通常是U-Boot - Kernel - Rootfs。Recovery作为一个特殊的、精简的Linux系统其编译独立于主系统。编译U-BootU-Boot是系统上电后运行的第一段裸机程序负责初始化最基础的硬件如DDR内存、时钟、存储控制器然后加载并启动内核。执行./build.sh uboot这个过程会进入u-boot/目录调用其自身的Makefile进行编译。编译输出通常位于u-boot/目录下但最终会被构建系统拷贝到统一的输出目录如rockdev/中。编译Kernel这是编译过程中最可能遇到交互式配置的一步。执行./build.sh kernel脚本会先进入kernel/目录可能先应用一些默认的板级配置文件defconfig然后启动一个基于ncurses的文本菜单配置界面即经典的make menuconfig。这里就是原文中提到会“弹出下图界面”的地方。实际上这个界面是内核的配置菜单飞凌可能预先在其中标记了一些需要用户确认的硬件配置选项例如某些GPIO的电压VCCIO4, VCCIO6。实操心得对于OK3568-C常见的配置是如原文所述将VCCIO4和VCCIO6的电压选择为1800000即1.8V其他选项保持33000003.3V。这个选择关系到板载外设如SD卡、EMMC、某些电平转换芯片的供电电压选错可能导致硬件无法识别甚至损坏。务必根据你手中开发板的实际硬件版本和原理图来确认如果不确定一个保守的做法是查阅飞凌官方提供的《硬件手册》或《用户资料》中的相关章节。在配置界面中使用方向键移动空格键选中/取消选中[*]表示编译进内核[M]表示编译为模块[ ]表示不编译回车键进入子菜单或确认选择。配置完成后选择Save保存然后退出。内核编译耗时较长会生成arch/arm64/boot/Image内核镜像和一系列设备树二进制文件*.dtb。编译RecoveryRecovery是一个小型Linux系统常用于系统升级、恢复出厂设置等。编译命令为./build.sh recovery其过程与编译主系统类似但通常配置更简单生成的镜像也较小。编译Buildroot根文件系统这是构建用户空间环境的一步。Buildroot会下载、配置、编译从BusyBox、工具链库到各种应用软件如Qt、Python在内的所有软件包并最终生成一个完整的根文件系统镜像。执行./build.sh buildroot这是整个编译过程中最耗时的一步可能长达数小时因为它需要从网络下载大量的源代码包。确保你的主机网络通畅。重要警告务必以普通用户非root身份执行此步骤Buildroot在设计上就不推荐在root权限下运行因为编译过程中会以当前用户身份创建大量文件以root身份运行可能导致后续权限问题甚至存在安全风险。如果你在之前步骤中使用了sudo请先退出root shell。4. 镜像打包与分区表深度解读当所有组件都编译完成后我们得到的是分散的镜像文件。需要将它们按照板子存储介质通常是eMMC或SD卡的预定布局打包成一个可供烧录工具如RKDevTool识别的单一文件。4.1 分区表parameter.txt的奥秘在打包之前必须理解分区表文件parameter.txt。这个文件定义了存储介质上的分区布局是U-Boot和内核识别文件系统的基础。其路径通常在device/rockchip/ok3568/下例如parameter-buildroot-fit.txt。让我们解剖一个典型的分区表条目CMDLINE: mtdpartsrk29xxnand:0x000020000x00004000(uboot),0x000020000x00006000(trust),0x000020000x00008000(misc),0x000080000x0000a000(boot),0x000100000x00012000(recovery),0x000400000x00022000(rootfs),0x000004000x00062000(oem),-0x00062400(userdata)mtdpartsrk29xxnand:指定了存储设备类型这里是NAND Flash对于eMMC可能是rk29xxnand的兼容表示或其它。0x000020000x00004000(uboot)这是分区的定义格式。0x00004000是分区的起始扇区地址单位通常是512字节的扇区0x00002000是分区的大小同样是扇区数。计算一下0x2000个扇区 * 512字节/扇区 4MB。所以这一行定义了一个从第0x4000即16MB扇区开始大小为4MB的名为uboot的分区。后面的trust、misc、boot、recovery、rootfs、oem、userdata分区依此类推。-0x00062400(userdata)这里的-表示分区大小扩展到存储设备的末尾即userdata分区从0x00062400扇区开始占据所有剩余空间。为什么分区起始地址不是从0开始因为存储介质的最前面一部分空间比如前16MB通常被保留给Boot ROM、Loader等更底层的引导代码使用这部分对操作系统是不可见的。4.2 打包流程与package-file文件的作用理解了分区表再看打包流程。首先需要将编译好的各个组件镜像“链接”到打包工作区./mkfirmware.sh这个脚本的作用是根据配置将散落在各处的最终镜像文件如kernel/arch/arm64/boot/Imageu-boot/uboot.img等拷贝或链接到rockdev/目录下并按照统一的命名规则放置为最终打包做准备。接下来执行打包命令./build.sh updateimg这个命令会调用一个底层工具rk2918_tools中的afptool和img_maker。它的工作逻辑由一个名为package-file的文件指导。这个文件通常位于tools/linux/Linux_Pack_Firmware/rockdev/下例如rk356x-package-file。package-file文件列出了需要被打包进update.img的所有文件及其在镜像中的逻辑名称。例如#NAME Relative path uboot Image/uboot.img boot Image/boot.img rootfs Image/rootfs.img打包工具会读取这个列表将rockdev/Image/目录下对应的uboot.img、boot.img等文件按照parameter.txt定义的分区顺序和大小组装成一个完整的、包含分区信息的update.img。这个update.img就是我们可以直接通过瑞芯微烧录工具写入开发板的最终固件。5. 进阶技巧镜像解包、修改与重组全量编译一次系统非常耗时特别是Buildroot。在实际开发中我们经常只需要修改内核或某个应用然后快速生成新固件。这时对已有固件进行解包、替换、再打包的技巧就非常实用。5.1 使用rk2918_tools进行镜像操作首先你需要获取并编译rk2918_tools工具集。这是一个开源工具专门用于处理瑞芯微平台的镜像文件。git clone https://github.com/TeeFirefly/rk2918_tools.git cd rk2918_tools make sudo cp afptool img_unpack img_maker mkkrnlimg /usr/local/bin/编译安装后你就拥有了四个关键工具img_unpack: 解包最外层的update.img。afptool: 解包或打包Android固件包AFPT格式的内部镜像。img_maker: 将Loader和AFPT包合并成最终的update.img。mkkrnlimg: 处理内核镜像的专用工具。5.2 替换内核或根文件系统的实战步骤假设你从飞凌官网下载了一个最新的update.img只想替换其中的内核模块而不想重新编译整个根文件系统。步骤1解包原始固件。# 假设update.img在当前目录 img_unpack update.img img执行后会生成一个img目录里面包含loader.img第一级Loader和update.img这个是需要进一步解包的AFPT包。步骤2解包AFPT包。cd img afptool -unpack update.img update这会在当前目录生成update文件夹里面就是按分区命名的各个镜像文件如boot.img,rootfs.img等。步骤3替换目标镜像。例如你修改了内核配置并重新编译生成了新的boot.img。你可以直接将它拷贝过来覆盖update/目录下的旧boot.img。cp /path/to/your/new/boot.img update/步骤4重新打包AFPT包。在img目录下使用afptool重新打包。注意这里需要指定package-file它定义了打包的列表和顺序。通常我们可以使用从原始固件中解包出来的那个或者使用SDK中的标准文件。afptool -pack ./ ./new_update.img ./update/package-file这条命令的意思是在当前目录(./)下使用update/文件夹里的内容按照./update/package-file的描述打包生成一个名为new_update.img的AFPT包。步骤5合成最终的update.img。最后需要将第一级Loader和新的AFPT包合并。img_maker -rk33 loader.img new_update.img new_final_update.img-rk33指定了芯片类型RK3568属于RK33系列。生成的new_final_update.img就是包含了你的新内核的完整可烧录固件。避坑指南在替换镜像时务必确保新镜像的大小不超过parameter.txt中为该分区定义的大小。例如如果boot分区定义为32MB那么你的boot.img就不能超过32MB。否则打包会失败或者烧录后启动异常。可以使用ls -lh命令查看镜像大小。6. 编译过程中的常见问题与排查实录即使按照指南操作编译过程也难免遇到问题。下面是我总结的几个最常见的问题及其解决方法。6.1 编译环境与依赖问题问题1编译U-Boot或Kernel时报错找不到交叉编译工具链aarch64-linux-gnu-gcc。排查首先确认你是否正确执行了./build.sh BoardConfig-ok3568.mk这个步骤通常会设置工具链路径。可以尝试在源码根目录执行echo $PATH和echo $CROSS_COMPILE查看环境变量。解决最直接的方法是手动指定。进入U-Boot或Kernel目录查看顶层的Makefile找到CROSS_COMPILE变量的定义处将其修改为你SDK中工具链的绝对路径例如CROSS_COMPILE/home/yourname/projects/rk3568/OK3568-linux-source/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-。然后执行make clean后再重新编译。问题2编译Buildroot时下载某个软件包如linux-headers失败卡住不动。排查Buildroot会从网络下载所有软件包源码。失败可能是网络问题、源地址失效或证书问题。查看output/build/目录下对应软件包目录的config.log或*.mk文件中的下载链接。解决手动下载复制失败的URL用浏览器或wget手动下载到dl/目录下。更换源编辑Buildroot的配置文件buildroot/Config.in或通过make menuconfig在Build options - Mirrors and download locations中将$(BR2_PRIMARY_SITE)替换为国内镜像源例如清华源https://mirrors.tuna.tsinghua.edu.cn/buildroot/。跳过验证对于已知安全的包可以临时注释掉package/目录下对应包的.mk文件中的$(addprefix https://, ...)校验行不推荐长期使用。6.2 内核配置与设备树问题问题3内核编译到一半报错某个结构体未定义或函数参数不匹配。排查这通常是内核版本与某些驱动模块特别是第三方或自己移植的驱动不兼容导致的。内核API在不同版本间可能会有变动。解决检查出错的驱动代码根据内核版本如5.10查找对应的内核文档修正API调用。如果该驱动非必需可以在内核配置菜单make menuconfig中将其关闭按N键。确保你使用的是飞凌官方为该版本SDK提供的、经过测试的内核分支不要随意切换内核版本。问题4系统能启动到U-Boot但加载内核后卡住提示找不到设备树dtb或无法挂载根文件系统。排查这几乎是嵌入式Linux启动过程中最经典的故障。问题可能出在设备树二进制文件.dtb未正确编译或打包。parameter.txt中的分区地址与实际烧录位置不符。根文件系统格式ext4/squashfs与内核配置不匹配。根文件系统镜像本身损坏。解决检查设备树确认kernel/arch/arm64/boot/dts/rockchip/下是否存在rk3568-ok3568-c.dtb或类似名称文件并确认它被打包进了boot.img。核对分区表使用烧录工具读取板子的当前分区信息与parameter.txt进行比对。确保boot分区的起始地址和大小完全一致。检查根文件系统尝试将rootfs.img挂载到本地检查其完整性。对于ext4格式sudo mount -o loop rootfs.img /mnt对于squashfs格式unsquashfs -l rootfs.img。启用内核早期调试在内核命令行在U-Boot中通过setenv bootargs设置中添加earlycon consolettyFIQ0,1500000n8 loglevel8可以在串口看到更详细的内核启动日志。6.3 打包与烧录问题问题5执行./build.sh updateimg失败提示“Image/xxx.img not found”。排查mkfirmware.sh脚本执行失败或者某个组件编译未成功导致rockdev/Image/目录下缺少必要的镜像文件。解决首先检查rockdev/Image/目录下是否缺少报错中提到的文件。然后回溯执行./mkfirmware.sh看是否有错误输出。检查对应组件的编译是否成功。例如缺少uboot.img就回到u-boot目录查看编译日志。确保所有编译步骤都是在正确配置执行过BoardConfig后进行的。问题6烧录update.img后板子无法启动一直停留在Loader或MaskRom模式。排查这可能是最底层的Loader或参数区出了问题。解决尝试擦除Flash后重新烧录在烧录工具中先执行“擦除Flash”然后再执行“烧录”。检查Loader版本确保你使用的烧录工具RKDevTool版本与SDK匹配过旧或过新的工具可能不兼容Loader。尝试使用SDKtools/目录下自带的烧录工具。进入MaskRom模式强制烧录如果板子变“砖”通常可以通过短接eMMC的某些引脚具体位置查开发板手册或按住特定按键上电强制进入MaskRom模式此时烧录工具可以识别到一个“发现一个MASKROM设备”然后进行底层恢复烧录。整个从源码到镜像的编译过程就像是在组装一个精密的数字时钟。U-Boot是上发条和校准的机构内核是擒纵轮和齿轮系根文件系统则是表盘和指针。任何一个环节的错位或损坏都会导致整个系统停摆。耐心、细致地理解每个步骤的原理并善用日志和调试工具是解决所有问题的万能钥匙。当你第一次看到自己编译的系统在屏幕上成功启动那种成就感是直接使用预编译镜像无法比拟的。这不仅仅是完成了一个任务更是真正拥有了对这块开发板的掌控力。