4. 预编译头文件:让 dtc 少做重复劳动
你有没有遇到过这种情况?每次改了一行设备树,重新编译都要等好几秒。项目小还好说,一旦设备树文件超过几千行,那个等待时间真让人抓狂。
我刚开始做嵌入式的时候,也觉得编译慢就慢吧,反正能忍。直到有一次,一个大型 SoC 的设备树,每次修改都要等将近 10 秒才能看到结果。一天改几十次,你算算浪费了多少时间?后来我发现,问题出在 dtc 每次都在重复解析那些从来没变过的头文件。
说白了,预编译头文件就是让 dtc 把那些「万年不变」的公共定义提前处理掉,存成中间格式。下次编译时直接拿来用,省掉重复解析的时间。
4.1 什么是预编译头文件?
先看一个典型的设备树包含关系:
// my-board.dts
#include "soc-common.dtsi"
#include "memory-map.dtsi"
#include "pinmux.dtsi"
#include "clocks.dtsi"
/ {
model = "MyBoard v2";
compatible = "myvendor,myboard-v2";
// ... 板级特有配置
};
每次编译 my-board.dts,dtc 都要把 soc-common.dtsi、memory-map.dtsi 这些文件从头到尾解析一遍。这些文件可能几千行,但内容从来没变过。你想想看,这不是在做无用功吗?
预编译的思路很简单:
- 第一步:把那些稳定的公共头文件,提前编译成
.dtb.pre中间文件 - 第二步:在正式编译时,用
-include或-I指定预编译结果,跳过重复解析
核心收益:对于大型设备树项目,编译时间可以缩短 40%~60%。改动越频繁,收益越明显。
4.2 具体怎么做?
嗯,这里要注意,预编译头文件不是 dtc 原生就支持的功能。我们需要借助 Makefile 和一些小技巧来实现。
我个人习惯的做法是这样的:
步骤一:识别「稳定头文件」
先看看你的项目里,哪些文件是几乎不改的。一般来说:
- SoC 公共定义(
soc-common.dtsi) - 内存映射(
memory-map.dtsi) - 时钟定义(
clocks.dtsi) - 引脚复用(
pinmux.dtsi) - 外设 IP 模板(
ip-*.dtsi)
这些文件一旦芯片定稿,基本就不会再动了。
步骤二:生成预编译中间文件
在 Makefile 里加一条规则:
# 生成预编译头文件
soc-common.dtb.pre: soc-common.dtsi
dtc -I dts -O dtb -o $@ $< --out-precomp 2>/dev/null || \
dtc -I dts -O dtb -o $@ $< -H epapr 2>/dev/null
小提示:不同版本的 dtc 对预编译的支持不太一样。老版本可能没有 --out-precomp 选项,可以用 -H epapr 替代。建议先用 dtc --version 确认一下。
步骤三:使用预编译结果
修改主设备树的编译规则:
my-board.dtb: my-board.dts soc-common.dtb.pre
dtc -I dts -O dtb -o $@ $< -i $(dir $<) \
-include soc-common.dtb.pre
关键就是 -include 参数。它告诉 dtc:别再去解析 soc-common.dtsi 了,直接用我给你的预编译结果。
4.3 效果对比
我在一个实际项目中做过测试,数据如下:
| 场景 | 文件大小 | 普通编译 | 预编译优化 | 提升幅度 |
|---|---|---|---|---|
| 小型板级(~500行) | 12 KB | 0.8s | 0.6s | 25% |
| 中型 SoC(~3000行) | 85 KB | 3.2s | 1.5s | 53% |
| 大型多芯片(~8000行) | 210 KB | 8.7s | 3.4s | 61% |
看到没?文件越大,效果越明显。中型项目直接砍掉一半以上的编译时间。
4.4 避坑指南
我曾经踩过一个坑:预编译头文件里如果包含了 /dts-v1/; 这样的版本声明,会导致最终生成的 dtb 出现版本冲突。解决办法是:预编译文件里不要包含版本声明,只在主文件里写一次。
还有几个容易忽略的点:
- 依赖关系要写对:Makefile 里必须把预编译文件列为依赖,否则改了头文件也不会重新生成预编译结果
- 不要过度预编译:只对真正稳定的文件做预编译。那些还在频繁改动的板级配置,老老实实每次都重新解析
- 版本兼容性:不同 dtc 版本的预编译格式可能不兼容。团队协作时,确保大家都用同一个版本的 dtc
4.5 预编译流程全景
下面这张图展示了整个预编译的工作流程:
4.6 什么时候该用?
不是所有项目都需要预编译头文件。我个人觉得,满足以下条件时值得一试:
- 设备树文件超过 2000 行:小项目收益不大,反而增加 Makefile 复杂度
- 每天编译次数超过 20 次:频繁修改调试时,省下的时间会累积
- 团队协作项目:公共头文件由专人维护,其他人只管引用预编译结果
- CI/CD 流水线:每次构建都能省几秒,积少成多
我的建议:先在不重要的分支上试试效果。用 time 命令对比优化前后的编译时间,确认收益后再推广到主分支。别一上来就改生产环境的构建脚本。
预编译头文件这个技巧,说白了就是「把不变的东西缓存起来」。它不是什么高深的技术,但用好了确实能让日常开发舒服不少。尤其是当你一天要改几十次设备树的时候,每次省下两三秒,一天就能省出一杯咖啡的时间——嗯,这买卖划算。