4. 设备树优化:精简设备树节点、移除未使用外设、使用设备树覆盖(overlay)
设备树(Device Tree,DT)是嵌入式 Linux 系统中描述硬件拓扑和资源的关键数据结构。内核在启动早期需要解析整个设备树二进制(DTB),为每个 compatible 匹配的节点创建 struct platform_device,并执行内存映射、中断注册、时钟使能等初始化操作。设备树节点越多、层次越深,内核的解析耗时就越长。本章聚焦于通过精简设备树来压缩启动时间,核心手段包括:移除未使用外设节点、合并冗余属性、以及利用设备树覆盖(overlay)实现运行时按需加载。
4.1 设备树解析的性能开销分析
内核在 start_kernel → setup_arch → unflatten_device_tree() 中将 FDT(扁平设备树)展开为树状结构,随后在 do_initcalls() 阶段由各驱动总线的 initcall 遍历设备树节点并创建设备。主要耗时环节如下:
| 阶段 | 主要操作 | 耗时占比(典型) | 优化空间 |
|---|---|---|---|
| FDT 展开 | 内存分配、字符串复制、属性解析 | 10%–15% | 中等(减少节点数) |
| 设备节点匹配 | 遍历 compatible 表、调用 driver probe | 40%–50% | 大(移除未使用节点) |
| 资源映射 | ioremap、中断申请、时钟使能 | 25%–35% | 中等(按需延迟) |
| 子系统初始化 | GPIO、I2C、SPI 等总线注册 | 10%–15% | 小(依赖架构) |
4.2 精简设备树节点的具体方法
4.2.1 移除未使用外设节点
产品最终固件通常只使用 SoC 内部外设的 30%–60%。在设备树源文件(.dts)中直接删除或注释掉未使用的节点,是最直接有效的优化手段。常见可移除对象包括:
- 未连接的显示控制器(Display Controller)
- 未使用的 USB 控制器或 PHY
- 多余的 I2C/SPI 总线及其从设备
- 未启用的 GPU/VPU 加速器
- 调试接口(JTAG、Serial Debug)
- 未使用的音频编解码器(Codec)
示例:原始设备树包含两个 USB 控制器,产品仅使用 USB0:
/* 原始 dtsi 片段 */
usb0: usb@fe800000 {
compatible = "snps,dwc3";
reg = <0x0 0xfe800000 0x0 0x10000>;
status = "okay";
};
usb1: usb@fe900000 {
compatible = "snps,dwc3";
reg = <0x0 0xfe900000 0x0 0x10000>;
status = "okay"; /* 产品未使用,应移除或设为 disabled */
};
优化后:
/* 产品 dts 覆盖 */
&usb1 {
status = "disabled"; /* 或直接删除整个节点 */
};
4.2.2 合并与精简属性
部分设备树属性在运行时不会被内核使用,可以移除或合并以减少解析开销:
- 移除
status属性:对于默认启用的节点,省略status = "okay"可减少字符串比较。 - 合并
reg与reg-names:如果驱动不使用命名寄存器区域,可移除reg-names。 - 精简
clocks与resets:只保留驱动实际使用的时钟/复位信号。 - 移除
pinctrl-*默认状态:如果 bootloader 已配置引脚复用,可省略内核侧的 pinctrl 设置。
drivers/ 下对应驱动的 of_match_table 和 of_property_read_* 调用链来验证。错误的精简可能导致驱动 probe 失败或系统崩溃。
4.3 设备树覆盖(Overlay)实现按需加载
设备树覆盖允许在系统运行时动态加载/卸载设备节点,而无需重新编译整个 DTB。对于启动时间优化,overlay 的核心价值在于:将非关键外设的初始化推迟到用户空间启动之后,从而压缩内核启动阶段的设备创建时间。
4.3.1 Overlay 的工作原理
下图展示了 overlay 在启动流程中的位置与传统方式的对比:
4.3.2 创建与使用设备树覆盖
步骤 1:编写 overlay 源文件(.dtso)
// usb1-overlay.dtso
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &usb1;
__overlay__ {
status = "okay";
// 可在此添加额外属性
maximum-speed = "high-speed";
};
};
};
步骤 2:编译 overlay
# 使用 dtc 编译 .dtso 为 .dtbo
dtc -@ -I dts -O dtb -o usb1-overlay.dtbo usb1-overlay.dtso
步骤 3:在用户空间加载 overlay
# 通过 configfs 接口加载(内核需配置 CONFIG_OF_OVERLAY=y)
mkdir /sys/kernel/config/device-tree/overlays/usb1
cat usb1-overlay.dtbo > /sys/kernel/config/device-tree/overlays/usb1/dtbo
# 或使用 libubootenv 等工具在 bootloader 阶段预加载
4.4 设备树优化实战清单
以下清单总结了设备树优化的关键步骤与验证方法:
| 优化项 | 操作方式 | 预期收益 | 风险等级 |
|---|---|---|---|
| 移除未使用外设节点 | 在 .dts 中删除或设置 status = "disabled" |
高(每节点节省 0.5–2 ms) | 低(需确认硬件未连接) |
| 精简冗余属性 | 移除 reg-names、未使用的 clocks 等 |
中(减少字符串解析) | 中(需验证驱动依赖) |
| 合并同类型节点 | 将多个 GPIO 按键合并为单一节点 | 低(减少节点数量) | 低 |
| 使用 overlay 延迟加载 | 将非核心外设编译为 .dtbo,用户态加载 | 高(推迟 50–100 ms 设备创建) | 中(需用户态工具支持) |
| 裁剪 dtsi 包含 | 移除未使用的 #include 头文件 |
低(减少编译时间) | 低 |
4.5 验证与测量方法
优化后必须通过以下方式验证设备树解析时间的实际变化:
- 使用 initcall_debug 内核参数:在 kernel cmdline 中添加
initcall_debug,通过 dmesg 查看每个 initcall 的耗时。 - ftrace 追踪 of 相关函数:
trace-cmd record -p function_graph -g of_platform_populate可精确测量设备树遍历时间。 - 对比 DTB 大小:精简后的 DTB 文件大小通常减少 20%–40%,可作为间接指标。
- bootgraph 可视化:使用
bootgraph.py生成启动时间分布图,直观对比优化前后差异。
4.6 常见陷阱与避坑指南
- 陷阱 1:移除节点导致驱动 probe 顺序依赖问题。某些驱动依赖其他设备的
clk或reset信号,移除后可能引发 probe 失败。建议先通过depends-on或device_link检查依赖链。 - 陷阱 2:overlay 加载时序冲突。如果用户空间在 overlay 加载前就尝试访问对应设备节点,会导致文件节点不存在错误。需确保 overlay 加载脚本在相关服务启动前执行。
- 陷阱 3:
status = "disabled"与直接删除的区别。设置为 disabled 的节点仍会被内核解析并占用内存,只是不创建设备。对于不使用的节点,直接删除更彻底。 - 陷阱 4:overlay 与电源管理冲突。部分 SoC 的电源域(power domain)在设备 probe 时才会使能。如果 overlay 加载时对应电源域未初始化,可能导致设备无法正常工作。需在 overlay 中显式引用
power-domains属性。