Linux 内核 Devicetree Overlay 完全指南:动态修改设备树的原理、API 与实战

发布时间:2026/9/17 11:50:26
Linux 内核 Devicetree Overlay 完全指南:动态修改设备树的原理、API 与实战 Linux 内核 Devicetree Overlay 完全指南动态修改设备树的原理、API 与实战【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读本文基于 Linux 内核源码仓库中的 Documentation/devicetree/overlay-notes.rst 官方文档系统讲解内核态 Device Tree Overlay设备树叠加层机制的实现原理与编程接口。该机制允许在系统运行期间向内核的活设备树live tree动态注入或移除设备节点从而在不重启、不改写基础设备树的前提下完成外设的插拔与配置变更。读完本文你将掌握 overlay 的编译前提-选项与/plugin/标记、按 label 与按显式路径两种定位语法、of_overlay_fdt_apply()/of_overlay_remove()/of_overlay_remove_all()三个核心内核 API、overlay 操作通知链的完整生命周期以及 overlay 内存释放导致悬垂指针这一最容易踩的坑。Overlay 的工作原理对活设备树做增量修改Overlay 的用途是对内核的活设备树进行修改并且让修改真实反映到内核状态中。由于内核主要与设备打交道因此凡是新增的设备节点只要能够激活对应设备就应该创建设备如注册 platform device凡是设备节点被 disable 或整体删除受影响的设备就应该被注销deregister。这套逻辑由内核态 overlay 功能实现核心代码位于 drivers/of/overlay.c它是 Documentation/devicetree/dynamic-resolution-notes.rst动态解析器说明文档的姊妹篇——overlay 负责改树resolver 负责解引用。一个完整的 overlay 示例foo 板卡 bar 外设假设有一块 foo 板卡其基础设备树foo.dts如下/* FOO platform */ /dts-v1/; / { compatible corp,foo; /* shared resources */ res: res { }; /* On chip peripherals */ ocp: ocp { /* peripherals that are always instantiated */ peripheral1 { ... }; }; };现在要叠加一个外设baroverlay 源文件bar.dtso通过label 定位目标节点/dts-v1/; /plugin/; ocp { /* bar peripheral */ bar { compatible corp,bar; ... /* various properties and child nodes */ }; };注意两个关键点必须声明/plugin/;——这是告诉 dtc 编译器按 overlay 模式编译生成__fixups__与__local_fixups__节点供内核解析器使用ocp引用了基础树中的 labelocp即叠加目标位置。当 overlay 被加载并按 dynamic-resolution-notes.rst 描述的方式完成解析后等效的合并结果foobar.dts为/* FOO platform bar peripheral */ / { compatible corp,foo; /* shared resources */ res: res { }; /* On chip peripherals */ ocp: ocp { /* peripherals that are always instantiated */ peripheral1 { ... }; /* bar peripheral */ bar { compatible corp,bar; ... /* various properties and child nodes */ }; }; };叠加的结果是新增了设备节点bar因此内核会注册一个 bar 的 platform device只要存在匹配的驱动程序设备就会被正常创建。label 与显式路径两种定位语法如果基础设备树在编译时没有使用-选项即未生成__symbols__节点那么ocp这个 label 就无法用于把 overlay 节点解析到基础树中的正确位置。此时可以改用显式路径target path语法/dts-v1/; /plugin/; {/ocp} { /* bar peripheral */ bar { compatible corp,bar; ... /* various properties and child nodes */ } };两种方式各有取舍语法写法适用场景优点按 labelocp { ... };基础树带-编译具备__symbols__只要基础树包含该 label无论 label 位于树中何处都能解析可移植性强按显式路径{/ocp} { ... };基础树未启用-不依赖 label 符号直接以绝对路径锚定目标文档明确指出按 label 定位是首选写法因为它对任意包含该 label 的基础设备树都适用与 label 在树中的具体位置无关。内核自带的单元测试也印证了两种语法并存例如 drivers/of/unittest-data/overlay_0.dtso 就是使用绝对路径定位目标的用例// SPDX-License-Identifier: GPL-2.0 /dts-v1/; /plugin/; /* overlay_0 - enable using absolute target path */ {/testcase-data/overlay-node/test-bus/test-unittest0} { status okay; };背后的一步动态解析器如何接上引用Overlay 能按 label 定位离不开 drivers/of/resolver.c 中的解析器。其工作流程在 dynamic-resolution-notes.rst 中有精炼描述取活设备树中的最大 phandle 值并加 1作为 phandle 偏移基准把 overlay 树中所有本地 phandle 整体偏移该值依据__local_fixups__节点信息把所有本地引用同步偏移同样的量针对__fixups__节点中的每个属性在活设备树中定位其引用的节点——这就是用于标记节点的 label取出 fixup 目标节点的 phandle针对每个 fixup 记录的node:property:offset位置将原值替换为目标 phandle。对应到源码drivers/of/resolver.c 中的of_resolve_phandles()正是从live_tree_max_phandle() 1开始计算phandle_delta然后依次执行adjust_overlay_phandles()与adjust_local_phandle_references()。而 overlay 应用流程会通过 drivers/of/overlay.c 中的of_overlay_mutex_lock()/unlock()保护解析 应用这一临界区防止两个 overlay 并发叠加时 phandle 冲突对应源码中DEFINE_MUTEX(of_overlay_phandle_mutex)的注释说明。Overlay 内核态 API应用、移除与批量清理内核态 API 十分简洁只有三个核心入口全部定义在 drivers/of/overlay.c 中并以EXPORT_SYMBOL_GPL导出1.of_overlay_fdt_apply()—— 创建并应用 overlay changesetint of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size, int *ret_ovcs_id, const struct device_node *base);参数与返回值overlay_fdt/overlay_fdt_size编译好的 overlay FDT 二进制数据及其字节大小ret_ovcs_id输出参数成功时返回标识该 overlay 的 cookieoverlay changeset id后续of_overlay_remove()需要用到它base叠加目标基准节点通常传NULL表示以根节点为基础返回值0表示成功否则为负的错误码。从源码实现看该函数内部会完成 FDT 对齐、复制、展开unflatten、创建struct overlay_changeset再调用静态函数of_overlay_apply()。值得注意的错误语义源码注释明确说明如果返回错误changeset 可能已被部分应用尤其是当OF_OVERLAY_POST_APPLY通知回调返回错误时此时调用方应使用*ret_ovcs_id中的值继续调用of_overlay_remove()来做清理而不能直接放弃。这正是 drivers/of/overlay.c 中出错时不要走err_free_ovcs路径的注释所强调的原因。2.of_overlay_remove()—— 移除并清理 overlay changesetint of_overlay_remove(int *ovcs_id);参数ovcs_id指向of_overlay_fdt_apply()返回的 cookie注意传的是指针若*ovcs_id 0直接返回 0源码中的空操作处理关键限制如果一个 overlay 的 changeset 被另一个 overlay 叠加stacked在其上则不允许直接移除它。源码中overlay_removal_is_ok()会检查是否存在非最顶层重叠重叠时会打印overlay #%d is not topmost并拒绝移除见 drivers/of/overlay.c。3.of_overlay_remove_all()—— 一次性移除全部 overlayint of_overlay_remove_all(void);当需要一次性清空所有 overlay 时使用。实现上从列表尾部开始反向遍历逐个调用of_overlay_remove()——源码注释明确列表尾部保证是可以安全移除的这样天然满足先移除后叠加的 overlay的正确顺序见 drivers/of/overlay.c。错误状态粘滞changeset 部分失败后的内核保护源码中还有一个容易被忽略的细节devicetree_state_flags是粘滞标志sticky一旦置位不再清除DTSF_APPLY_FAIL应用失败与DTSF_REVERT_FAIL回退失败一旦置位devicetree_corrupt()就会持续返回真此后相关 overlay 操作会被拒绝。原因在于如果 changeset 应用或回退过程中出错内核尝试撤销部分变更但可能失败此时设备树状态未知任何后续补救都可能让情况更糟见 drivers/of/overlay.c 的注释与实现。Overlay 操作通知链观察每一次 apply / removeof_overlay_fdt_apply()/of_overlay_remove()之外还有一套通知机制of_overlay_notifier_register()与of_overlay_notifier_unregister()二者都是对内核blocking_notifier_chain的封装见 drivers/of/overlay.c通知动作类型定义在 include/linux/of.h 的enum of_overlay_notify_actionenum of_overlay_notify_action { OF_OVERLAY_INIT 0, /* kzalloc() of ovcs sets this value */ OF_OVERLAY_PRE_APPLY, OF_OVERLAY_POST_APPLY, OF_OVERLAY_PRE_REMOVE, OF_OVERLAY_POST_REMOVE, };对应的人类可读名称由of_overlay_action_name()提供init、pre-apply、post-apply、pre-remove、post-remove。每个 overlay 的完整生命周期通知顺序为OF_OVERLAY_INIT → OF_OVERLAY_PRE_APPLY → OF_OVERLAY_POST_APPLY →应用后长期运行 → OF_OVERLAY_PRE_REMOVE → OF_OVERLAY_POST_REMOVE通知回调携带的数据结构struct of_overlay_notify_data包含overlayoverlay 节点与target目标节点两个指针由 overlay.c 中的overlay_notify()在遍历每个 fragment 时填充并广播。通知回调中的指针生命周期红线文档用较大篇幅强调了指针生命周期这一核心约束这是 overlay 使用中最容易产生内存安全问题的部分overlay 通知回调of_overlay_notifier_*回调中可以保存指向 overlay 设备树节点或内容的指针但前提是这些指针不得存活超过OF_OVERLAY_POST_REMOVE通知回调。因为OF_OVERLAY_POST_REMOVE通知返回后容纳 overlay 的内存会被kfree()释放而且即使该通知回调返回错误内存同样会被释放错误不会阻止释放。changeset 通知器位于 drivers/of/dynamic.c即OF_RECONFIG_*系列这是第二类可能因 overlay 应用/移除而被触发的通知器。它们不允许保存指向 overlay 节点或内容的指针——overlay 代码不会保护这类指针在内存释放后仍然存活的情况。其他任何保留了 overlay 节点或数据指针的代码都被视为 bug因为 overlay 被移除后这些指针指向的是已释放的内存悬垂指针任何解引用都会导致未定义行为。一个典型误用场景文档特别举了一个无心之失的例子如果某个驱动或子系统模块是在 overlay已应用之后才加载的而该模块会扫描整棵设备树或其中很大一部分包括 overlay 节点那么它就可能在不知情的情况下持有 overlay 节点的指针。一旦 overlay 被移除、相关内存被kfree()这些指针就悬空了。因此overlay 的用户必须对系统上的整体操作保持警觉确保其他内核代码不会保留指向 overlay 节点或数据的指针。从内核 API 到用户态overlay 的完整数据通路结合源码可以把 overlay 从 FDT 二进制到活设备树的完整数据通路串起来编译期bar.dtso用-以及/plugin/标记编译dtc 生成__fixups__外部 label 引用与__local_fixups__树内引用节点解析期of_overlay_fdt_apply()内部经of_resolve_phandles()drivers/of/resolver.c完成 phandle 偏移与引用替换构建期init_overlay_changeset()递归遍历 overlay 子树对照活设备树生成struct of_changeset条目OF_RECONFIG_ATTACH_NODE/OF_RECONFIG_ADD_PROPERTY/OF_RECONFIG_UPDATE_PROPERTY等动作重复添加同一节点/属性会被-EINVAL拒绝见 overlay.c 中find_dup_cset_node_entry()/find_dup_cset_prop()通知期PRE_APPLY→ 应用 changeset调用 drivers/of/dynamic.c 中的 changeset 应用逻辑触发OF_RECONFIG_*通知→POST_APPLY设备生命周期新节点生效后platform 总线扫描并注册新设备匹配驱动后创建设备节点被 disable/删除则对应设备被注销。内核 KUnit 测试可以佐证这一流程例如 drivers/of/kunit_overlay_test.dtso 是一个最简 overlay/plugin/{/} { kunit-test { compatible test,empty; }; };被 overlay KUnit 测试套件加载应用drivers/of/unittest-data/ 目录下还有大量overlay_*.dtso测试用例分别覆盖按 label、按绝对路径、属性修改、节点禁用等多种叠加场景是学习 overlay 行为的最佳实践样例。使用注意事项与最佳实践总结结合官方文档与源码实现使用 overlay 时应遵守以下要点编译基础树时尽量启用-否则只能使用显式路径语法定位目标且无法享受label 与位置无关的可移植性优先按 label 定位overlay 可复用于任何包含该 label 的基础树记住移除顺序约束后叠加的 overlay 必须先移除of_overlay_remove()对非最顶层的移除会直接拒绝需要批量清理时用of_overlay_remove_all()严格遵守指针生命周期overlay 通知回调中的节点指针不得存活到OF_OVERLAY_POST_REMOVE之后changeset 通知器则一律不得持有这类指针其他内核代码也不得保留指向 overlay 节点/数据的指针否则 overlay 移除后即成为悬垂指针错误处理要完整of_overlay_fdt_apply()返回错误时 changeset 可能已部分应用应依据*ret_ovcs_id调用of_overlay_remove()收尾若devicetree_corrupt()已置位应用/回退失败且粘滞标志生效设备树状态视为不可知相关操作会被拒绝警惕事后扫描型模块在 overlay 应用后才加载、且会全树扫描的驱动或子系统最容易在不知情的情况下持有已释放的 overlay 指针属于文档点名的典型误用。掌握了 Overlay 的定位语法、三个核心 API、通知链生命周期与指针约束你就可以在内核模块中安全地实现运行时动态配置设备树的能力——无论是热插拔外设、运行时启用/禁用节点还是为开发板动态注入测试节点这套机制都是 Linux 设备模型下标准且受官方文档支持的解决方案。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考