首页 / 资讯中心 / 文章详情

嵌入式SDK目录结构详解:从板级配置到固件产物

嵌入式SDK目录结构详解:从板级配置到固件产物 ★ FEATURED ARTICLE
接手一块不熟悉的板子第一步往往不是急着编译跑系统而是先把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/Androiddevice/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真正消耗时间的不是第一次编译通过而是后续每次改动时能快速定位到“改哪、怎么改、影响什么”。把目录当成活地图来维护比什么分析技巧都管用。
阅读完成 · 觉得有帮助?
咨询建站