这一篇正好接着你前面的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_KERNEL | kernel 基础设施已完成,可初始化依赖 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 Level | Priority | 示例设备 | 合法性 | 说明 |
|---|---|---|---|---|---|
| 1 | PRE_KERNEL |