Zephyr学习 第四章 -2:初始化 level、priority 与启动顺序 04-2初始化 level、priority 与启动顺序验证方式SYS_INIT()、device init、ELF/map、native_sim/native/64验证结论源码确认 编译确认 模拟确认1. 本节目标上一节确认 device init 会在main()前执行。本节进一步回答Zephyr 有哪些初始化 level同一个 level 内priority 如何决定顺序SYS_INIT()与 device init 是否使用同一套基础设施驱动为什么不能随便选择一个更早的 level本节最终验证的时间线EARLY - PRE_KERNEL_1 - PRE_KERNEL_2 - POST_KERNEL - APPLICATION - main2. Zephyr 的初始化基础设施本地源码zephyr/include/zephyr/init.hZephyr 使用同一套 init entry 基础设施支持SYS_INIT 注册的系统初始化函数 DEVICE_DT_DEFINE 注册的设备初始化函数init entry 的核心结构structinit_entry{unioninit_function init_fn;union{conststructdevice*dev;/* 可选 mutable device */};};其中union init_function能保存两种签名int(*sys)(void);int(*dev)(conststructdevice*dev);所以SYS_INIT callback 没有 device 参数 device init callback 接收对应 struct device *源码确认3. 初始化 level 总览本地init.h按顺序定义level执行阶段主要限制/用途EARLY刚进入 C 环境后的极早期架构、SoC 极早期设置系统服务不可假设可用PRE_KERNEL_1kernel 初始化上下文使用 interrupt stackkernel services 尚不可用PRE_KERNEL_2仍在 pre-kernel 上下文与 PRE_KERNEL_1 相同限制但阶段更晚POST_KERNELkernel 已可用可以使用 kernel primitives常用于普通 device driverAPPLICATIONmain()之前应用/子系统级初始化SMPSMP 专用阶段仅在CONFIG_SMP启用时可用本次native_sim没有验证 SMP 阶段因此实验只覆盖到APPLICATION。4. EARLY 阶段为什么要非常谨慎EARLY发生在系统启动的最前面。通常不应假设下面内容已经可用schedulersemaphore、mutexheaplogging backend普通 device object readyconsole driver 已完成初始化。本实验的 EARLY callback 只做boot_events[boot_event_count]EARLY:50;即只修改已经清零的静态 RAM不调用printk()从而避免依赖早期 console。5. PRE_KERNEL_1 与 PRE_KERNEL_2本地源码说明这两个阶段运行在 kernel initialization context 使用 interrupt stack kernel services 尚不可用因此 pre-kernel init 不适合阻塞等待 semaphore创建依赖 scheduler 的工作睡眠依赖普通 POST_KERNEL device使用尚未初始化的子系统。两个 level 的区别主要是全局顺序所有 PRE_KERNEL_1 都早于 所有 PRE_KERNEL_2即使PRE_KERNEL_1 priority 99 PRE_KERNEL_2 priority 0前者仍先执行因为 level 排序优先于 priority。6. POST_KERNEL本地定义Executed after Kernel is alive. From this point on, Kernel primitives can be used.普通传感器、总线和外设驱动经常使用POST_KERNEL因为初始化中可能需要semaphore、mutex 等内核对象已初始化的父总线 device日志或较完整的系统服务设备依赖检查。本节伪驱动使用DEVICE_DT_INST_DEFINE(...,POST_KERNEL,50,...)7. APPLICATIONAPPLICATIONinit 在应用main()前执行。适合应用级注册订阅系统事件在 main 前完成的子系统设置不属于具体 device object 的初始化逻辑。实验SYS_INIT(trace_application_20,APPLICATION,20);最终记录证明APPLICATION:20 早于 main8. priority 的规则priority 范围0 ... 99同一个 level 内数值越小 - 越早执行 数值越大 - 越晚执行例如POST_KERNEL priority 10 POST_KERNEL priority 50 POST_KERNEL priority 90顺序为10 - 50 - 90注意这与之后学习的 thread priority 不是同一套对象。这里只讨论启动 init entry 的链接排序。9. level 比 priority 更高一级错误理解priority 0 永远比 priority 99 早正确理解先按 level 排序 再在同一个 level 内按 priority 排序例如PRE_KERNEL_2:99 仍早于 POST_KERNEL:0可以把完整排序 key 简化理解为(level ordinal, priority, sub-priority)10. priority 必须是可用于 section 名的值SYS_INIT()文档要求 priority 是无前导零、无符号的十进制整数 literal或展开为这种整数的 symbolic name/Kconfig symbol。可以SYS_INIT(foo,POST_KERNEL,32);#defineMY_INIT_PRIORITY32SYS_INIT(foo,POST_KERNEL,MY_INIT_PRIORITY);不可以SYS_INIT(foo,POST_KERNEL,CONFIG_KERNEL_INIT_PRIORITY_DEFAULT5);原因是 priority 被拼进 linker section 名普通 C 算术表达式无法在这个位置使用。11. SYS_INIT 的基本形式系统初始化函数staticintmy_init(void){return0;}SYS_INIT(my_init,POST_KERNEL,50);SYS_INIT()创建一个静态struct init_entry并把它放入.z_init_LEVELPRIORITY_SUBPRIORITY_形式的 linker section。它不代表创建了struct deviceSYS_INIT 只有 system init entrydev 指针为 NULL DEVICE_DT_DEFINE 同时有 device object、device state 和 device init entry12. 本节 boot trace 设计实验注册EARLY:50 PRE_KERNEL_1:80 先写在源码中 PRE_KERNEL_1:20 后写在源码中 PRE_KERNEL_2:50 POST_KERNEL:90 先写在源码中 POST_KERNEL:10 后写在源码中 POST_KERNEL:device:50 APPLICATION:20 main故意逆序书写 priority 80/20 和 90/10是为了排除“按源码出现顺序执行”的错误理解。每个 callback 只调用boot_trace_record(LEVEL:priority);最后由 main 统一打印数组。13. 实际运行结果[init] devicelearning-device-0 initial42 current_before0 *** Booting Zephyr OS build v4.1.0-rc1 *** boot_trace_count9 boot_trace[0]EARLY:50 boot_trace[1]PRE_KERNEL_1:20 boot_trace[2]PRE_KERNEL_1:80 boot_trace[3]PRE_KERNEL_2:50 boot_trace[4]POST_KERNEL:10 boot_trace[5]POST_KERNEL:device:50 boot_trace[6]POST_KERNEL:90 boot_trace[7]APPLICATION:20 boot_trace[8]main [main] devicelearning-device-0 ready1 init_called1 initial42 current42验证结论level 顺序正确 同 level 内数值较小的 priority 先执行 device init 和 SYS_INIT entry 参与同一 POST_KERNEL 排序 APPLICATION 在 main 前执行模拟确认14. linker map 的直接证据查看rg-n-C2\.z_init_(EARLY|PRE_KERNEL_1|PRE_KERNEL_2|POST_KERNEL|APPLICATION)\/mnt/c/study/1-zephyr/work/ch04_init_order/zephyr/zephyr.map实际 section 顺序.z_init_EARLY50_0_ .z_init_PRE_KERNEL_120_0_ .z_init_PRE_KERNEL_180_0_ .z_init_PRE_KERNEL_199_0_ native console .z_init_PRE_KERNEL_20_0_ native timer .z_init_PRE_KERNEL_250_0_ .z_init_POST_KERNEL10_0_ .z_init_POST_KERNEL50_00012_ learning device .z_init_POST_KERNEL90_0_ .z_init_APPLICATION20_0_这说明 linker 不只排列实验代码也把 Zephyr 自带的 console、timer 和 device init 放进同一全局序列。编译确认15. 为什么源码逆序但执行仍有序init.h中#defineZ_INIT_ENTRY_SECTION(level,prio,sub_prio)\__attribute__((__section__(\.z_init_#levelSTRINGIFY(prio)\_STRINGIFY(sub_prio)_)))链接脚本再使用排序SORT_BY_NAME SORT_BY_ALIGNMENT因此真正决定顺序的是section 名 linker script不是C 文件中的函数位置CMake 中源码列出的顺序object 文件碰巧的顺序。16. sub-priority 是什么普通SYS_INIT()本次生成..._0_learning device 生成.z_init_POST_KERNEL50_00012_其中00012对应当前 node 的 dependency ordinal 相关排序信息。它帮助 Zephyr 在相同 level/priority 下处理设备依赖顺序。本节只观察到它存在设备依赖、required handles 和初始化失败传播将在 04-3 展开。不要在应用代码中手工依赖这个生成数字。17. nm 证据nm-n/mnt/c/study/1-zephyr/work/ch04_init_order/zephyr/zephyr.elf\|rgdevice_dts_ord_12|__init_trace实际地址顺序0x10 __init_trace_early_50 0x20 __init_trace_pre1_20 0x30 __init_trace_pre1_80 0x60 __init_trace_pre2_50 0x70 __init_trace_post_10 0x80 __init___device_dts_ord_12 0x90 __init_trace_post_90 0xa0 __init_trace_application_20与运行时记录一致。编译确认18. device init 可以选哪些 levelDEVICE_DEFINE()/DEVICE_DT_DEFINE()文档面向普通 device 时列出PRE_KERNEL_1 PRE_KERNEL_2 POST_KERNEL选择原则必须在 kernel 前准备的底层设备 - PRE_KERNEL level但必须遵守严格限制 需要 kernel primitives 或常规设备依赖 - POST_KERNEL 纯应用初始化 - 通常使用 SYS_INIT(..., APPLICATION, ...)不要为了“更早”而盲目选择 PRE_KERNEL。过早会让可用 API 更少、依赖关系更难满足。19. 初始化 priority 不是延时priority 10 priority 90不表示相差 80 ms每个 init 有固定时间片priority 10 一定执行得更快调度器 thread priority。它只表示同一 level 中 init entry 的相对排序键。如果 priority 10 的 init 阻塞很久priority 90 仍必须等待它返回。21. 常见错误21.1 认为所有 priority 在全局比较priority 只在相同 level 内比较。21.2 把 init priority 与 thread priority 混淆init priority 是链接排序thread priority 是 scheduler 概念。21.3 PRE_KERNEL 中使用 kernel primitive此时 kernel services 尚不可用。21.4 EARLY 阶段打印大量日志console/logging backend 可能尚未初始化。应使用最小、无依赖的状态记录。21.5 使用 priority 算术表达式section 名生成要求 literal 或可直接展开的 symbolic integer。21.6 用源码书写顺序推断执行顺序应检查 level、priority、生成 section 和最终 map。21.7 随意调整 driver priority 修复依赖如果 Devicetree 已表达设备依赖应优先理解依赖图和自动排序而不是靠不断试 priority 数字。下一节会专门学习。22. 可迁移到其他驱动的结论SYS_INIT 和 device init 使用同一 init entry/linker section 基础设施初始化先按 level、再按 priority、最后按 sub-priority 排序同 level 中数值较小的 priority 先执行PRE_KERNEL 阶段不能假设 kernel services 可用POST_KERNEL 是普通 device driver 的常见选择APPLICATION init 发生在 main 之前最终zephyr.map是确认真实 init 顺序的重要证据。23. 练习与自测请执行rg-n-C2\.z_init_(EARLY|PRE_KERNEL_1|PRE_KERNEL_2|POST_KERNEL|APPLICATION)\/mnt/c/study/1-zephyr/work/ch04_init_order/zephyr/zephyr.map然后回答PRE_KERNEL_2:99与POST_KERNEL:0哪个先执行为什么同为POST_KERNEL时priority 10、50、90 的顺序是什么为什么实验在 EARLY 阶段只记录内存而不直接打印为什么源码中 priority 80 写在 20 前面最终仍是 20 先执行SYS_INIT()与DEVICE_DT_DEFINE()的 init entry 有什么共同点和区别下一节04-3-初始化失败、设备依赖与device_is_ready。