news 2026/8/5 8:15:18

Zephyr学习 第四章 -2:初始化 level、priority 与启动顺序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Zephyr学习 第四章 -2:初始化 level、priority 与启动顺序

04-2:初始化 level、priority 与启动顺序

验证方式:SYS_INIT()、device init、ELF/map、native_sim/native/64
验证结论:源码确认 + 编译确认 + 模拟确认

1. 本节目标

上一节确认 device init 会在main()前执行。本节进一步回答:

  1. Zephyr 有哪些初始化 level?
  2. 同一个 level 内,priority 如何决定顺序?
  3. SYS_INIT()与 device init 是否使用同一套基础设施?
  4. 驱动为什么不能随便选择一个更早的 level?

本节最终验证的时间线:

EARLY -> PRE_KERNEL_1 -> PRE_KERNEL_2 -> POST_KERNEL -> APPLICATION -> main

2. Zephyr 的初始化基础设施

本地源码:

zephyr/include/zephyr/init.h

Zephyr 使用同一套 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 stack;kernel services 尚不可用
PRE_KERNEL_2仍在 pre-kernel 上下文与 PRE_KERNEL_1 相同限制,但阶段更晚
POST_KERNELkernel 已可用可以使用 kernel primitives,常用于普通 device driver
APPLICATIONmain()之前应用/子系统级初始化
SMPSMP 专用阶段仅在CONFIG_SMP启用时可用

本次native_sim没有验证 SMP 阶段,因此实验只覆盖到APPLICATION

4. EARLY 阶段为什么要非常谨慎

EARLY发生在系统启动的最前面。

通常不应假设下面内容已经可用:

  • scheduler;
  • semaphore、mutex;
  • heap;
  • logging backend;
  • 普通 device object ready;
  • console 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. APPLICATION

APPLICATIONinit 在应用main()前执行。

适合:

  • 应用级注册;
  • 订阅系统事件;
  • 在 main 前完成的子系统设置;
  • 不属于具体 device object 的初始化逻辑。

实验:

SYS_INIT(trace_application_20,APPLICATION,20);

最终记录证明:

APPLICATION:20 早于 main

8. 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_DEFAULT+5);

原因是 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_<LEVEL><PRIORITY>_<SUBPRIORITY>_

形式的 linker section。

它不代表创建了struct device

SYS_INIT 只有 system init entry,dev 指针为 NULL DEVICE_DT_DEFINE 同时有 device object、device state 和 device init entry

12. 本节 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] device=learning-device-0 initial=42 current_before=0 *** Booting Zephyr OS build v4.1.0-rc1 *** boot_trace_count=9 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] device=learning-device-0 ready=1 init_called=1 initial=42 current=42

验证结论:

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\|rg'device_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 可以选哪些 level

DEVICE_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. 可迁移到其他驱动的结论

  1. SYS_INIT 和 device init 使用同一 init entry/linker section 基础设施;
  2. 初始化先按 level、再按 priority、最后按 sub-priority 排序;
  3. 同 level 中数值较小的 priority 先执行;
  4. PRE_KERNEL 阶段不能假设 kernel services 可用;
  5. POST_KERNEL 是普通 device driver 的常见选择;
  6. APPLICATION init 发生在 main 之前;
  7. 最终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

然后回答:

  1. PRE_KERNEL_2:99POST_KERNEL:0哪个先执行?为什么?
  2. 同为POST_KERNEL时,priority 10、50、90 的顺序是什么?
  3. 为什么实验在 EARLY 阶段只记录内存而不直接打印?
  4. 为什么源码中 priority 80 写在 20 前面,最终仍是 20 先执行?
  5. SYS_INIT()DEVICE_DT_DEFINE()的 init entry 有什么共同点和区别?

下一节:04-3-初始化失败、设备依赖与device_is_ready

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/5 8:13:11

容器化部署性能优化:JVM与CPU调度实战

1. 容器化部署性能优化实战背景 容器化技术已经成为现代应用部署的标准方式&#xff0c;但很多团队在迁移到容器环境时都会遇到性能下降的问题。最近我在一个客户项目中就遇到了这样的情况&#xff1a;一个原本在物理服务器上运行良好的Java应用&#xff0c;迁移到容器环境后响…

作者头像 李华
网站建设 2026/8/5 8:10:52

百度网盘下载加速终极指南:5分钟实现10倍速度提升

百度网盘下载加速终极指南&#xff1a;5分钟实现10倍速度提升 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 还在为百度网盘下载速度慢而烦恼吗&#xff1f;本文将为你揭秘一…

作者头像 李华
网站建设 2026/8/5 8:10:15

华三交换机三权分立配置实战:基于RBAC实现网络设备精细化权限管理

1. 项目背景与核心价值&#xff1a;为什么需要“三权分立”在任何一个稍具规模的企业网络里&#xff0c;网络设备的管理权限都是一个敏感且关键的话题。想象一下&#xff0c;如果一台核心交换机的管理员账号密码被一个实习生或者一个即将离职的员工掌握&#xff0c;会发生什么&…

作者头像 李华
网站建设 2026/8/5 8:05:12

苍穹外卖【day7| 对购物车进行操作】

&#x1f308;个人主页:一条泥憨鱼(欢迎各位大佬莅临) &#x1f3ac;精选专栏传送门: ❄️《数据结构》 ❄️《AI与Agent那些事》 ❄️《从0开始学计算机网络》 ❄️《后端开发》 添加购物车 需求分析和设计 数据库设计 代码开发 1️⃣ 数据传输层 (DTO) ShoppingCartDTO…

作者头像 李华
网站建设 2026/8/5 8:04:20

STM32+DHT11+OLED温湿度检测系统:从时序调试到工程实践

为什么很多嵌入式初学者做的第一个项目都是“温湿度检测系统”&#xff1f;因为它看起来简单——传感器、单片机、显示屏&#xff0c;连上线就能跑。但真正动手后&#xff0c;你会发现&#xff0c;从DHT11的时序调试到OLED的I2C驱动&#xff0c;从Proteus的仿真到STM32的HAL库配…

作者头像 李华