尧图精选

Linux 内核 KUnit Platform Device API:在内核单元测试中注册平台设备与平台驱动的完整实践

🕒 发布时间:2026/9/16 14:08:19 📁 来源:尧图网络
Linux 内核 KUnit Platform Device API在内核单元测试中注册平台设备与平台驱动的完整实践【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxKUnit 是 Linux 内核自带的轻量级单元测试框架而 Platform Device API 是它面向驱动测试提供的一组专用辅助接口允许测试代码在运行时创建并注册platform_device、注册platform_driver并等待其 probe 完成。读完本文你将掌握 KUnit Platform Device API 文档 所定义的全部 6 个接口kunit_platform_device_alloc、kunit_platform_device_add、kunit_platform_device_register_full、kunit_platform_device_unregister、kunit_platform_device_prepare_wait_for_probe、kunit_platform_driver_register的用法、生命周期语义与引用计数细节并能参考仓库内真实测试 lib/kunit/platform-test.c 写出自己的平台驱动 KUnit 测试。一、这套 API 解决什么问题平台型驱动platform driver的标准工作流是平台设备platform_device被注册到 platform 总线后总线机制自动匹配同名的platform_driver并调用其probe()。但要在单元测试里验证一个平台驱动的 probe/remove 逻辑测试代码必须能够动态创建一个平台设备指定 name 和 id可携带 devm 资源、DT node、设备树 overlay 等把它挂到总线上触发驱动匹配可靠地等到 probe 真正执行完毕再进行断言测试结束时自动清理无论测试成功还是失败都不泄漏设备引用。KUnit 的 Platform Device API 正是围绕这四件事构建的。它的实现全部位于 lib/kunit/platform.c约 365 行对外声明在 include/kunit/platform_device.hAPI 文档则由 Documentation/dev-tools/kunit/api/platformdevice.rst 通过kernel-doc指令直接从源码注释导出属于 KUnit API 参考文档中的 Driver KUnit API 一部分见 Documentation/dev-tools/kunit/api/index.rst与 clk API、OF API 并列。从 lib/kunit/Makefile 可以看到构建关系platform.o在CONFIG_KUNIT开启时编入kunit.o第 13 行而自测文件platform-test.o需要CONFIG_KUNIT_TEST第 25 行。相关 Kconfig 选项定义在 lib/kunit/KconfigKUNITtristate总开关、KUNIT_TESTKUnit 框架自测、KUNIT_ALL_TESTS启用所有满足依赖的 KUnit 测试等。接口总览函数作用返回值清理时机kunit_platform_device_alloc()按 name/id 分配但不注册平台设备设备指针失败为 NULL测试结束时platform_device_put()kunit_platform_device_add()把已分配的设备注册到 platform 总线0 成功负 errno 失败测试结束时自动platform_device_unregister()kunit_platform_device_register_full()通过struct platform_device_info一步完成分配注册设备指针失败为 IS_ERR测试结束时自动反注册kunit_platform_device_unregister()手动反注册设备并撤销测试结束时的自动清理无立即kunit_platform_device_prepare_wait_for_probe()挂接总线通知使 completion 在设备 probe 完成时被 complete0 成功负 errno 失败测试结束时自动注销通知块kunit_platform_driver_register()注册测试用平台驱动0 成功负 errno 失败测试结束时自动platform_driver_unregister()所有函数的清理动作都建立在 KUnit 的资源/动作resource/action机制之上实现见 lib/kunit/resource.c即注册即入栈、测试结束逆序执行清理函数这保证了即使测试中途KUNIT_ASSERT失败内核也不会残留半注册的设备。二、创建设备kunit_platform_device_alloc()struct platform_device * kunit_platform_device_alloc(struct kunit *test, const char *name, int id);语义引自 lib/kunit/platform.c#L41-L50 的 kernel-doc 注释分配一个测试托管的平台设备设备将在测试完成时被platform_device_put()释放。name是设备名后续与驱动名匹配id是设备编号常用-1此时设备名为name而非name.id。从源码结构看它本质上是对platform_device_alloc()的一次资源包装lib/kunit/platform.c#L20-L32kunit_platform_device_alloc_init中调用platform_device_alloc(params-name, params-id)把pdev存入kunit_resource-datakunit_platform_device_alloc_exit在测试结束时执行platform_device_put(pdev)。因此它的内存开销极小仅占一个kunit_resource槽位。配套测试见 lib/kunit/platform-test.c#L14-L18static void kunit_platform_device_alloc_test(struct kunit *test) { KUNIT_EXPECT_NOT_ERR_OR_NULL(test, kunit_platform_device_alloc(test, kunit-platform, 1)); }注意该 API 只分配、不注册此时设备还不在总线上任何驱动都不会匹配它。三、注册设备kunit_platform_device_add() 与引用计数迁移int kunit_platform_device_add(struct kunit *test, struct platform_device *pdev);调用platform_device_add(pdev)把设备挂上 platform 总线并登记测试结束时调用platform_device_unregister()的清理动作lib/kunit/platform.c#L94-L131。这里有一段非常关键的实现细节源码注释lib/kunit/platform.c#L104-L123解释得很清楚如果pdev是此前用kunit_platform_device_alloc()分配的那么分配时的那个引用已经登记为测试结束时的platform_device_put()。而platform_device_add()成功后按内核惯例这个引用应移交由platform_device_unregister()负责释放unregister内部会platform_device_put()。若两个清理动作都执行就会发生引用计数下溢underflow。因此kunit_platform_device_add()会用kunit_platform_device_alloc_match()在资源栈中查找该设备对应的 alloc 资源匹配条件res-data pdev res-free kunit_platform_device_alloc_exit见 lib/kunit/platform.c#L73-L80找到后把该资源的释放函数改写为kunit_platform_device_add_exit即调用platform_device_unregister。如果设备不是通过 KUnit alloc 的例如测试自己手工platform_device_alloc则退而求其次kunit_add_action_or_reset()直接登记platform_device_unregister作为动作包装器KUNIT_DEFINE_ACTION_WRAPPER(platform_device_unregister_wrapper, ...)lib/kunit/platform.c#L82-L83。官方验证用例lib/kunit/platform-test.c#L24-L37检查了注册后设备的三要素pdev kunit_platform_device_alloc(test, name, id); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); KUNIT_EXPECT_EQ(test, 0, kunit_platform_device_add(test, pdev)); KUNIT_EXPECT_TRUE(test, dev_is_platform(pdev-dev)); KUNIT_EXPECT_STREQ(test, pdev-name, name); KUNIT_EXPECT_EQ(test, pdev-id, id);两个值得注意的行为同名同 id 注册两次会失败——lib/kunit/platform-test.c#L43-L57 用kunit_platform_device_add_twice_fails_test验证了第二次kunit_platform_device_add()返回非 0。测试结束自动清理——lib/kunit/platform-test.c#L67-L102 的kunit_platform_device_add_cleans_up构造了一个 fakekunit上下文注册设备后先确认设备存在于platform_bus_type再调用kunit_cleanup(fake)触发资源释放随后确认总线上已查不到该设备若引用计数迁移有 bug此处会暴露 refcount 下溢。四、一步注册kunit_platform_device_register_full()struct platform_device * kunit_platform_device_register_full(struct kunit *test, const struct platform_device_info *pdevinfo);这是组合拳版本内部调用platform_device_register_full(pdevinfo)一步完成分配注册lib/kunit/platform.c#L145-L161然后登记platform_device_unregister_wrapper动作保证测试结束时反注册。struct platform_device_info定义于include/linux/platform_device.h支持比 name/id 更丰富的设备描述——资源列表memory/IRQ 等、设备树节点、devm初始化等适合需要为平台设备模拟硬件资源的测试场景。失败时返回ERR_PTR测试侧应使用KUNIT_ASSERT_NOT_ERR_OR_NULL检查。五、手动反注册kunit_platform_device_unregister()void kunit_platform_device_unregister(struct kunit *test, struct platform_device *pdev);用途是在测试中途提前拆除设备并取消自动清理典型场景是测试驱动被 remove或设备热拔出路径。实现lib/kunit/platform.c#L180-L194分三种情况处理设备是kunit_platform_device_add()过且仍处于 add 清理态的把资源释放函数置NULL不再自动反注册再手动platform_device_unregister(pdev)设备是register_full()等走 action 包装器路径的kunit_remove_action()撤销已登记的 unregister 动作最后统一调用platform_device_unregister(pdev)真正拆除设备。这保证了手动反注册一次与测试结束时自动反注册一次不会叠加成两次platform_device_unregister()那样会造成引用计数下溢。六、等待 probekunit_platform_device_prepare_wait_for_probe()平台驱动的 probe 可能发生在platform_device_add()或驱动注册的任意时刻测试若不做同步就无法断言 probe 的结果。该 API 的签名是int kunit_platform_device_prepare_wait_for_probe(struct kunit *test, struct platform_device *pdev, struct completion *x);kernel-doc 强调的一点是在 completion 上等待会强制一次抢占从而让平台驱动的 probe 得以执行lib/kunit/platform.c#L223-L231——在内核态单线程执行测试时这一点常被忽略却至关重要。实现机制lib/kunit/platform.c#L197-L296通过kunit_kzalloc分配一个kunit_platform_device_probe_nb内含struct notifier_block、目标dev指针、completion *先device_lock(dev)并检查device_is_bound(dev)若设备已经绑定驱动probe 已过直接complete(x)并提前返回 0——这就是对已 probe 设备调用也安全的由来否则在platform_bus_type上bus_register_notifier()注册通知块回调kunit_platform_device_probe_notify()仅在事件为BUS_NOTIFY_BOUND_DRIVER且data正是目标设备时complete(x)lib/kunit/platform.c#L203-L216测试结束时通过登记的 action 执行bus_unregister_notifier(platform_bus_type, nb)自动清理。配套测试lib/kunit/platform-test.c#L175-L202专门验证了已经 probe 过再 preparecompletion 会立即完成这条快路径先完整走一遍注册-等待流程reinit_completion后再次调用 prepare随后wait_for_completion_timeout(comp, HZ)必须非 0。七、注册测试驱动kunit_platform_driver_register() 与 container_of 模式int kunit_platform_driver_register(struct kunit *test, struct platform_driver *drv);封装platform_driver_register(drv)并登记platform_driver_unregister清理动作lib/kunit/platform.c#L355-L365。kernel-doc 特别指出它的使用意图允许把platform_driver嵌入一个更大的上下文结构体这样 probe 函数就能通过container_of()把数据带进驱动侧——这是 KUnit 中在 probe/remove 回调里与测试上下文交互的标准模式。文档中的示例lib/kunit/platform.c#L312-L351struct kunit_test_context { struct platform_driver pdrv; const char *data; }; static inline struct kunit_test_context * to_test_context(struct platform_device *pdev) { return container_of(to_platform_driver(pdev-dev.driver), struct kunit_test_context, pdrv); } static int kunit_platform_driver_probe(struct platform_device *pdev) { struct kunit_test_context *ctx; ctx to_test_context(pdev); ctx-data test data; return 0; } static void kunit_platform_driver_test(struct kunit *test) { struct kunit_test_context *ctx; ctx kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); ctx-pdrv.probe kunit_platform_driver_probe; ctx-pdrv.driver.name kunit-platform; ctx-pdrv.driver.owner THIS_MODULE; KUNIT_EXPECT_EQ(test, 0, kunit_platform_driver_register(test, ctx-pdrv)); /* ... wait for driver to probe ... */ KUNIT_EXPECT_STREQ(test, ctx-data, test data); }仓库中的自测 lib/kunit/platform-test.c#L146-L169 就是这段示例的可运行版本完整链路如下可作为编写自己测试的模板/* 1. 上下文 设备 */ ctx kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); pdev kunit_platform_device_alloc(test, name, -1); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev)); /* 2. 配置驱动probe 里用 container_of 取回 ctx */ ctx-pdrv.probe kunit_platform_driver_probe; ctx-pdrv.driver.name name; ctx-pdrv.driver.owner THIS_MODULE; /* 3. 先挂等待再注册驱动最后阻塞等 probe 完成 */ KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, comp)); KUNIT_EXPECT_EQ(test, 0, kunit_platform_driver_register(test, ctx-pdrv)); KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(comp, 3 * HZ)); /* 4. 断言 probe 写入的数据 */ KUNIT_EXPECT_STREQ(test, ctx-data, test_data);调用顺序有讲究先 prepare 等待、再注册驱动。prepare_wait_for_probe()返回后驱动注册 → 总线匹配 → probe →BUS_NOTIFY_BOUND_DRIVER→ complete整条链路的竞争窗口被消除wait_for_completion_timeout(comp, 3 * HZ)的超时值 3 秒也与框架的默认测试超时KUNIT_DEFAULT_TIMEOUT默认 300 秒见 lib/kunit/Kconfig#L121-L132处于同一数量级考量之下。测试结束时KUnit 资源栈会自动反注册驱动、反注册设备、注销总线通知全部按登记逆序执行。八、构建与运行平台设备 API 本身不需要额外开关只要内核配置开启CONFIG_KUNITlib/kunit/platform.c就会随kunit一起构建lib/kunit/Makefile 第 13 行。若要连同框架自测platform-test.c一起编译需要CONFIG_KUNIT_TESTy或更省事地CONFIG_KUNIT_ALL_TESTSy。运行方式沿用 KUnit 的标准流程详见 Documentation/dev-tools/kunit/ 下的 run_wrapper.rst、running_tips.rst 等文档内核命令行参数kunit.enable1若CONFIG_KUNIT_DEFAULT_ENABLEDy则默认已启用、kunit.autorun默认y控制是否自启动、kunit.filter/kunit.filter_glob按 suite 或 case 过滤例如只跑kunit_platform_device与kunit_platform_driver两个 suite、kunit.timeout默认 300 秒且按KUNIT_SPEED_*乘 1x/3x/12x 系数结果以 TAP 格式打印到内核日志同时开启CONFIG_KUNIT_DEBUGFS时可在/sys/kernel/debug/kunit/suite/results查看最近一次结果平台驱动测试需要设备能真正 probe建议在支持CONFIG_KUNIT_UML_PCI见 lib/kunit/Kconfig#L134-L143的 UML 环境或目标架构真机上运行纯 QEMU 空壳配置下 platform 总线机制同样可用platform 总线不依赖 PCI。两个 suite 的命名与自测用例清单来自 lib/kunit/platform-test.c#L107-L118 和 L204-L216Suite用例覆盖点kunit_platform_devicealloc_test/add_test/add_twice_fails_test/add_cleans_up分配、注册校验name/id/bus、重复注册失败、结束后清理kunit_platform_driverregister_test/prepare_wait_for_probe_completes_when_already_probed驱动注册并 probe 成功、对已 probe 设备再次 prepare 立即完成九、小结与使用建议组合习惯alloc add是轻量路径只需 name/id需要模拟资源/DT 时用register_full()需要中途拆除设备测 remove 路径时用kunit_platform_device_unregister()它会自动撤销结束时的清理动作避免二次反注册。同步纪律等待 probe 一律用prepare_wait_for_probe() completion不要msleep()轮询——它基于platform_bus_type的BUS_NOTIFY_BOUND_DRIVER通知精确且无竞态且调用它本身即完成了已绑定则立即 complete的幂等处理。数据通道把platform_driver嵌进测试上下文结构体probe 里container_of()取回是官方推荐且自测实际采用的驱动回调 ↔ 测试断言通信模式。清理是自动的六个 API 的注册类动作都挂在 KUnit 资源栈上测试无论通过还是KUNIT_ASSERT失败都会逆序清理开发者无需手写 teardown唯一需要手动干预的场景就是提前kunit_platform_device_unregister()。关键源码与文档索引API 文档 Documentation/dev-tools/kunit/api/platformdevice.rst由 kernel-doc 从 lib/kunit/platform.c 导出、声明 include/kunit/platform_device.h、自测 lib/kunit/platform-test.c、构建与配置 lib/kunit/Makefile、lib/kunit/Kconfig以及 KUnit 总体文档入口 Documentation/dev-tools/kunit/index.rst。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →