2. Linux设备模型概述
在Linux内核中,设备模型(Device Model)是一套用于统一描述设备、驱动和总线之间关系的抽象框架。它并不是某个具体驱动,而是贯穿内核各子系统的“公共语言”。理解设备模型,是掌握总线框架、驱动匹配、电源管理、热插拔以及sysfs用户接口的前提。本节从设备模型的作用出发,逐层剖析kobject、kset、ktype三个核心概念,并说明它们如何在sysfs中呈现。
一、设备模型要解决什么问题
在设备模型出现之前,内核中设备、驱动、总线各自为政,电源管理、设备挂起顺序、热插拔通知、用户空间查看设备拓扑都缺乏统一机制。设备模型的核心目标可以归纳为以下几点:
- 统一表示设备与驱动:用
struct device、struct device_driver等结构描述对象,使总线能够进行匹配与绑定。 - 建立层次关系:设备之间存在父子关系,例如PCI设备下挂USB控制器,USB控制器下挂U盘,形成树状拓扑。
- 提供引用计数生命周期管理:通过kobject的引用计数决定对象何时真正释放,避免悬空指针。
- 导出用户空间视图:通过sysfs把内核对象以文件目录形式暴露给用户空间,实现“所见即所得”的设备拓扑。
- 支撑电源管理与热插拔:设备挂起/恢复需要按父子顺序进行,uevent事件需要沿设备树向上传播。
二、kobject:设备模型的最小基石
kobject是设备模型中最基本的对象单元,定义于include/linux/kobject.h。它本身并不描述具体设备,而是为其他结构提供通用能力:引用计数、父子关系、名称、sysfs目录项以及uevent通知。
struct kobject {
const char *name; // 在sysfs中的目录名
struct list_head entry; // 挂入所属kset的链表节点
struct kobject *parent; // 父kobject,决定sysfs层级
struct kset *kset; // 所属kset
struct kobj_type *ktype; // 属性与释放方法
struct kernfs_node *sd; // sysfs中的目录项
struct kref 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;
};
kobject的关键操作包括:
kobject_init():初始化kobject,设置ktype,引用计数置1。kobject_add():把kobject加入设备模型,创建sysfs目录,建立父子与kset关系。kobject_del():从设备模型移除,删除sysfs目录。kobject_put():减少引用计数,归零时调用ktype的release释放对象。
struct device内部包含一个struct kobject kobj。通过container_of()可以从kobject反推外层对象。若忘记调用kobject_put(),对象将永远不会释放,造成内存泄漏。
三、kset与ktype:组织与行为
kset是一组kobject的集合,用于把同类对象组织在一起,并在sysfs中表现为一个公共父目录。kset自身也内嵌一个kobject,因此kset可以嵌套,形成层级。典型例子是/sys/bus、/sys/class、/sys/devices这些顶层目录,它们背后往往对应一个kset。
struct kset {
struct list_head list; // 该kset下所有kobject的链表
spinlock_t list_lock;
struct kobject kobj; // kset自身的kobject
const struct kset_uevent_ops *uevent_ops; // uevent处理
};
ktype(struct kobj_type)描述一类kobject的“行为”,即它们拥有哪些属性文件、如何释放。它决定了sysfs中会出现哪些属性文件,以及引用计数归零时如何回收内存。
struct kobj_type {
void (*release)(struct kobject *kobj); // 释放函数
const struct sysfs_ops *sysfs_ops; // 属性读写方法
struct attribute **default_attrs; // 默认属性数组
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
};
三者关系可以概括为:kobject是“点”,kset是“容器”,ktype是“模板”。一个kobject必须属于某个ktype以确定释放方式;它可以属于某个kset以确定sysfs父目录和uevent归属;它的parent决定它在设备树中的具体位置。
四、在sysfs中的体现
sysfs是设备模型面向用户空间的窗口,通常挂载在/sys。内核中的kobject树与sysfs目录树一一对应:每个kobject对应一个目录,kobject的name就是目录名,ktype的default_attrs对应目录下的属性文件。
下面这张图展示了kobject、kset、ktype与sysfs目录、属性文件之间的映射关系:
从图中可以看到,内核对象空间与sysfs视图是严格对应的。用户执行ls /sys/bus/pci/devices/看到的每个目录,背后都是一个kobject;目录下的uevent、vendor、device等文件,则来自ktype的default_attrs。kset则提供了/sys/bus/pci/这样的公共父目录。
五、典型场景与操作示例
以查看一个PCI设备为例,用户空间可以这样观察设备模型:
# 查看设备树中的PCI设备
ls /sys/devices/pci0000:00/
# 查看某个设备的属性文件
ls /sys/bus/pci/devices/0000:00:1f.2/
# 输出示例:uevent vendor device class resource driver ...
# 读取厂商ID
cat /sys/bus/pci/devices/0000:00:1f.2/vendor
# 0x8086
# 查看设备与驱动的绑定关系
ls -l /sys/bus/pci/devices/0000:00:1f.2/driver
# driver -> ../../../../bus/pci/drivers/ahci
这些目录和符号链接并非人为创建,而是设备模型在设备注册、驱动绑定过程中自动生成的。当驱动通过device_register()注册设备时,内核会初始化内嵌的kobject,调用kobject_add()创建sysfs目录,并根据ktype生成属性文件。
六、常见误区与复盘
| 误区 | 正确理解 |
|---|---|
| kobject就是设备 | kobject只是通用基础对象,设备是内嵌kobject的更大结构体 |
| kset就是kobject的父对象 | kset是容器,kobject的parent可以不是kset,二者职责不同 |
| ktype可有可无 | ktype决定release和属性文件,缺失会导致无法释放或sysfs无属性 |
| sysfs目录是手动创建的 | sysfs目录由kobject_add自动创建,属性由ktype自动生成 |
| 引用计数可以忽略 | kobject_put必须与kobject_init/add配对,否则对象泄漏 |
struct device中的struct kobject kobj成员,再顺着device_register()→device_add()→kobject_add()的调用链,观察sysfs目录和属性文件是如何一步步生成的。这样能把抽象概念与真实代码路径对应起来。
总结来说,设备模型用kobject提供通用对象能力,用kset组织同类对象,用ktype定义行为模板,三者共同支撑起sysfs这棵用户可见的设备树。掌握这三者的分工与协作,是后续理解总线匹配、驱动绑定、电源管理和热插拔机制的基础。