Linux内核分析之设备驱动-00
39.1 kobject、kset 与 ktype
kobject 是设备模型的"对象基类"——它不描述任何设备,只提供名字、树关系、引用计数、sysfs 投影与生命周期回调五件通用物。device/driver/bus 全部把它当第一个字段嵌入。本节拆解这个基类与它的两个伙伴。
39.1.1 kobject:五件通用物
// include/linux/kobject.h:64-88
struct kobject {
const char *name; /* 对象名 (sysfs 目录名) */
struct list_head entry; /* 挂 kset 的链 */
struct kobject *parent; /* 父对象 (目录层级) */
struct kset *kset; /* 所属集合 */
const struct kobj_type *ktype; /* 类型: 属性+释放回调 */
struct kernfs_node *sd; /* sysfs 目录项 (37.2 节 kernfs!) */
struct kref kref; /* 引用计数 (17 章 kref) */
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
...
};
五件通用物与 sysfs 的对应:name→目录名、parent/kset→目录位置、kref→"目录存在多久"、ktype→目录里有哪些属性文件、sd→与 37.2.1 节 kernfs 节点的直连。kobject 永远动态分配、以 kref 管理——kobject_get/put 包装 kref;归零时经 ktype 的 release 回调释放(容器宏 container_of 反查宿主结构)。"put 之后不得再碰"的纪律由 release 回调统一执行,这是 C 语言设备驱动生命周期管理的全部答案。
39.1.2 ktype 与 kset
// include/linux/kobject.h:116-123
struct kobj_type {
void (*release)(struct kobject *kobj); /* 引用归零回调 */
const struct sysfs_ops *sysfs_ops; /* show/store 分发 */
const struct attribute_group **default_groups; /* 默认属性组 */
const struct kobj_ns_type_operations *(*child_ns_type)(...);
const struct ns_common *(*namespace)(...);
void (*get_ownership)(...);
};
// :168
struct kset { ... /* 子 kobject 集合 + 自己也是 kobject + uevent 策略 */ };
ktype 与 kset 的分工常被混淆:ktype 回答"这类对象长什么样"(属性表+释放回调),kset 回答"这批对象住哪、怎么广播事件"(聚合目录 + kset_uevent_ops)。同一 kset 里的对象可有不同 ktype。kset 的 entry/parent 双向关系使"集合"既是一个目录又是一个链表——/sys/bus/pci/devices/ 就是典型 kset:枚举全部 PCI 设备目录。
39.1.3 uevent:向用户态广播
设备生灭的用户态通知 (lib/kobject_uevent.c):
kobject_uevent(kobj, KOBJ_ADD/KOBJ_CHANGE/KOBJ_REMOVE...)
├─ 按 kset 的 uevent_ops 过滤/取子系统的名字
├─ 组装环境变量: ACTION= / DEVPATH= / SUBSYSTEM=
│ + MODALIAS= (驱动自动加载的钥匙)
├─ 两条投递路径:
│ netlink 广播 (48 章, udev 在线监听)
│ + uevent 文件写入 (coldplug: udev 重启后
│ 重放 /sys 全部设备的 add 事件)
└─ udev 收到: modprobe MODALIAS / 建设备节点 / 改权限
32.3 节 signalfd 式的统一性: 用户态的一切
设备管理 (命名/权限/模块加载) 都建立在
这条"内核→netlink"的广播上
state_add_uevent_sent/remove_uevent_sent 位保证 add/remove 事件的配对性(不重复广播、remove 必达)——udev 数据库的一致性依赖此。
小结
kobject 以五件通用物(名字/树位置/引用/sysfs 投影/释放回调)充当设备模型基类,kref+release 回调构成 C 世界对象生命周期纪律,ktype 定形态、kset 定居所与广播策略,uevent 经 netlink+coldplug 双路径把设备生灭通知用户态。37.2 节 sysfs 的目录树在本节找到了内核本体。下一节看硬件拓扑如何经设备树变成这些对象。
39.2 设备树 (Device Tree) 与 platform_device
x86 的设备靠 PCI/ACPI 枚举,嵌入式 ARM64/RISC-V 的 SoC 外设没有可枚举总线——设备树(Device Tree,DT)以"数据描述硬件"取代"代码描述硬件":硬件布局写成 .dts 编译成 .dtb 传给内核,内核解析后生成 platform_device。本节拆解 DT 的结构与 platform 机制。
39.2.1 设备树的节点与绑定
// include/linux/of.h:48(内核侧节点, 节选要点)
struct device_node {
const char *name;
phandle phandle;
const char *full_name;
...
struct property *properties; /* 属性链 */
...
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
...
};
DT 源码片段 (SoC 的 UART 节点):
soc {
compatible = "acme,soc";
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@10010000 {
compatible = "acme,uart", "generic-8250";
reg = <0x10010000 0x1000>; /* 寄存器基址+长度 */
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk 3>;
status = "okay";
};
};
解析结果: device_node 树 = DT 文本的结构化镜像
"compatible" 是驱动的钥匙: 字符串列表,
从特异到通用 ("acme,uart" → "generic-8250"),
驱动的 of_match_table 逐串比对
compatible 匹配的内核入口 of_device_is_compatible()(drivers/of/base.c:379)——of_match_table 的匹配规则与 39.4 节 driver 匹配函数对齐。属性即 ABI:reg/interrupts/clocks 的编码规则由"DT binding 文档"约定,设备树与驱动跨版本兼容全靠它——这是内核少有的数据驱动硬件设计(ACPI 是 x86 的对应物,Windows 固件共用)。
39.2.2 platform_device:不可枚举总线的设备
platform 总线的角色:
SoC 外设 (UART/SPI 控制器/GPIO...) 无 PCI/USB
这样的可枚举协议 — 内核虚构 platform 总线:
设备: platform_device (来自 DT 解析 / ACPI / 旧式
board 文件硬编码)
驱动: platform_driver (probe 挂到匹配的设备)
匹配优先级: of_match_table > id_table > 名字
DT → 设备的批量生成:
of_platform_populate(root, matches, lookup, parent)
(drivers/of/platform.c:443)
深度遍历 device_node 树, 对有 compatible 的节点
建 platform_device 并注册 → 进 39.4 节的
绑定流水线
// include/linux/platform_device.h:234(驱动模板, 节选)
struct platform_driver {
int (*probe)(struct platform_device *);
void (*remove)(struct platform_device *);
...
struct device_driver driver; /* 基类: 39.1.1 的 kobject 链 */
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
// drivers/base/platform.c:901
int __platform_driver_register(struct platform_driver *drv, struct module *owner)
驱动的标准模板就此定型(全书 40-43 章的驱动示例皆此形):
static const struct of_device_id my_of_match[] = {
{ .compatible = "acme,uart" },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "acme-uart",
.of_match_table = my_of_match,
.pm = &my_pm_ops, /* 电源管理钩子 */
},
};
module_platform_driver(my_driver); /* 注册+注销宏 */
设备树叠加(device tree overlay)与 status = "okay"/"disabled" 使同一内核映像适配同代不同板卡——设备树因此是嵌入式 Linux 的"硬件 ABI",5.4 节 ARM64 引导的 dtb 传递正是它的入口。
小结
设备树以"数据描述硬件"取代枚举协议:compatible 字符串是设备与驱动的钥匙、reg/interrupts/clocks 属性携带资源、of_platform_populate 把节点树批量变成 platform_device;platform 总线为不可枚举外设提供 device/driver 三角的标准舞台,module_platform_driver 模板定型全部本书后续驱动示例。下一节看这些对象如何以属性文件的形式暴露给用户。
39.3 sysfs 设备属性导出
37.2 节从 sysfs 视角看过属性机制;本节回到驱动开发视角——设备模型如何把"一个 kobject + 属性组"变成 /sys/devices/... 下的目录,以及设备级属性(uevent、power、driver 链接)的来源。
39.3.1 device 的 sysfs 投影
一个 PCI 设备在 sysfs 的完整投影:
/sys/devices/pci0000:00/0000:00:1f.3/
├── uevent ← 39.1.3 节的冷plug 出口
├── driver -> ../../../bus/pci/drivers/hda_intel (符号链接)
├── subsystem -> ../../../bus/pci
├── vendor / device / class / irq / resource...
│ ← bus 专属属性 (pci_bus_type 的 dev_groups)
├── power/ ← 电源管理属性 (runtime_status/wakeup)
├── iommu/ ← 43 章 IOMMU 组
└── ...私有属性组
层级即拓扑: 父目录 = 物理父设备 (PCI 根桥→设备)
— kobject.parent 的链就是 39.1.1 节 device 树
三层属性来源汇成一个目录:bus 层(bus_type.dev_groups,类型公共属性)、class 层(class.dev_groups,功能属性如 net 设备的 statistics)、driver/device 层(驱动自定义 attribute_group)。device_register()(drivers/base/core.c)装配时依次 sysfs_create_groups。
39.3.2 驱动自定义属性
// 设备属性的驱动模板 (device.h 的宏族):
static ssize_t fw_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct my_dev *m = dev_get_drvdata(dev);
return sysfs_emit(buf, "%u.%u\n", m->fw_major, m->fw_minor);
}
static DEVICE_ATTR_RO(fw_version); /* 只读属性 */
static const struct attribute_group my_group = {
.attrs = (struct attribute *[]) {
&dev_attr_fw_version.attr,
NULL,
},
};
// probe 里: device_add_group(&pdev->dev, &my_group)
show/store 的缓冲纪律:show 只能写一页且必须用 sysfs_emit(37.2.2 节格式化安全);store 收到的是以 NUL 结尾的整页副本——解析必须容忍尾随换行。dev_get_drvdata/dev_set_drvdata 是 probe 与属性回调间传递私有数据的标准通道(probe 里 platform_set_drvdata(pdev, priv))。
链接文件的三个方向都是设备模型自动维护的:driver -> 指向绑定驱动、bus 的 devices ->/drivers -> 互指、supplier:/consumer: 链接表达资源依赖(39.4 节 device_links 的 sysfs 投影)。属性写即配置的边界:sysfs 只放"一人一值",多字段/二进制一律走 ioctl/netlink——37.2.3 节分工纪律在设备层的执行。
小结
设备级 sysfs 目录由三层属性源装配(bus 公共/class 功能/driver 私有),拓扑即 kobject 父链,driver/subsystem/supplier 链接自动维护;驱动以 DEVICE_ATTR_* 宏 + attribute_group 声明属性,dev_drvdata 传递私有态,sysfs_emit 守住格式化安全。属性导出是设备模型"状态可见"的一半;下一节回到"对象如何被绑定"——注册与 probe。
39.4 设备注册与自动探测 (probe)
设备与驱动的配对是设备模型的心脏:任一方注册都可能触发绑定,绑定流程(really_probe)按序执行依赖检查、资源配置、驱动 probe;资源未就绪则以 EPROBE_DEFER 延迟重试。本节解剖这条流水线。
39.4.1 bus_type:匹配的裁判
// include/linux/device/bus.h:83(节选要点)
struct bus_type {
const char *name;
const struct attribute_group **bus_groups;
const struct device_type *dev_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(...);
int (*probe)(struct device *dev);
void (*remove)(struct device *dev);
...
int (*dma_configure)(struct device *dev); /* 43 章接口 */
};
// include/linux/device/driver.h:98
struct device_driver {
const char *name;
const struct bus_type *bus;
struct module *owner;
int (*probe)(...);
...
const struct of_match_table *of_match_table; /* 39.2 节钥匙 */
...
bool suppress_bind_attrs;
};
总线是匹配算法的载体:pci_bus_type.match 比对设备 ID 表、platform_bus_type.match 走 compatible 字符串、i2c/spi/scsi 各有变体。probe 的调用序是"先 bus->probe(总线钩子,如 PCI 的资源使能)再 driver->probe(驱动本体)"。
39.4.2 really_probe:绑定主流程
// drivers/base/dd.c:667-760(节选)
static int really_probe(struct device *dev, const struct device_driver *drv)
{
...
if (defer_all_probes) {
/* ...device_block_probing() ... wait_for_device_probe()... */
return -EPROBE_DEFER;
}
link_ret = device_links_check_suppliers(dev); /* :681 依赖就绪检查 */
if (link_ret == -EPROBE_DEFER)
return link_ret;
...
re_probe:
device_set_driver(dev, drv);
ret = pinctrl_bind_pins(dev); /* 引脚配置 */
if (ret)
goto pinctrl_bind_failed;
if (dev->bus->dma_configure) { /* DMA 域配置 */
ret = dev->bus->dma_configure(dev); /* :709 43 章入口 */
if (ret)
goto pinctrl_bind_failed;
}
...
ret = dev->bus->probe(dev); /* 总线钩子 */
if (ret)
goto probe_failed;
if (drv->probe) {
ret = drv->probe(dev); /* 驱动 probe */
...
}
...
driver_bound(dev); /* :459 绑定完成: 链接/电源 */
...
}
probe 的检查清单 (代码序 = 执行序):
[1] 全局探测封锁? → defer (device_block_probing 场景)
[2] device_links_check_suppliers: supplier 驱动都 bound?
(时钟/GPIO/regulator 的"消费者→提供者"图,
phandle 引用经 DT 建立)
[3] pinctrl 引脚、[4] dma_configure (43.2 节)
[5] bus->probe → drv->probe
[6] driver_bound (:459): 标 bound、consumer 依赖
释放 (等待本驱动的下游设备获准重试)
任何一步失败: probe_failed 路径清理
(remove 已 probe 的部分、release 资源)
39.4.3 EPROBE_DEFER:延迟协议
deferred probing (内核最优雅的"稍后再试"):
驱动 probe 里资源未就绪 (时钟驱动还没注册):
return -EPROBE_DEFER;
→ 设备放回 deferred 链, 不算失败
触发源: 任何驱动成功 bound / 模块加载 / 手动
/sys/.../drivers_probe 写入 → 重扫 deferred 链
副作用控制: prevent_deferred_probe (platform.h:239
区字段) 标"此驱动不许延迟"(串口控制台类)
device_links 是它的声明式升级:
DT/固件声明依赖 → 内核自动建 supplier-consumer
链 → [2]/[6] 两端自动管理 — 驱动不再手写
"时钟没好吗? 那我 -EPROBE_DEFER"
双向注册的对称性:device_register() 先于驱动 → 尝试匹配全部已注册驱动,失败进 unbound 队列等待;driver_register() 后于设备 → 沿总线枚举全部未绑定设备逐个 __driver_probe_device(dd.c:801/883 两处 really_probe 调用点即"设备驱动"与"驱动扫描设备"两条入口)。bind/unbind 的 sysfs 钩子(drivers/.../bind 文件写入)提供人工强制配对——调试驱动匹配的利器。
小结
绑定流水线由 bus_type 裁决匹配(PCI 比 ID 表、platform 比 compatible),really_probe 按序执行"全局封锁→supplier 依赖→pinctrl→dma_configure→bus probe→driver probe→driver_bound";EPROBE_DEFER 把"资源未就绪"变成可重试的软状态,device_links 以声明式依赖图取代手写延迟。设备与驱动双向注册、sysfs bind/unbind 人工兜底——设备被发现、配对、可见的闭环完成。设备模型四章至此齐备;下一章进入第一个驱动类型:字符设备。