04-1:从 DEVICE_DT_DEFINE 到 struct device
验证方式:Zephyr 源码、ELF/map、native_sim/native/64
验证结论:源码确认 + 编译确认 + 模拟确认
1. 本节目标
本节建立 Zephyr Device Model 的第一条完整链路:
Devicetree node -> DT_INST_FOREACH_STATUS_OKAY -> DEVICE_DT_INST_DEFINE -> struct device + device_state + init entry -> kernel 在 main 前调用 init -> DEVICE_DT_GET 取得 device pointer -> device_is_ready 检查初始化状态本节重点理解设备对象的组成和生命周期,不深入初始化优先级、依赖排序和 API table 调度。
第四章计划分为五节:
04-1 DEVICE_DT_DEFINE 与 struct device 04-2 初始化 level、priority 与启动顺序 04-3 初始化失败、依赖与 device_is_ready 04-4 driver API table 与子系统调用 04-5 多实例驱动、常见问题与第四章总结2. Device Model 解决什么问题
不同驱动的硬件和业务不同,但 Zephyr 希望用统一对象表达:
这个设备叫什么 它的只读硬件配置在哪里 它的可变运行状态在哪里 它提供什么 API 初始化是否已经执行并成功 它依赖哪些其他设备这就是struct device的职责。
应用通常拿到:
conststructdevice*dev;然后:
- 检查设备是否 ready;
- 通过子系统 API 使用设备;
- 不直接了解驱动私有 data/config 的结构。
3. 本地 Zephyr 的 struct device
源码位置:
zephyr/include/zephyr/device.h:453核心字段可简化为:
structdevice{constchar*name;constvoid*config;constvoid*api;structdevice_state*state;void*data;/* 根据 Kconfig 还可能有 deps、PM、DT metadata 等字段 */};需要先记住五个核心指针:
| 字段 | 含义 | 常见存储性质 |
|---|---|---|
name | 设备实例名称 | 只读字符串 |
config | 设备实例的固定配置 | static const,通常 ROM/rodata |
api | 驱动对外函数表 | static const,通常 ROM/rodata |
state | Zephyr 维护的公共初始化状态 | 可变状态 |
data | 驱动实例私有运行数据 | 可变 RAM/BSS |
源码确认
4. config 与 data 必须分开
实验驱动定义:
structlearning_device_config{intinitial_value;};structlearning_device_data{intcurrent_value;bool init_was_called;};区别:
config 构建时由 Devicetree 决定 初始化后通常不改变 多数驱动声明为 static const data 运行过程中会改变 保存状态、buffer、callback、锁、计数等 不能声明为 const本节的具体值:
config.initial_value = 42 data.current_value = 0(BSS 初值)init 执行后:
data.current_value = 42 data.init_was_called = true5. Devicetree 实例
overlay:
/ { learning_device0: learning-device-0 { compatible = "zephyr,learning-device"; status = "okay"; initial-value = <42>; }; };binding:
compatible:"zephyr,learning-device"include:base.yamlproperties:initial-value:type:intrequired:true生成结果:
#defineDT_N_INST_0_zephyr_learning_deviceDT_N_S_learning_device_0#defineDT_N_S_learning_device_0_P_initial_value42编译确认
6. 每实例 config 和 data
驱动使用:
#defineDT_DRV_COMPATzephyr_learning_device每实例展开宏:
#defineLEARNING_DEVICE_DEFINE(inst)\staticstructlearning_device_data\learning_device_data_##inst;\staticconststructlearning_device_config\learning_device_config_##inst={\.initial_value=\DT_INST_PROP(inst,initial_value),\};\DEVICE_DT_INST_DEFINE(inst,learning_device_init,NULL,\&learning_device_data_##inst,\&learning_device_config_##inst,\POST_KERNEL,50,NULL);DT_INST_FOREACH_STATUS_OKAY(LEARNING_DEVICE_DEFINE)因为只有一个 status-okay 实例,简化展开后会产生:
learning_device_data_0 learning_device_config_0 一个 struct device 一个 device_state 一个 init entry7. DEVICE_DT_INST_DEFINE 做了什么
本地定义:
zephyr/include/zephyr/device.h:246#defineDEVICE_DT_INST_DEFINE(inst,...)\DEVICE_DT_DEFINE(DT_DRV_INST(inst),__VA_ARGS__)它只是先把inst转成 node identifier,再调用:
DEVICE_DT_DEFINE(node_id,...)所以:
DEVICE_DT_DEFINE 接收明确 node identifier DEVICE_DT_INST_DEFINE 接收当前 DT_DRV_COMPAT 的 instance number驱动多实例模式通常使用后者。
8. DEVICE_DT_DEFINE 做了什么
本地定义:
zephyr/include/zephyr/device.h:229核心工作可以概括为:
1. 为 node 创建 device_state 2. 创建全局 struct device 3. 把 name/config/api/state/data 写入 device object 4. 注册 init function、level 和 priority 5. 把对象放进 Zephyr 特定 iterable/linker section它不是普通的局部变量声明,也不是运行时malloc()。
设备对象及初始化记录在链接阶段就已经存在于镜像中。
9. 本节 DEVICE_DT_INST_DEFINE 参数
DEVICE_DT_INST_DEFINE(inst,learning_device_init,NULL,&learning_device_data_##inst,&learning_device_config_##inst,POST_KERNEL,50,NULL);参数含义:
| 参数 | 本实验值 | 含义 |
|---|---|---|
inst | 0 | 当前 compatible instance |
init_fn | learning_device_init | 启动初始化函数 |
pm | NULL | 暂不使用设备电源管理 |
data | &learning_device_data_0 | 私有可变数据 |
config | &learning_device_config_0 | 私有只读配置 |
level | POST_KERNEL | kernel 可用后初始化 |
prio | 50 | 同 level 内的初始化优先级 |
api | NULL | 本节暂不建立 API table |
level 和 priority 将在 04-2 单独验证。
10. init function 的输入
staticintlearning_device_init(conststructdevice*dev){conststructlearning_device_config*config=dev->config;structlearning_device_data*data=dev->data;data->current_value=config->initial_value;data->init_was_called=true;return0;}初始化函数已经拿到了完整struct device,因此能从中取得对应实例的 config 和 data。
返回值:
0 初始化成功 非 0 初始化失败,device 不应被认为 ready失败路径将在 04-3 用故障注入验证。
11. init 为什么在 main 之前运行
DEVICE_DT_DEFINE()不只是生成 device object,还创建 init entry。
Zephyr 启动代码按初始化 level 和 priority 遍历这些 entry,在进入应用main()前调用相应 init function。
本节选择:
POST_KERNEL, priority 50实际输出顺序:
[init] device=learning-device-0 initial=42 current_before=0 *** Booting Zephyr OS build v4.1.0-rc1 *** [main] device=learning-device-0 ready=1 init_called=1 initial=42 current=42结论:
learning_device_init() 先执行 main() 后执行,并观察到 init 已修改 data模拟确认
12. DEVICE_DT_GET 不是运行时查找
main 中:
#defineLEARNING_DEVICE_NODEDT_NODELABEL(learning_device0)staticconststructdevice*constlearning_dev=DEVICE_DT_GET(LEARNING_DEVICE_NODE);本地定义:
#defineDEVICE_DT_GET(node_id)(&DEVICE_DT_NAME_GET(node_id))这表示:
DEVICE_DT_GET 在编译/链接期引用 node 对应的全局 device object 不是按字符串遍历设备表 不会自动检查 init 是否成功如果 node 存在,但没有驱动为它执行DEVICE_DT_DEFINE(),通常会在链接时报错:
undefined reference to __device_dts_ord_<N>这是很重要的排查信号。
13. node identifier 与 device pointer
不要混淆:
DT_NODELABEL(learning_device0)这是预处理阶段使用的 node identifier。
DEVICE_DT_GET(DT_NODELABEL(learning_device0))这是指向运行时struct device对象的 C pointer。
转换关系:
node identifier -> DEVICE_DT_GET -> const struct device *node identifier 自己不是指针,也不能传给运行时 device API。
14. device_is_ready 的真实判断
本地实现:
zephyr/kernel/device.c:132boolz_impl_device_is_ready(conststructdevice*dev){if(dev==NULL){returnfalse;}returndev->state->initialized&&(dev->state->init_res==0U);}所以 ready 至少要求:
init function 已经被调用 && init result 表示成功device_is_ready()不是:
- Devicetree status 的别名;
- 真实总线通信测试;
- 传感器数据有效性测试;
- 永久保证设备以后不会出错。
本节结果:
ready=1因为 init 已执行并返回 0。模拟确认
15. device name 从哪里来
DEVICE_DT_DEFINE()使用:
DEVICE_DT_NAME(node_id)本地规则:
如果 node 有 label property -> 使用 label property 字符串 否则 -> 使用 node full name实验节点没有labelproperty,因此:
dev->name = "learning-device-0"不要把这里的dev->name再与 node labellearning_device0混淆。
16. ELF 与 map 证据
检查:
nm-n/mnt/c/study/1-zephyr/work/ch04_device_object/zephyr/zephyr.elf\|rg'learning_device|__device_dts_ord|__init_'实际关键 symbol:
learning_device_config_0 learning_device_data_0 learning_device_init __device_dts_ord_12 __init___device_dts_ord_12生成头文件说明:
#defineDT_N_S_learning_device_0_ORD12因此 ordinal 12 对应/learning-device-0。
map 中还能看到:
.rodata.learning_device_config_0 .bss.learning_device_data_0 __device_dts_ord_12这与设计相符:
config -> rodata data -> BSS/RAM device + init entry -> 静态链接对象编译确认
17. 本节最小构建
cd~/project/exportZEPHYR_SDK_INSTALL_DIR=/home/yff/zephyr-sdk/zephyr-sdk-0.17.1sourcezephyr/zephyr-env.sh west build\-bnative_sim/native/64\/mnt/c/study/1-zephyr/labs/ch04_device_model\--build-dir /mnt/c/study/1-zephyr/work/ch04_device_object\-palways /mnt/c/study/1-zephyr/work/ch04_device_object/zephyr/zephyr.exe实验不需要 GPIO、SPI、interrupt 或真实传感器,验证的是 Zephyr Device Model 本身。
19. 常见错误
19.1 config 没有 const
固定硬件配置通常应放在只读区。误放入可变 data 会增加 RAM 使用并模糊职责。
19.2 在 config 中保存运行状态
config 可能位于只读存储,不能用于计数、callback 状态或实时数据。
19.3 DEVICE_DT_GET 后不检查 ready
取得 pointer 不表示 init 成功。
conststructdevice*dev=DEVICE_DT_GET(node_id);if(!device_is_ready(dev)){/* handle error */}19.4 把 status okay 当 ready
status okay 只让实例参与构建;ready 是 init 后的运行时状态。
19.5 看到 __device_dts_ord_N undefined 就怀疑编译器
通常应检查:
- 对应 driver Kconfig 是否为 y;
- driver
.c是否进入编译; - node compatible/status 是否正确;
- driver 是否执行
DEVICE_DT_DEFINE/INST_DEFINE。
19.6 在应用中直接访问 dev->data
本实验为了教学展示内部结构。正常应用应通过驱动/子系统 API 使用设备,避免依赖私有 data/config 类型。
20. 可迁移到其他驱动的结论
struct device是配置、数据、API 和公共状态的统一连接点;- config 通常
static const,data 通常位于可变 RAM; DEVICE_DT_INST_DEFINE()为 Devicetree instance 创建静态 device object 和 init entry;DEVICE_DT_GET()取得全局对象地址,不执行字符串查找,也不检查 ready;device_is_ready()检查 init 已执行且返回成功;- status okay、device object 存在和 runtime ready 是不同阶段。
下一节:04-2-初始化level、priority与启动顺序。