3. SDK工程结构解析

拿到一个 SDK 压缩包后,很多人的第一反应是直接解压、找 README、敲 make。但真正决定你能否顺利发布根文件系统版本的,不是命令本身,而是你是否理解 SDK 的目录布局与构建系统之间的映射关系。SDK 不是一堆源码的简单堆叠,它是一套“配置驱动 + 分层构建 + 产物归集”的工程体系。本节将带你从目录结构入手,逐步定位根文件系统源码,并理清构建系统与配置文件之间的依赖链路。

一、典型 SDK 目录布局:三层结构

绝大多数嵌入式 SDK(如 Buildroot、Yocto、OpenWrt、厂商定制 SDK)都遵循相似的三层布局:顶层工程层、构建系统层、源码与产物层。理解这三层,是后续所有操作的基础。

  • 顶层工程层:包含 Makefile、build.sh、README、LICENSE、configs/ 等。它是用户入口,负责环境检查、配置加载和构建调度。
  • 构建系统层:如 buildroot/、yocto/、openwrt/ 或厂商自研的 build/ 目录。它包含包管理、交叉编译工具链、镜像生成规则。
  • 源码与产物层:package/、source/、output/、images/、rootfs/ 等。这里存放第三方包源码、编译中间件和最终可烧录镜像。

下面这张结构关系图展示了一个典型 SDK 的目录布局及其内部依赖方向,请重点观察“配置如何驱动构建、构建如何产出 rootfs”这条主线。

顶层工程层(用户入口) Makefile 构建总入口 build.sh 环境检查/调度 configs/ 板级/产品配置 README 编译说明 .config 当前生效配置 加载配置 构建系统层(规则与工具链) buildroot/ 或 yocto/ 包管理 + 构建规则 toolchain/ 交叉编译工具链 package/ 软件包定义与依赖 fs/ 或 target/ 根文件系统骨架 编译/安装 源码与产物层(编译输出) source/ 第三方源码缓存 output/ 编译中间件 rootfs/ 根文件系统目录树 images/ 最终烧录镜像 打包/签名 可烧录根文件系统镜像(.img / .tar.gz / .ubi) 配置反馈

二、根文件系统源码位置:三个关键目录

根文件系统(rootfs)不是单一文件,而是一棵目录树。它的源码通常分布在三个位置,理解它们的分工,才能精准修改。

目录 作用 典型内容 是否直接修改
fs/ 或 target/ 根文件系统骨架定义 目录结构、权限、设备节点、启动脚本 是,核心修改区
package/ 软件包安装规则 每个包的 .mk、Config.in、安装路径 是,增删软件包
output/rootfs/ 编译后生成的根文件系统 最终目录树、库文件、可执行文件 否,由构建生成

以 Buildroot 为例,fs/ 下常见 skeleton/、common/、initramfs/ 等子目录。其中 skeleton/ 是根文件系统的最小骨架,包含 /etc/init.d/rcS、/dev、/proc 等挂载点。而 package/ 下的每个包通过 INSTALL_TARGET_CMDS 决定文件安装到 rootfs 的哪个位置。

关键认知:根文件系统的“源码”不是一份代码,而是“骨架 + 包安装规则 + 配置文件”三者共同描述的结果。修改 rootfs,本质是修改这三者的组合。

三、构建系统与配置文件的关系

构建系统与配置文件之间是“读取—解析—执行”的关系。以 Buildroot 为例,配置层级如下:

  1. 板级默认配置:configs/xxx_defconfig,定义芯片架构、工具链、内核版本、rootfs 格式。
  2. 包级配置:package/xxx/Config.in,定义该包是否可选、依赖关系、版本号。
  3. 当前生效配置:.config,由 make xxx_defconfig 生成,是构建系统实际读取的文件。
  4. 构建规则:package/xxx/xxx.mk,定义下载、解压、编译、安装的具体命令。

执行 make 时,构建系统先读取 .config,再遍历 package/ 下所有 .mk 文件,根据配置决定哪些包被编译、安装到 rootfs 的哪些路径。最终由 fs/ 下的规则将 output/target/ 打包成镜像。

# 典型构建流程(Buildroot 风格)
make xxx_defconfig          # 生成 .config
make menuconfig             # 可选:交互式调整
make -j$(nproc)             # 编译所有选中包
make rootfs-ext4            # 单独生成 ext4 根文件系统镜像
ls output/images/           # 查看最终产物

四、案例与常见误区

案例场景:某产品需要将默认的 /etc/init.d/rcS 替换为自定义启动脚本,并增加一个 myapp 到 /usr/bin。正确做法是:在 fs/skeleton/etc/init.d/ 下修改 rcS,在 package/myapp/ 下新增 Config.in 和 myapp.mk,并在 myapp.mk 中写入 INSTALL_TARGET_CMDS 将二进制安装到 $(TARGET_DIR)/usr/bin。最后在 defconfig 中打开 BR2_PACKAGE_MYAPP=y。

常见误区:
  • 直接修改 output/target/ 下的文件——下次 make 会被覆盖,必须改源码层。
  • 把 .config 当作永久配置——它由 defconfig 生成,应修改 defconfig 而非直接改 .config。
  • 忽略 package/ 的依赖声明——缺少 DEPENDENCIES 会导致编译顺序错误。
  • 在 fs/ 中硬编码绝对路径——应使用 $(TARGET_DIR) 等变量,保证可移植性。
复盘建议:每次修改 rootfs 前,先画一张“配置 → 包 → 安装路径 → 镜像”的映射表。确认修改点落在正确的层级,再执行构建。这样能避免 80% 以上的“改了没生效”问题。

总结来说,SDK 工程结构解析的核心就是三句话:顶层管入口,构建系统管规则,源码与产物层管结果。根文件系统的源码位置集中在 fs/ 和 package/,而构建系统与配置文件之间通过 defconfig → .config → .mk → rootfs 这条链路紧密耦合。掌握这条链路,你就掌握了 SDK 版本工程的主动权。