☰
嵌入式SDK目录结构详解:从板级配置到固件产物
2026/10/14 21:11:59 网站建设 项目流程

接手一块不熟悉的板子,第一步往往不是急着编译跑系统,而是先把SDK包解开,摸清楚里面的文件到底谁管谁。前阵子我拿到CQR40平台的SDK,压缩包解出来接近十个GB,顶层目录一眼望去好几层子目录,名字看着都挺眼熟,可真要问一句“这文件夹是干什么的、改了里面哪个文件会影响最终固件”,不少人会卡壳。这篇记录就是我当时完整梳理SDK目录结构的笔记,按“入口、配置、源码、工具链、产物”这条线拆开讲,适合刚接手新平台、准备做定制开发的工程师参考,也适合想把现有SDK吃透再动手的人。

很多国产嵌入式平台的SDK,本质上就是一套“全家桶”:交叉编译工具链、Kernel源码、Bootloader源码、根文件系统构建框架、打包脚本和配套配置项全塞在一个包里面。目录布局大同小异,但每个平台都有自己的脾气。CQR40这套SDK的整体架构不算复杂,可如果不先把骨架摸清,后面就会反复出现“改了配置不生效”“编译半天找不到产物”这类问题。

1. 拿到SDK后的第一件事:先看目录树,别急着读代码

我先在Linux环境下解压SDK压缩包,然后立刻生成了一份完整的目录树。这一步看似简单,却是后续一切分析的索引。网上很多人拿到的SDK是从某个网盘里拉来的压缩包,解压出来直接扔进Windows共享目录,甚至连符号链接都断了,后面编译一万个报错都找不到根因。所以第一步的检查清单里,一定得有“符号链接完整性”和“磁盘空间”两项。

CQR40平台的SDK顶层目录大概长这样(不同版本略有出入,但模式一致):

CQR40_SDK/ ├── kernel/ ├── uboot/ ├── buildroot/ ├── tools/ ├── device/ ├── package/ ├── target/ ├── scripts/ ├── out/ ├── prebuilt/ ├── .repo/ └── build.sh

这种布局在国产平台SDK里很有代表性:architecture相关的源码和构建系统分级放置,kernel、uboot、buildroot各管一段,device存放与具体单板强相关的配置,out目录存放编译产物。拿到这棵树之后,我建议按三类角色去理解每个目录,而不是死记硬背:

  • 源码类:kernel、uboot、buildroot。这类目录决定“系统由什么代码构成”。
  • 构建与配置类:device、target、scripts、package。这类目录决定“怎么把代码构建成固件”。
  • 产物与工具类:out、prebuilt和工具链目录。这类目录决定“最终产出什么、用什么工具构建”。

因为CQR40平台对应Cortex-A系列应用处理器,没有单独的DSP或MCU目录,比带协处理器的平台要清爽。如果你拿到的是带安全协处理器的SDK,会发现还有一整套security相关目录,分析逻辑就要另起一条线。

2. 逐层拆解:配置文件、源码包、工具链和打包脚本各管什么事

2.1 配置文件区:板级配置藏在哪

先打开device目录看,这是板级方案的入口。CQR40平台的device目录内部是这样的:

device/ ├── config/ │ ├── boardconfig/ │ └── rootfs/ ├── kernel/ ├── uboot/ ├── product/ └── system/

boardconfig目录里面存放各型号板卡的BoardConfig.mk,文件名通常以单板型号命名。这个文件的重要性极高,因为它决定了整个SDK从工具链选择到内核镜像大小、启动传参、串口控制台的整套编译基线。我在分析时直接打开了一个core单板的配置文件看前几十行,里面就有CQR40_USE_ANDROID或CQR40_USE_LINUX之类的开关。这是整套SDK的主开关,切换构建目标系统就靠它。

如果你发现自己改了某个目录的源码,编出来的固件却没有任何变化,别急着怀疑编译器没生效,先回来看BoardConfig里的模块开关是不是根本没打开。类似经验我在好几个平台上都验证过,CQR40平台也不例外。

2.2 设备树与内核源码:改硬件先找这里

kernel目录里的大结构是:

kernel/ ├── linux-5.4/ └── linux-4.9/

CQR40平台的SDK里经常同时保留两个内核版本树,配置阶段选择其中一个作为实际编译对象。容易踩坑的是:有些文件的修改会被另一个版本树覆盖,特别是git切换分支或执行make kernel_clean之后。

每一棵内核树内部都有arch/arm64/boot/dts/目录,存放与该平台对应的设备树文件。CQR40是64位CPU,所以主要在arm64下找。设备树文件往往分成两个层面:SoC级dtsi和Board级dts。SoC级描述芯片内部外设和地址映射,一般不要动;Board级描述某块单板上接了什么外设、用了哪个引脚、电平和时钟配置,这部分是硬件工程师与软件对接的重点。分析目录时先查这两个文件的修改时间,就能大致判断这个SDK包是否被二次开发过。

2.3 工具链与prebuilt:为什么要用SDK里自带的

打开prebuilt或者toolchain目录,会发现里面放了一整套交叉编译工具链。很多linux出身的老工程师习惯干掉SDK自带的工具链,直接改用系统的aarch64-linux-gnu-。我不建议这么做。

CQR40平台的SDK在编译uboot、kernel、buildroot时会默认使用工具链目录里的版本。这套工具链的gcc版本是关键变量——如果SDK脚本里写死了某种gcc大版本下的编译选项,再用另一个版本强行编译,轻则编译警告铺天盖地,重则生成的内核模块因为工具链版本差异根本无法在rootfs里加载。原因在于内核模块的 vermagic 机制会检查编译工具链的版本标记,版本对不上就"Invalid module format"。

除非你是极其资深的系统移植人员且明确知道风险,否则老老实实用SDK内置工具链是最稳妥的。它的另一个好处是:工具链目录里还自带了依赖库,可以避免系统glibc版本不一致导致的间接触雷。

2.4 打包脚本与产物:固件是怎么合成的

SDK根目录的build.sh是整个编译流程的总入口,但它只是一个壳,真正干活的逻辑分布在scripts/目录和各个子目录的Makefile里。我把里面的逻辑按阶段拆开梳理了一下:

  • 环境准备阶段:加载工具链路径、检查依赖、设置输出目录变量;
  • 配置选择阶段:根据device/config的产品化目录确定BoardConfig与内核defconfig;
  • 编译阶段:分模块构建uboot、kernel、buildroot,产生镜像文件;
  • 打包阶段:调用打包脚本生成最终的固件镜像文件(常见为img格式或自解压包)。

out/目录会很清晰地反映每个阶段的状态。以CQR40平台为例,一次完整编译后,out目录下会有out/kernel/、out/uboot/、out/buildroot/这些子目录。在最底层的target输出目录里能看到boot.img、rootfs.img、uboot.bin这些实际烧写到板子的镜像文件,以及各类校验和文件。

这种分析路径的价值是:当你了解了“哪个阶段出哪个产物”,后面做定制屏、换Flash分区、加第三方驱动时,就能准确找到该改的地方。

3. 把“配置如何流进固件”这条路走通:Kconfig、BoardConfig与out目录的联动

源码目录分析清楚了,还差最后一块拼图:运行./build.sh时,配置是怎么一级一级穿透到最终镜像里的。我拿CQR40平台的实际编译过程举例。

3.1 Buildroot 的配置选择机制

CQR40平台的rootfs基于buildroot构建。buildroot的顶层目录里有一个隐藏的配置系统,入口是buildroot/configs/下的defconfig文件。编译时脚本会把设备目录里的rootfs配置拷贝过来,再执行make CQR40_defconfig。

这里有一个很隐蔽的细节:如果你直接在这个目录里写了一个新defconfig,但设备目录里的BoardConfig没有同步修改,脚本永远不会去读你的新文件。因为CQR40平台是用一套“产品定义”的概念把板卡、rootfs、内核配置关联起来的,“产品定义”文件里明确写了该产品的rootfs应该用哪个配置。所以我的经验是:先看产品定义,再看BoardConfig,最后才看buildroot自身的configs。

3.2 内核 defconfig 和设备树的确定链路

内核配置方面,SDK里一般会存放多个defconfig文件,分别命名为cqr40_core_defconfig、cqr40_lite_defconfig之类的。平台编译脚本会把产品定义里指定的defconfig拷贝到kernel源码根目录的.config,再执行make olddefconfig做默认补充。

这里要学会区分两个变量:

  • CONFIG_系列:由defconfig决定,控制内核功能裁剪;
  • 设备树(dts):由dts目录下的文件命名决定,控制硬件外设映射。

CQR40平台的内核默认会在编译时把指定dts编译成dtb并打进boot.img。板级配置文件里通常会写明选用的dts文件名,改这个才是更换硬件方案的入口。不要做那种“改了dts但没改BoardConfig里的dtb名字”的事情——内核会按旧名字找dtb,结果就是启动时设备节点缺失,看起来像是驱动没生效。

3.3 out目录结构:怎么看编译结果对不对

完整编译后的out目录建议按这样的顺序检查:

  • 首先看out/下有没有生成target和image子目录;
  • 其次看image子目录里的固件文件时间戳,超出预期时间差说明某个模块没重新编译;
  • 然后看out/buildroot/build/目录下有没有生成对应的rootfs源码解包目录。

一旦这套检查形成习惯,排查编译问题就快很多。比如某次我发现rootfs镜像时间戳还是昨天的,一路追下去,结果发现buildroot的编译缓存没有失效,脚本因为Markdown文件时间戳没变而跳过了rootfs重建。此类“没改对地方”的问题,是SDK目录分析的常见盲区,比直接报错更坑。

4. 目录分析时最容易翻车的几个隐蔽细节

4.1 一样的目录名,可能是两套代码

CQR40平台的SDK里,kernel这个目录经常出现两次:根目录下有kernel/,而out/下也有内核编译的中间产物目录。很多新手修改源码时,会下意识进入根目录去改,这没问题;但排错时如果误以为某个编译报错指向的文件在out目录里,就会越翻越乱。

out目录里的代码通常是编译时自动解压、拷贝出来的独立副本。根目录源码改动后,如果不重新执行完整拷贝流程,out里的副本就是旧版本。这个设计使得“增量编译”变得不安全,因此我的习惯是:修改任何与uboot/kernel相关的源码后,严格执行特定模块的清理命令。

4.2 符号链接问题:解压工具不同,结果天差地别

SDK包内部为了省空间,经常用符号链接关联多个目录。在Linux下用tar解压没问题,但如果你先传到Windows再解压,符号链接就全丢或变成普通有内容的文件副本了。

CQR40平台SDK里,UBoot的某个板级目录就可能是指向../common/的链接。一旦链接丢失,编译uboot时会报找不到头文件,而且报错路径看起来非常诡异。最麻烦的是,有些脚本会读取链接指向的目录是否存在来决定配置项,链接断了配置就直接回调到默认值。

排查方法也简单:在SDK根目录执行find -type l -ls,把所有的链接列出来,再抽查几个关键路径能否正确访问。

4.3 隐藏的repo目录与git子仓库

现在不少SDK都采用repo管理多仓库,CQR40平台SDK也不例外。根目录的.repo/目录里保存着一整套仓库清单,如果SDK是从git仓库拉取的,那么每个源码子目录都是独立的git仓库。

这里有个实战经验:如果你要写“SDK目录分析文档”,一定要把子仓库的当前分支和最新提交记录在文档里。否则过了半年再回来看,根本不知道当时基于哪一版编译的。而且后续如果想升级某个模块版本,也得先在子仓库里切分支,而不是直接改根目录。

4.4 磁盘空间不足:编译到一半才报错

SDK完整编译需要的磁盘空间往往比压缩包大好几倍。CQR40平台的完整输出,包括内核、buildroot、临时文件和最终镜像,我实测下来需要至少100GB的剩余空间。如果你开机时没看磁盘,编译到buildroot阶段才报No space left on device,那是最折磨人的——因为此时几乎所有中间缓存都废了,clean之后重来太浪费时间。

所以在拿到SDK、分析完目录后,我建议第一步先执行一次完整编译,目的就是把所有隐患提前暴露出来,包括磁盘空间、工具链依赖、符号链接等。分析记录做得再详细,不如实际build一把来得可靠。

5. 从目录分析到可复用文档:我沉淀的三种记录

5.1 带功能注释的目录树

只会把目录树贴到笔记里,没什么价值。我的做法是把每层目录按生命周期标注成三类:只读参考、偶尔修改、频繁修改。举个例子:

  • kernel/linux-5.4/arch/arm64/boot/dts/ → 偶尔修改,仅在硬件改版时动;
  • device/config/ → 频繁修改,产品级配置入口;
  • out/ → 不做修改,只做产物检查;
  • scripts/ → 只读参考,除非要做构建系统二次开发。

这样的注释比单纯列出目录名有用得多,看的人能立刻知道哪些地方需要谨慎。我用这套方法给团队做技术交接时,新人上手时间明显缩短了。

5.2 配置索引表

准备一份表格,把关键配置项和它们所在的路径一一对应起来。这是我的CQR40平台记录表的一部分,供参考:

配置事项对应文件路径常见修改场景
构建目标系统(Linux/Android)device/config/.../BoardConfig.mk切换产品形态
内核配置kernel/linux-5.4/arch/arm64/configs/xxx_defconfig裁剪内核功能
设备树选择同一defconfig或产品配置中的dtb变量更换单板硬件
根文件系统功能buildroot/configs/xxx_defconfig增减应用与库
分区表布局device/config/.../sys_partition.fex调整固件存储布局
固件打包参数scripts/或打包脚本配置文件调整镜像输出形式

这份表的核心价值在于“查得到”,而不是“写得多”。我一般会保持表格精简,否则维护成本太高,改个配置就要翻一次表,反而容易出错。

5.3 编译链路速查表

最后,我把CQR40平台的一条典型编译全链路写成了速查表,方便在任何构建环节中断时快速定位:

如果问题出现在先检查再检查
uboot编译失败uboot板级配置头文件工具链版本与链接器路径
kernel编译失败defconfig是否有新模块未同步设备树文件是否有语法错误
rootfs构建失败buildroot下载缓存是否完整目标架构配置是否与工具链匹配
最终打包失败分区表大小是否溢出中间镜像文件是否存在

这表看似简单,但在连续两天排错后回看,能节省大量时间。分析SDK不是一次性的工作,目录结构最终要变成团队能长期使用的索引文档,才算真正完成了“分析记录”这一步。

最后再分享一点我的个人习惯:每次分析完一个平台的SDK,我都会在文档末尾留一节“踩坑日志”,按时间记录当天遇到的问题和解法。有些坑当时觉得永远忘不了,其实半年后就模糊了。尤其像CQR40这种带多套内核树、多产品定义、多板级配置的SDK,真正消耗时间的不是第一次编译通过,而是后续每次改动时能快速定位到“改哪、怎么改、影响什么”。把目录当成活地图来维护,比什么分析技巧都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询