尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Zephyr BSP: 13-Zephyr 初始化优先级
摘要:本文深入解析 Zephyr 中两个同为PRE_KERNEL_2的 Driver 的初始化顺序问题。核心结论是:Zephyr 的初始化顺序由(Init Level, Init Priority)二元组共同决定——先按 Level 分阶段(PRE_KERNEL_1→PRE_KERNEL_2→POST_KERNEL→APPLICATION),同一 Level 内再按 priority 数字从小到大执行。PRE_KERNEL_2是初始化阶段而非优先级,40才是真正的 Init Priority。当两个 Driver 的 Level 与 Priority 完全相同时,不要依赖其先后顺序来表达依赖关系,而应通过调整 priority 或借助 Devicetree dependency 与device_is_ready()来保证初始化顺序正确。文章最后结合 UART/GPIO 实例,说明了 Priority 设计对大型 SoC BSP 移植的重要性。** 快速结论**初始化顺序由(Level, Priority)二元组决定:先按 Level 分阶段,同一 Level 内再按 priority 排序;同 Level 内 priority 数字小者先执行:如PRE_KERNEL_2 / 30早于PRE_KERNEL_2 / 50;Level 与 Priority 相同时不要依赖顺序表达依赖:应显式调整 priority 或改用 Devicetree dependency;大型 SoC 应优先使用 Devicetree dependency:通过clocks、pinctrl-0、interrupts等属性自动推导初始化顺序,而非手写 priority。这正好是理解 ZephyrDevice Init 系统的关键问题。Zephyr Init Priority:两个 Driver 都是 PRE_KERNEL_2,到底谁先初始化?先给结论:如果两个 Driver 都是 PRE_KERNEL_2,不能简单理解成"谁的 priority 数字小谁先"。**Zephyr 最终会根据init entry 的排序键决定顺序;而对于同一个 init level,priority 数字越小越早。如果 priority 也相同,则还存在进一步的链接/排序规则,不要依赖它来表达 Driver 之间的依赖关系。1. 先看 Zephyr Init 的四个大阶段你上一节已经看到:PRE_KERNEL_1 ↓ PRE_KERNEL_2 ↓ POST_KERNEL ↓ APPLICATION可以先把它理解成:Zephyr Boot │ ┌───────────┴───────────┐ ↓ ↓ PRE_KERNEL_1 PRE_KERNEL_2 │ ↓ POST_KERNEL │ ↓ APPLICATION因此:PRE_KERNEL_1一定早于:PRE_KERNEL_2而:PRE_KERNEL_2一定早于:POST_KERNEL但是问题来了:Driver A → PRE_KERNEL_2 Driver B → PRE_KERNEL_2谁先?2. 真正决定顺序的是 (level, priority)可以把 Zephyr 的 init entry 想象成一个排序键:(level, priority)例如:Driver A: PRE_KERNEL_2,30Driver B: PRE_KERNEL_2,50那么:PRE_KERNEL_2 /30↓ PRE_KERNEL_2 /50所以:同一个 Init Level 下,priority 数字越小,越早执行。例如:DEVICE_DT_DEFINE(DT_NODELABEL(uart0),uart_init,NULL,uart_data,uart_config,PRE_KERNEL_2,40,uart_api);另一个:DEVICE_DT_DEFINE(DT_NODELABEL(timer0), timer_init, NULL,timer_data,timer_config, PRE_KERNEL_2,60,timer_api);启动顺序就是:PRE_KERNEL_2 /40↓ uart_init()↓ PRE_KERNEL_2 /60↓ timer_init()3. 所以 PRE_KERNEL_2 本身不是一个 priority这是非常容易混淆的地方。很多初学者看到:PRE_KERNEL_2会认为:“这是初始化优先级。”其实不是。它是:Init Level而:40才是:Init Priority所以:PRE_KERNEL_2,40应该读成:初始化阶段=PRE_KERNEL_2 优先级=404. 举一个 UART + GPIO Driver 的例子假设你的 SoC 有:GPIO Driver UART Driver你定义:GPIO: PRE_KERNEL_2 /30UART: PRE_KERNEL_2 /50那么启动:Zephyr boot │ ├── PRE_KERNEL_1 │ └── PRE_KERNEL_2 │ ├── GPIO init priority30│ └── UART init priority50也就是:GPIO ↓ UART这样 UART 初始化时:uart_init()就可以假设 GPIO Driver 已经完成初始化。5. 但是这里有一个非常重要的问题假设你写成:GPIO:PRE_KERNEL_2/50UART:PRE_KERNEL_2/50现在:GPIO ──┐ ├── PRE_KERNEL_2 /50UART ──┘那么:不要把"谁先"当成稳定的 Driver 依赖机制。因为你真正表达的是:GPIO 和 UART 的 priority 完全相同而不是:UART depends on GPIO如果 UART 确实依赖 GPIO,那么正确思路应该是:GPIO priority=30UART priority=50明确表达:GPIO ↓ UART6. 这和你现在学习的 Device Model 有直接关系你前面已经学习了:Device Tree ↓ DEVICE_DT_DEFINE()↓ struct device ↓ initfunction↓ device_is_ready()现在把 Init Priority 加进来:Devicetree │ ↓ DEVICE_DT_DEFINE()│ ┌──────────┴──────────┐ ↓ ↓ struct device init entry │ ┌──────┴──────┐ ↓ ↓ level priority │ │ └──────┬──────┘ ↓ Zephyr init │ ↓ driver init()所以:DEVICE_DT_DEFINE() 不只是创建一个 struct device。它同时把 Driver 的初始化信息放进 Zephyr 的system initialization machinery。7. 再看两个不同 Level 的 Driver假设:Driver A: PRE_KERNEL_2 /90Driver B: POST_KERNEL /0很多人会误以为:B priority=0所以 B 更早。不是。真正排序首先看 Level:PRE_KERNEL_2 ↓ POST_KERNEL所以实际:A ↓ B即使:A priority=90B priority=0也不会改变 Level 的先后关系。8. 可以记成一条非常重要的规则Zephyr Driver Init 可以先记:Init Level ↓ Init Priority即:PRE_KERNEL_1 ↓ PRE_KERNEL_2 ↓ POST_KERNEL ↓ APPLICATION在同一个 Level 内:priority 小 ↓ priority 大所以:PRE_KERNEL_1 /90↓ PRE_KERNEL_2 /10↓ PRE_KERNEL_2 /50↓ POST_KERNEL /0↓ APPLICATION /0这才是比较正确的思维方式。9. 那 device_is_ready() 又是什么?这里就开始和你上一节的问题连接起来了。假设:GPIO Driver PRE_KERNEL_2 /30UART Driver PRE_KERNEL_2 /50UART:const struct device\*gpio=DEVICE_DT_GET(DT_NODELABEL(gpio0));if(!device_is_ready(gpio)){return-ENODEV;}这里:device_is_ready(gpio)不是:“帮我把 GPIO 初始化。”而是:检查这个 struct device 当前是否已经完成初始化并处于可用状态。所以:GPIO init()↓ device-state=initialized ↓ UART init()↓ device_is_ready(gpio)↓true10. 这就是为什么 Priority 设计很重要如果你写:GPIO: PRE_KERNEL_2 /50UART: PRE_KERNEL_2 /30那么:UART init()↓ device_is_ready(GPIO)此时 GPIO 可能还没有初始化。于是:UART ↓ GPIO ? ↓ not ready这时候问题并不是:device_is_ready()坏了。而是:你的 Driver Init Dependency 排错了。11. 实战:UART 依赖 GPIO 的排查与修复下面用一个完整示例,把第 9、10 节的内容串起来:UART 初始化时device_is_ready(gpio)返回false,问题出在 priority 排反了。11.1 错误写法:UART 比 GPIO 先初始化假设你的 SoC 上同时有 GPIO 和 UART,但两个 Driver 的 priority 写反了:/* gpio driver */DEVICE_DT_DEFINE(DT_NODELABEL(gpio0),gpio_init,NULL,gpio_data,gpio_config,PRE_KERNEL_2,50,/* GPIO 反而排后面 */gpio_api);/* uart driver */DEVICE_DT_DEFINE(DT_NODELABEL(uart0),uart_init,NULL,uart_data,uart_config,PRE_KERNEL_2,30,/* UART 反而排前面 */uart_api);UART 的uart_init()里这样使用 GPIO:staticintuart_init(conststructdevice*dev){conststructdevice*gpio=DEVICE_DT_GET(DT_NODELABEL(gpio0));if(!device_is_ready(gpio)){printk("uart_init: GPIO not ready!\n");return-ENODEV;}/* 配置 UART 的 TX/RX 引脚 */gpio_pin_configure(gpio,TX_PIN,GPIO_OUTPUT);gpio_pin_configure(gpio,RX_PIN,GPIO_INPUT);return0;}11.2 启动日志:修复前*** Booting Zephyr OS build zephyr-v3.7.0 ***[00:00:00.000]uart_init: GPIO not ready!-- 失败[00:00:00.000]uart0: init failed(-19)[00:00:00.000]gpio_init: GPIO ready可以看到:uart_init()先执行,此时 GPIO 还没初始化,device_is_ready()返回false,UART 初始化直接失败。11.3 修复:交换 priority把 GPIO 的 priority 调小,让它在 UART 之前完成初始化:/* gpio driver */DEVICE_DT_DEFINE(DT_NODELABEL(gpio0),gpio_init,NULL,gpio_data,gpio_config,PRE_KERNEL_2,30,/* GPIO 先初始化 */gpio_api);/* uart driver */DEVICE_DT_DEFINE(DT_NODELABEL(uart0),uart_init,NULL,uart_data,uart_config,PRE_KERNEL_2,50,/* UART 后初始化 */uart_api);11.4 启动日志:修复后*** Booting Zephyr OS build zephyr-v3.7.0 ***[00:00:00.000]gpio_init: GPIO ready[00:00:00.000]uart_init: GPIO ready, configuring pins...[00:00:00.000]uart0: init ok现在顺序正确:PRE_KERNEL_2 /30↓ gpio_init()↓ device-state=initialized ↓ PRE_KERNEL_2 /50↓ uart_init()↓ device_is_ready(gpio)→true11.5 排查思路小结遇到device_is_ready()返回false时,按这个顺序排查:确认两个 Driver 的Init Level是否相同;如果相同,检查Init Priority是否满足依赖关系(被依赖者 priority 更小);如果 Level 不同,先确认被依赖者是否在更早的 Level;如果 Level 和 Priority 都相同,不要依赖顺序,应显式调整 priority 或改用 Devicetree dependency 表达依赖。11.6 排查决策流程图下面这张 Mermaid 流程图完整展示了device_is_ready()返回false时的排查决策路径,从确认 Init Level 是否相同开始,依次检查 Init Priority、Devicetree dependency,最后给出修复建议:
RELATED

相关推荐

模2运算详解:从异或到CRC校验的底层原理与实操

模2运算详解:从异或到CRC校验的底层原理与实操

做通信协议和底层软件这行,几乎天天要和模2运算打交道。奇偶校验、CRC校验、LFSR伪随机序列、汉明码纠错,这些看似各不相同的技术,剥开外壳后核心全是模2加法、模2减法、模2乘法、模2除法这一套四则运算。很多人一开始觉得简单,不…

📅 2026/9/29 8:14:35
攻击者画像关键技术:从日志聚类到团伙处置的工程实践

攻击者画像关键技术:从日志聚类到团伙处置的工程实践

简介:这是一份面向网络安全从业者、安全态势感知研究人员及高校相关专业学生的技术文献,聚焦攻击者画像这一主动防御关键环节,帮助读者理解如何从攻击行为中识别意图、预测威胁并辅助溯源。资源为单份PDF文档,压缩包约1008KB&…

📅 2026/9/29 8:14:35
SSTI服务端模板注入从原理到实战:Jinja2利用链与过滤绕过

SSTI服务端模板注入从原理到实战:Jinja2利用链与过滤绕过

聊到Web安全,SSTI(服务端模板注入)是我每次做内部分享都会拿出来讲的漏洞类型。原因很简单:它把“用户输入”和“代码执行”之间的边界彻底模糊了,明明只是一段渲染模板的小功能,最后却能演变成服务器上的任…

📅 2026/9/29 8:14:35
MORE NEWS

更多资讯

📰

DSH小鲸鱼挂件装完不显示?7个常见问题的完整自检清单

DSH小鲸鱼挂件装完不显示?7个常见问题的完整自检清单 【免费下载链接】DeepSeek-Balance-Whale-Widget DeepSeek Harness(DSH)一只住在 DSH 界面右下角的小鲸鱼娘,帮你盯着DeepSeek账户余额。QQ弹弹,支持拖拽吸附、左吸…

📰

C语言期末通关:内存模型与五大核心枢纽解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

cursor:pointer 失效排查:从 z-index 到 CSS 层叠的完整配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

用AI-Novel-Writing-Assistant写出你的第一本完整长篇小说:从一句灵感到成稿的实操路径

用AI-Novel-Writing-Assistant写出你的第一本完整长篇小说:从一句灵感到成稿的实操路径 【免费下载链接】AI-Novel-Writing-Assistant 面向长篇小说创作的 AI Native 开源系统,用 Agent、世界观、写法引擎、RAG 和整本生产工作流,帮助新手从一…

📰

Mac Mouse Fix 按钮映射指南:5 分钟让 10 美元鼠标的侧键好用起来

Mac Mouse Fix 按钮映射指南:5 分钟让 10 美元鼠标的侧键好用起来 【免费下载链接】mac-mouse-fix Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad! 项目地址: https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix 你按下鼠标侧键&a…

📰

BMAD命名智能体实践--智能体人设扩展

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬