
这一篇正好接着你前面的09 Driver API + Device Instance、10 Device Model、11 DT_INST_FOREACH_STATUS_OKAY() 多实例。摘要:本文以 UART0/UART1/UART2 为例,讲透 Zephyr 设备初始化框架:DEVICE_DT_DEFINE()如何注册设备、Init Level + Priority两级排序如何决定启动顺序,以及device_is_ready()的本质——它检查的是设备初始化状态,而非硬件健康状态。读完你将彻底掌握 Device Tree → Device Instance → Init Framework → Application 的完整链路。Zephyr 设备初始化框架:从 Device Tree 到 device_is_ready() 的完整链路这一篇真正要解决的问题是:Zephyr 是怎么决定「什么时候初始化 UART0、UART1、UART2」的?以及:device_is_ready() 到底检查了什么?先记住一个非常重要的结论:UART0 / UART1 / UART2 │ ▼ 每一个都是一个 struct device │ ▼ 每一个 device 都有自己的 init 函数 │ ▼ init 函数被 DEVICE_DT_DEFINE()注册 │ ▼ Zephyr 根据 init level + priority 排序 │ ▼ 系统启动时依次执行 │ ├── PRE_KERNEL_1 ├── PRE_KERNEL_2 ├── POST_KERNEL └── APPLICATION而:device_is_ready(dev)不是重新初始化设备。它主要是在问:这个 struct device 是否已经完成初始化,并且被标记为 ready?1. 先建立整个启动模型假设你的 SoC 有三个 UART:UART0 UART1 UART2设备树:uart0{status="okay";};uart1{status="okay";};uart2{status="okay";};经过 Zephyr Device Model 后,最终类似:Device Tree │ ┌─────────┼─────────┐ ▼ ▼ ▼ uart0 uart1 uart2 │ │ │ ▼ ▼ ▼ device_0 device_1 device_2 │ │ │ ▼ ▼ ▼ init_uart init_uart init_uart注意:UART0、UART1、UART2 并不是一个 driver 只被初始化一次。而是:同一个 Driver Code │ ├── instance0→ 一个 struct device ├── instance1→ 一个 struct device └── instance2→ 一个 struct device这就是你上一章讲的 DT_INST_FOREACH_STATUS_OKAY() 的意义。2. DEVICE_DT_DEFINE() 是关键入口例如你的 UART driver 可能最终生成类似:DEVICE_DT_DEFINE(DT_NODELABEL(uart0),uart_init,NULL,uart0_data,uart0_config,PRE_KERNEL_1,50,uart_api);UART1:DEVICE_DT_DEFINE(DT_NODELABEL(uart1),uart_init,NULL,uart1_data,uart1_config,PRE_KERNEL_1,51,uart_api);UART2:DEVICE_DT_DEFINE(DT_NODELABEL(uart2),uart_init,NULL,uart2_data,uart2_config,PRE_KERNEL_1,52,uart_api);这里已经出现了两个非常重要的信息:PRE_KERNEL_150它们分别代表:Init Level + Priority3. Init Level 到底是什么?Zephyr 并不是:main()↓ 初始化 UART0 ↓ 初始化 UART1 ↓ 初始化 UART2这么简单。它有一个完整的系统初始化阶段。可以把它理解成:系统启动 │ ▼ PRE_KERNEL_1 │ ▼ PRE_KERNEL_2 │ ▼ POST_KERNEL │ ▼ APPLICATION │ ▼ main()也就是:Zephyr Boot │ ▼ ┌─────────────────┐ │ PRE_KERNEL_1 │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ PRE_KERNEL_2 │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ POST_KERNEL │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ APPLICATION │ └─────────────────┘ │ ▼main()这就是 Zephyr 的system initialization framework。4. 为什么要分成 PRE_KERNEL_1 和 PRE_KERNEL_2?因为硬件之间存在依赖关系。例如:Clock │ ▼ UART │ ▼ Console │ ▼ ApplicationUART 不能在 clock 尚未准备好的情况下正常工作。因此:Clock driver ↓ UART driver ↓ Console ↓ Application需要有顺序。下面用一张 ASCII 依赖关系图,把这条初始化依赖链完整画出来,并标注每个环节对应的 Init Level 和典型 Priority 值:┌─────────────────────────────────────────────────────────────┐ │ Zephyr 初始化依赖链 │ │ (Level 优先于 Priority 的两级排序) │ └─────────────────────────────────────────────────────────────┘ Clock driver UART driver ┌──────────────┐ ┌──────────────────┐ │ Init Level │ │ Init Level │ │ PRE_KERNEL_1 │ │ PRE_KERNEL_2 │ │ Priority10│ │ Priority50│ └──────┬───────┘ └────────┬─────────┘ │ │ │ ① clock 先就绪 │ ② 依赖 clock ▼ ▼ ┌───────────────────────────────────────────────────┐ │ Console driver │ │ Init Level: POST_KERNEL │ │ Priority:30│ └──────────────────────┬────────────────────────────┘ │ │ ③ 依赖 UART 输出 ▼ ┌───────────────────────────────────────────────────┐ │ Application │ │ Init Level: APPLICATION │ │ Priority:10│ └──────────────────────┬────────────────────────────┘ │ ▼ main()读图要点:这条链的每一环都依赖上一环先完成初始化。Clock放在PRE_KERNEL_1 / 10,UART放在PRE_KERNEL_2 / 50,Console放在POST_KERNEL / 30,Application放在APPLICATION / 10。即使UART的 priority 50 比Console的 30 大,UART依然先执行——因为Level 优先于 Priority,PRE_KERNEL_2永远排在POST_KERNEL前面。Init Level 就是在表达这种关系。5. PRE_KERNEL_1:非常早期的初始化这是系统初始化非常早的阶段。常用于:非常基础的硬件例如:CPU / interrupt controller clock timer 某些 SoC 基础设施具体哪些 driver 放在这里,要看具体 Zephyr SoC / architecture / driver 实现。你可以把:PRE_KERNEL_1理解成:系统还没有完全起来之前,先准备最基础的硬件。6. PRE_KERNEL_2:继续初始化基础设备然后:PRE_KERNEL_1 ↓ PRE_KERNEL_2这个阶段仍然非常早。很多低层硬件设备会在这里初始化。例如某些:GPIO UART I2C SPI Timer具体 level 依赖 driver 的设计和依赖关系。所以千万不要记成:“UART 永远是 PRE_KERNEL_1。”这是错误的。正确理解是:Driver 作者根据设备依赖关系选择 init level。7. POST_KERNEL:kernel 已经起来了接下来:POST_KERNEL这时 kernel 基础设施已经完成。因此可以初始化一些依赖 kernel 服务的设备。例如某些设备可能需要:kernel object mutex semaphore work queue thread那么就不能放在太早的:PRE_KERNEL_1阶段。8. APPLICATION:最后才轮到应用级初始化最后:APPLICATION这个阶段已经非常接近:main()通常用于:application-level initialization例如:applicationserviceapplication subsystem9. 所以真正的排序是两级排序这是理解 Zephyr Init 最重要的一点。不是只有:PRE_KERNEL_1 PRE_KERNEL_2 POST_KERNEL APPLICATION还存在:priority因此真正类似:Init Level + Priority排序。例如:PRE_KERNEL_1 priority10priority20priority50PRE_KERNEL_2 priority10priority30priority50POST_KERNEL priority10priority50APPLICATION priority10因此:PRE_KERNEL_1/10↓ PRE_KERNEL_1/20↓ PRE_KERNEL_1/50↓ PRE_KERNEL_2/10↓ PRE_KERNEL_2/30↓ PRE_KERNEL_2/50↓ POST_KERNEL/10↓...下面用一个对比表格,把四个 Init Level 的关键差异整理清楚:Init Level典型用途可访问的 kernel 服务常见设备示例PRE_KERNEL_1系统最早期的基础硬件准备,kernel 尚未完全起来基本不可用(kernel 服务尚未就绪)CPU / interrupt controller、clock、timer、某些 SoC 基础设施PRE_KERNEL_2继续初始化低层硬件设备,仍处于早期阶段基本不可用(kernel 服务尚未就绪)GPIO、UART、I2C、SPI、Timer 等低层外设POST_KERNELkernel 基础设施已完成,可初始化依赖 kernel 服务的设备kernel object、mutex、semaphore、work queue、thread依赖 kernel 服务的设备驱动APPLICATION应用级初始化,最接近main()完整 kernel 服务application service、application subsystem排序规则:Level 优先于 Priority。也就是说,Zephyr 先按 Init Level 分大阶段,再在同一 Level 内部按 Priority 从小到大排序。PRE_KERNEL_2 / 10永远不会跑到PRE_KERNEL_1 / 100前面——即使它的 priority 更小。Level 决定了设备初始化的"大阶段",Priority 只决定同一阶段内部的先后顺序。Level 优先于 priority。也就是说:PRE_KERNEL_2/10也不会跑到:PRE_KERNEL_1/100前面。下面用一个组合排序示例表,把不同Level + Priority组合的实际执行顺序列出来,并标注哪些组合是合法的、哪些会产生歧义:执行顺序Init LevelPriority示例设备合法性说明1PRE_KERNEL