4. 设备树优化:精简设备树节点、移除未使用外设、使用设备树覆盖(overlay)

设备树(Device Tree,DT)是嵌入式 Linux 系统中描述硬件拓扑和资源的关键数据结构。内核在启动早期需要解析整个设备树二进制(DTB),为每个 compatible 匹配的节点创建 struct platform_device,并执行内存映射、中断注册、时钟使能等初始化操作。设备树节点越多、层次越深,内核的解析耗时就越长。本章聚焦于通过精简设备树来压缩启动时间,核心手段包括:移除未使用外设节点、合并冗余属性、以及利用设备树覆盖(overlay)实现运行时按需加载。

优化目标:将设备树解析与设备创建阶段(从 start_kernel 到 late_initcall)的时间减少 30%–50%,典型场景下可节省 50–200 ms(取决于 SoC 外设数量)。

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% 小(依赖架构)
经验法则:每减少 100 个设备节点,内核在 device_initcall 阶段的耗时平均减少 15–25 ms。对于包含 500+ 节点的复杂 SoC,精简至 200 个以内可带来显著收益。

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 在启动流程中的位置与传统方式的对比:

设备树 Overlay 按需加载 vs 传统全量加载 传统方式:全量 DTB 一次性加载 Bootloader 加载 DTB 内核解析所有节点 创建所有 platform_device 用户空间启动 Overlay 方式:最小 DTB + 按需加载 Bootloader 加载最小 DTB 仅解析核心节点 创建核心设备 用户空间启动 用户态加载 overlay 动态创建设备 ⏱ 传统方式:设备创建在 initcall 阶段完成(阻塞启动) | 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 阶段预加载
性能收益:将 3 个非关键外设(如音频、蓝牙、以太网 PHY)通过 overlay 延迟加载,实测可减少内核 initcall 阶段耗时约 45 ms,同时用户空间启动时间提前约 30 ms。

4.4 设备树优化实战清单

以下清单总结了设备树优化的关键步骤与验证方法:

优化项 操作方式 预期收益 风险等级
移除未使用外设节点 在 .dts 中删除或设置 status = "disabled" 高(每节点节省 0.5–2 ms) 低(需确认硬件未连接)
精简冗余属性 移除 reg-names、未使用的 clocks 等 中(减少字符串解析) 中(需验证驱动依赖)
合并同类型节点 将多个 GPIO 按键合并为单一节点 低(减少节点数量) 低
使用 overlay 延迟加载 将非核心外设编译为 .dtbo,用户态加载 高(推迟 50–100 ms 设备创建) 中(需用户态工具支持)
裁剪 dtsi 包含 移除未使用的 #include 头文件 低(减少编译时间) 低

4.5 验证与测量方法

优化后必须通过以下方式验证设备树解析时间的实际变化:

  1. 使用 initcall_debug 内核参数:在 kernel cmdline 中添加 initcall_debug,通过 dmesg 查看每个 initcall 的耗时。
  2. ftrace 追踪 of 相关函数:trace-cmd record -p function_graph -g of_platform_populate 可精确测量设备树遍历时间。
  3. 对比 DTB 大小:精简后的 DTB 文件大小通常减少 20%–40%,可作为间接指标。
  4. bootgraph 可视化:使用 bootgraph.py 生成启动时间分布图,直观对比优化前后差异。
案例数据:某 i.MX8M 平台通过移除 47 个未使用外设节点、精简 120+ 冗余属性,并将 GPU/VPU 改为 overlay 加载,DTB 大小从 68 KB 降至 41 KB,内核 initcall 阶段耗时从 320 ms 降至 195 ms,启动总时间缩短 125 ms。

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 属性。
重要:设备树优化必须在硬件验证板上进行充分测试,特别是移除节点后要确认对应外设的电源域不会异常挂起,以及系统 suspend/resume 流程不受影响。建议在优化前后各运行一次完整的硬件自检程序。