3. 设备驱动模型核心:kobject、kset、ktype 的关系与作用,sysfs 文件系统的原理
Linux 设备驱动模型的核心目标是统一管理系统中所有的设备、驱动、总线和类,并通过 sysfs 文件系统向用户空间暴露一个层次化的视图。理解 kobject、kset 和 ktype 这三个基础数据结构,是掌握驱动模型的关键。
3.1 核心数据结构:kobject
kobject(Kernel Object)是设备驱动模型中最基本的对象,它封装了对象的引用计数、名称、父对象指针以及 sysfs 中的目录入口。几乎所有的“设备”、“驱动”、“总线”在内核中都以 kobject 或其嵌入的容器结构存在。
关键点: kobject 本身不单独存在,它总是嵌入在一个更大的结构体中(如
struct device、struct device_driver)。通过 container_of() 宏可以从 kobject 获取其宿主结构。
// include/linux/kobject.h
struct kobject {
const char *name; // 对象名称,对应 sysfs 目录名
struct list_head entry; // 用于加入 kset 的链表
struct kobject *parent; // 父对象,形成层次结构
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;
unsigned int uevent_suppress:1;
};
3.2 对象集合:kset
kset 是 kobject 的集合,它将同类型的 kobject 组织在一起,并作为这些对象的父目录出现在 sysfs 中。kset 本身也包含一个 kobject,因此它可以被嵌套。
面试高频题: kset 与 kobject 的关系是什么?
答案: kset 是 kobject 的“容器”或“集合”。一个 kobject 可以通过其
答案: kset 是 kobject 的“容器”或“集合”。一个 kobject 可以通过其
kset 字段加入一个 kset,同时 kset 自身的 kobject 可以作为子对象的 parent,从而在 sysfs 中形成目录层次。
struct kset {
struct list_head list; // 链表中所有 kobject 的头部
spinlock_t list_lock;
struct kobject kobj; // kset 自身的 kobject,用于在 sysfs 中创建目录
const struct kset_uevent_ops *uevent_ops; // 用户空间事件通知操作
};
3.3 对象类型:ktype
ktype 定义了同一类 kobject 的共性行为,主要包括属性操作和释放函数。所有属于同一个 ktype 的 kobject 共享相同的 sysfs_ops 和 release 方法。
struct kobj_type {
void (*release)(struct kobject *kobj); // 引用计数归零时的释放函数
const struct sysfs_ops *sysfs_ops; // sysfs 文件读写操作
struct attribute **default_attrs; // 默认属性列表(已废弃,改用属性组)
const struct attribute_group **default_groups; // 默认属性组
};
3.4 三者的关系与作用
下图展示了 kobject、kset、ktype 在设备模型中的协作关系:
3.5 sysfs 文件系统原理
sysfs 是一个基于内存的虚拟文件系统,通常挂载在 /sys 目录下。它将内核中的 kobject 层次结构导出为用户空间的目录和文件,使得用户可以通过读写文件来查看或修改内核对象的属性。
3.5.1 sysfs 与 kobject 的映射
| sysfs 元素 | 内核对象 | 说明 |
|---|---|---|
| 目录 | kobject | 每个 kobject 在 sysfs 中对应一个目录,目录名为 kobject 的 name 字段 |
| 文件 | struct attribute | kobject 的属性通过 sysfs_ops 的 show/store 方法实现读写 |
| 子目录 | kobject->parent | 父子关系通过 parent 指针建立,形成树形结构 |
| 符号链接 | sysfs_create_link() | 用于创建跨层次的关联,如 device 到 driver 的链接 |
3.5.2 sysfs 的典型目录结构
/sys/
├── bus/ # 总线类型(如 pci, usb, platform)
│ ├── pci/
│ │ ├── devices/ # 该总线上的设备符号链接
│ │ └── drivers/ # 该总线上的驱动
│ └── platform/
├── class/ # 设备类(如 net, input, tty)
│ ├── net/
│ └── input/
├── devices/ # 所有设备的真实目录树(按硬件拓扑)
│ └── .../
├── firmware/ # 固件信息
└── kernel/ # 内核参数
3.5.3 sysfs 的读写流程
当用户空间读取或写入 sysfs 文件时,内核会经历以下调用链:
- VFS 层:用户调用
read()/write(),VFS 根据文件路径找到对应的dentry和inode。 - sysfs 层:sysfs 的 inode 操作函数被调用,通过
kernfs找到对应的kernfs_node。 - 属性操作:从
kernfs_node获取关联的kobject,再通过kobject->ktype->sysfs_ops找到show()或store()方法。 - 驱动层:调用驱动注册的属性读写回调函数,完成实际的数据传输。
常见面试陷阱: 面试官可能会问“sysfs 与 procfs 的区别”。
标准回答: procfs 主要用于输出进程和系统运行时信息(如 /proc/cpuinfo),文件内容通常由内核直接生成;而 sysfs 严格对应 kobject 层次结构,每个文件对应一个内核属性,具有统一的 show/store 接口,更适合设备模型的管理。
标准回答: procfs 主要用于输出进程和系统运行时信息(如 /proc/cpuinfo),文件内容通常由内核直接生成;而 sysfs 严格对应 kobject 层次结构,每个文件对应一个内核属性,具有统一的 show/store 接口,更适合设备模型的管理。
3.6 实战:创建一个简单的 kobject 并导出到 sysfs
以下代码演示了如何在内核模块中创建一个 kobject,并将其添加到 sysfs 中:
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
static struct kobject *example_kobj;
// 属性读回调
static ssize_t my_value_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "42\n");
}
// 属性写回调
static ssize_t my_value_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int val;
if (sscanf(buf, "%d", &val) != 1)
return -EINVAL;
pr_info("my_value set to %d\n", val);
return count;
}
// 定义属性
static struct kobj_attribute my_attr =
__ATTR(my_value, 0664, my_value_show, my_value_store);
// 属性组
static struct attribute *attrs[] = {
&my_attr.attr,
NULL,
};
static struct attribute_group attr_group = {
.attrs = attrs,
};
static int __init my_module_init(void)
{
int ret;
// 创建 kobject,父对象为 kernel_kobj(/sys/kernel)
example_kobj = kobject_create_and_add("my_demo", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
// 创建属性组
ret = sysfs_create_group(example_kobj, &attr_group);
if (ret)
kobject_put(example_kobj);
return ret;
}
static void __exit my_module_exit(void)
{
kobject_put(example_kobj);
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
加载模块后,在 /sys/kernel/my_demo/my_value 下会出现一个可读写的文件。
3.7 面试核心问题清单
| 问题 | 核心要点 |
|---|---|
| kobject 的作用是什么? | 提供引用计数、名称管理、父子层次结构,是 sysfs 目录的基础单元 |
| kset 与 kobject 的关系? | kset 是 kobject 的集合,通过链表管理,同时 kset 自身的 kobject 作为父目录 |
| ktype 为什么是共享的? | 同一类 kobject 具有相同的属性操作和释放逻辑,共享 ktype 可以节省内存并统一行为 |
| sysfs 文件如何与内核属性关联? | 通过 kobject->ktype->sysfs_ops 中的 show/store 回调实现 |
| 如何手动创建 sysfs 文件? | 使用 sysfs_create_file() 或 sysfs_create_group(),配合 struct kobj_attribute |
面试加分项: 提到
kernfs(内核文件系统)是 sysfs 的底层实现,它分离了文件系统逻辑与 kobject 管理,使得 sysfs 的代码更加清晰。在 Linux 3.14 之后,sysfs 完全基于 kernfs 重构。