很多拿到 SDK 的兄弟,第一反应都是赶紧把build.sh跑起来,看看能不能点亮开发板。但我自己拿到全志 CQR40 平台这套 SDK 之后的做法恰恰相反:我先把整个文件夹结构完整过了一遍,前后花了两天,才动手编译。这套 SDK 解压开就几十个 GB,如果你不先把目录逻辑摸清楚,后面跑构建、改配置、裁剪系统,每一步都会像在黑屋子里找开关。这篇文章就是我这次做 CQR40 平台 SDK 文件夹分析时留下的记录,包含目录怎么分层、每个目录里到底放了什么、哪些文件动了会出事,以及我实际排查过的几个经典问题。想快速上手的做 BSP、驱动移植或系统定制的工程师,都可以直接拿这份思路去应付同类 SDK。
1. 项目背景:为什么文件夹结构本身值得花时间分析
1.1 拿到 SDK 后的第一件事,不要急着编译
我以前接过一个项目,拿到一套不熟悉的 SDK,上来就编译,结果第一次构建就报了找不到/out/xxx的错误。那时候我连out这个目录是编译期生成还是源码自带的都不确定,最后只能老老实实回去翻目录。全志 CQR40 平台的 SDK 不是一个小工具包,它包含了引导程序、内核、根文件系统、构建工具链、板级配置、烧录工具,甚至还有一堆文档和预编译产物。只要其中一个子模块的路径不对,整条构建链就会断掉。
所以我的建议很明确:先建目录树,再读 README 和顶层build.sh,然后顺藤摸瓜把关键配置文件的路径全部标出来。这个过程看起来像是在做“文档工作”,但实际上能给你省出后面大把的调试时间。拿到 SDK,眼睛不要只盯着哪个 defconfig 该选什么,先搞清楚“哪个目录是源码、哪个目录是输出、哪个目录是配置”这三大问题。
1.2 这份分析记录适合谁看
如果你是刚入门嵌入式 Linux 开发的,这套 SDK 目录分析记录可以帮助你避开很多无效劳动。尤其是当你面对的不是“干净”的开源主线内核,而是由芯片厂商整理过的 BSP 包时,目录结构和构建脚本里的隐含逻辑,往往比代码本身更影响成败。
如果你是做驱动移植或者内核裁剪的工程师,也需要这份记录。因为你要改的文件大概率分散在 kernel、device、buildroot 三个目录里,如果不知道它们之间怎么联动,改了配置也编不出来。如果你只是做上层应用集成,那一部分目录可以略过,但建议还是看看第 3、4 节,了解镜像是怎么被打包出来的,对排查启动问题会有帮助。
2. CQR40 平台 SDK 的顶层目录架构
2.1 第一眼看到的整体结构
全志 CQR40 平台这一版 SDK 解压后,顶层目录大致长这样:
CQR40_Linux_SDK/ ├── brandy/ ├── buildroot/ ├── device/ ├── docs/ ├── external/ ├── kernel/ ├── out/ ├── prebuilt/ ├── scripts/ ├── target/ ├── tools/ └── build.sh不用被这么多个目录吓到。它们其实可以归纳成四类:
- 源码类:
kernel/、brandy/、buildroot/、external/ - 配置类:
device/、target/ - 构建类:
scripts/、tools/、build.sh - 输出类:
out/、部分prebuilt/
我对这种组织的理解是,SDK 实际上把一整个嵌入式 Linux 系统拆成了若干独立编译单元。每个单元都有自己单独的构建入口,最后再由顶层脚本统一收集产物,生成烧录镜像。这种设计的好处是方便做“单个模块增量编译”:比如你只改内核驱动,那就可以只编 kernel,不必把整个 buildroot 重新跑一遍,节省时间。
2.2 顶层目录之间的依赖关系
只看目录名不够,还得知道谁的产物会被谁拿去用。我在分析时专门画过一张依赖表,虽然 CQR40 平台有一定的私有细节,但大体思路是通用的。
| 目录 | 编译产物 | 主要依赖 | 最终去向 |
|---|---|---|---|
| brandy | boot0、Uboot | 工具链、芯片初始化配置 | 烧录到启动分区 |
| kernel | zImage/Image、dtb | 板级 dts、内核 defconfig | boot 分区 |
| buildroot | rootfs 镜像、各类应用库 | 包配置、交叉编译工具链 | rootfs 分区 |
| scripts/tools | 打包工具、签名工具 | 无 | 生成整包镜像 |
| target | 镜像打包规则、分区表 | device 下的配置文件 | out 目录中的最终烧录包 |
从编译顺序上看,一般是 toolchain → brandy → kernel → buildroot → pack。但你要灵活点,如果只是做应用层调试,完全可以只跑 buildroot 和打包步骤。理解依赖关系后,就不会出现“改完内核却在 buildroot 目录里找产物”这类低级问题。
2.3 目录命名的规律
这套 SDK 的目录命名是有门道的。kernel下面一般会有个linux-x.y.z之类的深层目录,buildroot下面也可能带具体版本后缀。不要被这些多层嵌套搞晕,本质上就是“厂商把某个开源组件原样放进 SDK,然后再在外面包一层自己的链接”。更值得注意的是scripts/目录名称,不同版本的 SDK 可能叫scripts,也可能叫platform、build,但作用都差不多。我在分析 CQR40 平台时,习惯先把build.sh里所有source的脚本路径列出来,因为那才是真正决定构建流程的文件。
3. 核心目录逐个拆解
3.1 brandy:引导程序的三个关键产物
brandy这个目录在 CQR40 平台里存放的是整个系统的起点逻辑。按我拆解后的结果,它内部通常还分成若干子模块,最重要的两个是 boot0 和 Uboot。boot0 负责最基础的硬件初始化,比如 DDR、时钟和片外 Flash;Uboot 则在 boot0 之上负责加载内核、传递启动参数、实现环境变量等。
这里有个很容易被忽略的细节:boot0 和 Uboot 虽然是两个独立模块,但它们的配置必须保持一致,尤其是 DDR 参数和引脚复用。我见过有人只改了 Uboot 的串口配置,boot0 还在用默认串口,结果烧进去之后启动日志一片混乱。翻brandy目录时,重点看各模块的样板配置文件,比如boards或者configs下的文件,而不是一上来就钻进去读汇编代码。
真正改引导程序的机会不多,但一旦要改,必须顺着这个目录树找到对应平台配置。CQR40 平台的硬件差异基本都集中在board_xxx之类的头文件中,注意看宏开关,很多看似相同的配置在不同宏下行为完全不同。
3.2 kernel 与板级设备树
kernel/目录是整个 SDK 里最庞大的部分。它里面不是只有一个内核源码树,还挂着许多厂商补充的驱动源码。要在这个目录里分析,先记住两个关键的定位方式:
- 内核配置文件:在
arch/arm64/configs/或arch/arm/configs/下,CQR40 平台一般对应一个xxx_cqr40_defconfig - 设备树源文件:在
arch/arm64/boot/dts/或arch/arm/boot/dts/下,文件名可能会带有sunxi、cqr40之类的平台标识
我拿到 SDK 之后,第一件要干的事就是确认这份 SDK 用的是 32 位还是 64 位内核。因为这会直接决定你后面make ARCH=arm64 CROSS_COMPILE=...的命令写法。CQR40 这一版以 64 位为主,但内核源码里还是会混着 32 位平台的东西,这并不奇怪,不用觉得是目录结构坏了。
分析时建议重点看dts目录下与 CQR40 相关的.dts和.dtsi文件。板级外设的寄存器地址、中断号、GPIO 复用全部在这里。很多人喜欢直接去改驱动源码,但我在实际排查中发现,不少“驱动不工作”的 bug,最后问题其实出在设备树节点没配对。改设备树后记得重新生成 dtb,别只重新编内核镜像。
3.3 buildroot:根文件系统不是“一个静态目录”
buildroot/目录负责构建整个 rootfs。它从表面上看起来是一堆 Makefile 和配置文件,但实际作用是从下载源码包、编译库、生成根文件系统目录树,到最后压缩成镜像,一条龙完成。CQR40 平台的 SDK 里,buildroot 往往预先配置好了一批工具和库,比如网络管理、音频框架、图形界面组件等。
分析这个目录时,我建议先看configs/里与这个平台对应的 defconfig。里面会有一大堆BR2_PACKAGE_xxx=y的配置项,这些就是系统定制的开关。我常用grep去搜某个包名,比如BR2_PACKAGE_XXX,来确定某个库是不是已经被编进了系统。还有一个容易踩的坑是,buildroot 编译时会去联网下载软件包,如果你的环境不能联网,一定要提前把下载好的dl/目录保留好,或者配置离线包源。
这个目录里的package/、utils/等子目录,对于普通应用开发来说可以暂时不动。但如果你想裁剪系统,减小最终 rootfs 镜像的体积,那configs/里每一项BR2_PACKAGE_*都值得认真过一遍。
3.4 device 与 target:板级配置的“总闸”
在我看来,整个 SDK 里最不能乱动、但也最容易出问题的目录就是device/。以 CQR40 平台为例,路径一般会深入到device/config/chips/cqr40/,下面可能还有configs/这样的子目录。这里存放的是芯片级和板级配置,包括系统配置文件(常见是sys_config.fex或更通用的config.fex)、分区表image.cfg、环境变量env.cfg,以及板级设备树文件。
这些文件会被打包脚本读取,直接影响最终镜像的分区布局、启动参数和外设配置。如果你需要调整某个 GPIO 的功能、换一个默认的屏幕分辨率,或者修改默认串口波特率,多半都要来这里改配置。target/目录则更像是一个“打包规则集合”,它定义了 out 目录下固件包的组成方式。
很多奇怪的问题,比如“内核起来了但 rootfs 挂不上”、“烧录完整个系统反复重启”,最后都能追溯到device/和target/之间的配置不一致。所以我分析 SDK 时,会把device/config/chips/cqr40下的文件全部列成一张速查表,包括每个文件对应哪个模块、被哪些脚本引用,这样后面定位问题会快很多。
3.5 scripts、tools 和 out:别忽略“胶水层”
scripts/和tools/是 SDK 的“胶水层”。顶层build.sh会调用一系列脚本,比如设置交叉编译环境、选择平台配置、执行各模块的make,最后再调用打包工具生成烧录镜像。tools/里常见的是烧录工具、签名工具和调试辅助工具。
分析这些脚本时不需要逐行读,重点看两个东西:变量定义和函数调用顺序。比如build.sh里那些export CROSS_COMPILE=...,就是决定你用哪个工具链的关键。另外,out/目录在首次编译前可能不存在,它是构建系统自动创建的。很多人第一眼看到 SDK 没有out/就以为自己拿到的是不完整的包,其实不是,跑一次构建后自然就有了。
我习惯把out/叫做“所有罪案的案发现场”。编译报错、打包失败、找不到文件,大部分日志都集中在这里。分析目录时,提前确认out/在什么路径、日志写到哪个子目录,能帮你省下不少找日志的时间。
4. 实操方法:从一棵目录树到一份分析文档
4.1 用工具快速生成全景目录
面对一个几十 GB 的 SDK,靠鼠标一层层点文件夹不是不行,但效率太低。我全程在 Linux 下操作,用的第一个命令就是tree:
tree -L 3 -d --dirsfirst-L 3表示最多只展开三层目录,-d表示只看目录不看文件。如果系统里没有 tree,可以用 find 代替:
find . -maxdepth 3 -type d | sort这一步的目标不是把目录全部塞进脑子里,而是快速识别出 SDK 的整体形状。输出结果可以保存成文件,比如tree.txt,后面写分析记录时直接用。不要一上来就递归完整目录,否则屏幕会直接刷到让人怀疑人生。拿到这份目录树之后,我再结合du -sh检查哪些子目录体积最大:
du -sh */体积大的往往是内核源码、buildroot 源码包、或者预编译产物。这些信息能帮助你判断一套 SDK 里哪些模块是“大头”,后续如果要裁剪镜像,心里会有数。
4.2 顺着编译脚本分析主流程
目录树只能告诉你“有什么”,但“怎么构建”要看脚本。我会用grep和编辑器把顶层build.sh读一遍,重点看它调用的函数名和涉及的脚本路径。
常见的方式是在脚本里搜索以下关键词:
grep -n "source\|\. \|export\|make\|function" build.sh实际分析时,我发现 CQR40 平台 SDK 的构建脚本会把一系列子模块的编译打包到一个总的流程里。它可能有多个分支,比如build.sh kernel、build.sh boot、build.sh rootfs、build.sh pack之类的命令。这个设计非常实用,意味着你不必每次全量编译。
顺着脚本找到变量定义也很关键,例如:
grep -n "CQR\|CROSS_COMPILE\|ARCH\|OUT_DIR" scripts/build_env.sh我把这些变量整理出来后,就会搞清楚 SDK 默认目标架构、输出目录和工具链。以后再看到“某个脚本突然找不到交叉编译器”,多半是环境变量没被正确 source。
4.3 建立关键路径速查表
分析文件夹不是看完就完了,我会把结果落成文档。我在 CQR40 平台的项目里维护了一个DIRECTORY.md,内容就是一个大表格,记录模块、源码目录、配置文件、编译命令、产物路径。
这个文档最大的价值在于“可检索”。几个月后你再回头改这个平台,不用重新翻一遍 SDK,直接打开DIRECTORY.md查表即可。下面是我当时记录的简化示例:
| 模块 | 源码/配置路径 | 常见操作 | 产物 |
|---|---|---|---|
| 引导程序 | brandy/ | 配置板级头文件 | uboot.bin、boot0 |
| 内核 | kernel/linux-x.y/ | make xxx_cqr40_defconfig | Image、dtb |
| 设备树 | device/config/chips/cqr40/ | 修改 dts/dtsi | dtb |
| 根文件系统 | buildroot/ | 修改 BR2_PACKAGE_ 选项 | rootfs 镜像 |
| 打包脚本 | scripts/、target/ | pack 命令 | 最终固件包 |
建立速查表要注意一点:路径必须写相对 SDK 根目录的相对路径,不要写你自己机器的绝对路径,否则换一台电脑整个文档就废了。
5. 常见问题与排查技巧实录
5.1 板级配置对不上,启动串口一片空白
CQR40 平台这套 SDK 里最让我头疼的问题是板级配置不一致。具体情况是:设备树里配的串口是 UART0,但sys_config.fex里默认配置却把引脚复用到了 UART3 上。内核起来后没有任何输出,你会以为内核压根没起来,其实内核已经在跑了,数据只是从另一个串口出去了。
排查思路非常直接:先确认整套 SDK 里同一个外设被配置了几个地方。常见位置有:
- 设备树
.dts/.dtsi device/config/chips/cqr40/configs/下的.fex或.json文件- Uboot 的板级头文件
然后用grep一次性搜出所有相关配置项:
grep -rn "uart0\|uart3\|s_uart" device/config/chips/cqr40/ arch/arm64/boot/dts/经验之谈,遇到启动无输出,先不要怀疑代码,先检查配置口径是否一致。
5.2 磁盘被 out 目录和 buildroot 灌满
SDK 编译起来非常吃磁盘。CQR40 平台完整编一遍,out/下可能会有将近 20GB 的中间产物,buildroot 还会额外膨胀。如果你还保留了历史构建产物,磁盘很容易爆。
我的清理策略是把“增量产物”和“最终产物”分开看。out/下包括各个模块的中间文件,真正要保留的是最终打包出来的固件。我通常会在清空out/之前先执行一次打包,把最终固件复制出来,然后再跑make clean或直接删掉中间目录。buildroot 则可以通过它自己的 clean 命令来局部清理:
make -C buildroot clean这个命令有时候会把编译好的工具链也清掉,重新编译很耗时,建议大家先用du -sh buildroot/*看清各子目录占用,再决定删到什么程度。
5.3 符号链接失效,导致内核路径找不到
CQR40 平台 SDK 里有一些目录是符号链接,比如kernel下的某个链接指向具体的内核版本目录。如果你把 SDK 从 Windows 环境拷贝到 Linux,或者用某些解压工具解压,符号链接可能会变成普通文本文件或直接失效。之后执行编译时,系统就找不到内核源码了。
遇到这种情况不用慌,先检查:
ls -l kernel/ file 某个链接如果确实失效了,用ln -sfn重新建立正确的链接关系。我踩过这个坑之后,就给自己立了条规矩:SDK 一律在 Linux 环境解压、复制、编译,绝不经过 Windows 中转。
5.4 新旧 SDK 目录结构差异
同一个硬件平台,不同版本的 SDK 目录结构可能变化很大。早期版本可能直接把内核放在顶层linux/下,新版本可能把内核挪到kernel/linux-x.y/下。跨版本升级时,如果你把旧路径背得滚瓜烂熟,容易在新 SDK 里碰壁。
我的处理方式是拿到新版后先做一次目录 diff,用自己整理的DIRECTORY.md去对比。优先关注这几处:构建脚本名称、芯片配置路径、烧录工具调用方式。只要这三样能对上,其他目录就算多几个少几个,影响都不大。
6. 从目录分析延伸到后续维护
6.1 让分析文档跟着项目一起演进
文件夹分析不是一次性工作。SDK 会更新、项目会定制、同事会踩坑,文档必须持续维护。我现在习惯把每次新发现的问题和对应目录路径直接追加到DIRECTORY.md里,而不是单独记在聊天记录或者草稿里。这样团队里的新人拿到文档后,可以少走很多弯路。
6.2 为自动化构建做准备
目录分析清楚之后,可以顺手把关键变量固化下来,做一套可重复的构建脚本,甚至接入 CI。当时我基于这份分析文档,把“拉代码 → 配置交叉编译环境 → 编译内核 → 编译 buildroot → 打包”写成了一条可复现的流水线。有了这一步,想每次提交代码后自动出固件,就非常轻松了。
6.3 根据目录热点指导系统裁剪
最后再说一个延伸用途。目录体积分析能直观反映哪些模块占空间大。如果做量产项目需要精简固件,我就直接对照du的结果和 buildroot 的配置项,把没用的测试工具、示例程序、大体积可选库从配置里去掉。这个操作不需要动代码,只需要知道每个包在 buildroot 里的开关名就行。CQR40 平台上的 rootfs 一旦删除无用包,体积可以降得很明显,系统启动速度也会提升。
我个人在多次 SDK 分析之后最大的体会是:目录结构不是摆设,它就是芯片厂商设计思路的说明书。把这份说明书读透,比拿源码硬啃要高效得多。以后再拿到一套陌生 SDK,花两天整理目录和构建流程,绝对是一笔划算的投资。