2. Linux设备模型概述

在Linux内核中,设备模型(Device Model)是一套用于统一描述设备、驱动和总线之间关系的抽象框架。它并不是某个具体驱动,而是贯穿内核各子系统的“公共语言”。理解设备模型,是掌握总线框架、驱动匹配、电源管理、热插拔以及sysfs用户接口的前提。本节从设备模型的作用出发,逐层剖析kobject、kset、ktype三个核心概念,并说明它们如何在sysfs中呈现。

一、设备模型要解决什么问题

在设备模型出现之前,内核中设备、驱动、总线各自为政,电源管理、设备挂起顺序、热插拔通知、用户空间查看设备拓扑都缺乏统一机制。设备模型的核心目标可以归纳为以下几点:

  • 统一表示设备与驱动:用struct device、struct device_driver等结构描述对象,使总线能够进行匹配与绑定。
  • 建立层次关系:设备之间存在父子关系,例如PCI设备下挂USB控制器,USB控制器下挂U盘,形成树状拓扑。
  • 提供引用计数生命周期管理:通过kobject的引用计数决定对象何时真正释放,避免悬空指针。
  • 导出用户空间视图:通过sysfs把内核对象以文件目录形式暴露给用户空间,实现“所见即所得”的设备拓扑。
  • 支撑电源管理与热插拔:设备挂起/恢复需要按父子顺序进行,uevent事件需要沿设备树向上传播。
设备模型的本质:用一棵“对象树”统一描述内核中的设备、驱动、总线、类等实体,并用引用计数管理其生命周期,用sysfs把树形结构映射为用户可见的文件系统。

二、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释放对象。
常见误区:kobject本身通常不单独使用,而是内嵌在更大的结构体中,例如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目录、属性文件之间的映射关系:

kobject / kset / ktype 与 sysfs 的映射关系 内核对象空间 kset(容器) struct kset list + kobj + uevent_ops kobject A name / parent / kref kobject B name / parent / kref ktype(行为模板) struct kobj_type release() + sysfs_ops + default_attrs 决定属性文件与释放方式 sysfs 用户空间视图 /sys/.../kset_name/ kset 对应公共父目录 kobject_A/ 目录 kobject_B/ 目录 属性文件(attribute) 由 ktype->default_attrs 生成 如 uevent、dev、modalias、power/ 读写经 sysfs_ops->show/store 映射 映射 kobject 对应目录,ktype 决定属性文件,kset 提供公共父目录与 uevent 归属

从图中可以看到,内核对象空间与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这棵用户可见的设备树。掌握这三者的分工与协作,是后续理解总线匹配、驱动绑定、电源管理和热插拔机制的基础。