在 Linux 内核中,设备模型是连接硬件和软件的桥梁。理解 kobject 和 kset 这两个核心概念,对于开发高质量的内核驱动至关重要。本文将深入解析这两个概念,并通过实例代码展示如何在内核模块中使用它们。
一、Linux 设备模型概述
Linux 设备模型的核心目标是:
提供统一的设备管理机制 实现设备与驱动的分离 支持热插拔和电源管理 通过 sysfs 文件系统向用户空间暴露设备层次结构
而这一切的基础,就是kobject和kset。
二、kobject 与 kset 的核心概念
1. kobject
内核中最基本的对象结构 提供引用计数、父子关系和生命周期管理 是所有设备模型对象的基础 对应 sysfs 中的一个目录
2. kset
kobject 的集合,用于组织 kobject 本身也是一个 kobject 提供对象分组和过滤机制 对应 sysfs 中的一个子目录树
3. 关键数据结构
c
// include/linux/kobject.h
struct kobject {
const char *name; // 对象名称
struct list_head entry; // 链表节点
struct kobject *parent; // 父对象
struct kset *kset; // 所属kset
struct kobj_type *ktype; // 对象类型
struct sysfs_dirent *sd; // sysfs目录项
struct kref kref; // 引用计数
unsigned int state_initialized:1; // 初始化状态标志
...
};
c
// kset定义
struct kset {
struct list_head list; // 包含的kobject链表
spinlock_t list_lock; // 链表锁
struct kobject kobj; // 嵌入的kobject
const struct kset_uevent_ops *uevent_ops; // uevent操作
...
};
三、kobject 与 kset 的实现源码
下面通过一个完整的内核模块示例,展示如何使用 kobject 和 kset 创建自定义设备模型:
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>
/* 模块参数 */
static int my_param = 42;
module_param(my_param, int, 0644);
MODULE_PARM_DESC(my_param, "A sample parameter");
/* 设备私有数据结构 */
struct my_device {
struct kobject kobj; // 嵌入的kobject
int value; // 设备值
struct mutex lock; // 并发控制锁
};
static struct my_device *my_dev;
/* kobject属性操作函数 */
static ssize_t value_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
struct my_device *dev = container_of(kobj, struct my_device, kobj);
mutex_lock(&dev->lock);
ssize_t ret = sprintf(buf, "%d\n", dev->value);
mutex_unlock(&dev->lock);
return ret;
}
static ssize_t value_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct my_device *dev = container_of(kobj, struct my_device, kobj);
int ret;
long val;
mutex_lock(&dev->lock);
ret = kstrtol(buf, 10, &val);
if (ret) {
mutex_unlock(&dev->lock);
return ret;
}
dev->value = val;
mutex_unlock(&dev->lock);
return count;
}
/* 定义kobject属性 */
static struct kobj_attribute value_attr =
__ATTR(value, 0664, value_show, value_store);
/* 定义参数属性 */
static ssize_t param_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", my_param);
}
static ssize_t param_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
return kstrtoint(buf, 10, &my_param);
}
static struct kobj_attribute param_attr =
__ATTR(param, 0664, param_show, param_store);
/* kobject释放函数 */
static void my_kobj_release(struct kobject *kobj)
{
struct my_device *dev = container_of(kobj, struct my_device, kobj);
pr_info("My device kobject released\n");
kfree(dev);
}
/* 不使用groups,改用手动创建属性 */
static struct kobj_type my_ktype = {
.release = my_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};
/* 创建kset */
static struct kset *my_kset;
/* kset的uevent操作 */
static int my_uevent_filter(struct kset *kset, struct kobject *kobj)
{
pr_info("Uevent filter called for %s\n", kobject_name(kobj));
return 1; // 允许发送uevent
}
static const char *my_uevent_name(struct kset *kset,
struct kobject *kobj)
{
return kobject_name(kobj);
}
static int my_uevent(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env)
{
pr_info("Uevent generated for %s\n", kobject_name(kobj));
return 0;
}
static struct kset_uevent_ops my_uevent_ops = {
.filter = my_uevent_filter,
.name = my_uevent_name,
.uevent = my_uevent,
};
/* 模块初始化 */
static int __init my_module_init(void)
{
int ret;
/* 创建kset */
my_kset = kset_create_and_add("my_kset", &my_uevent_ops, kernel_kobj);
if (!my_kset) {
pr_err("Failed to create kset\n");
return -ENOMEM;
}
/* 分配并初始化设备结构 */
my_dev = kzalloc(sizeof(*my_dev), GFP_KERNEL);
if (!my_dev) {
pr_err("Failed to allocate device\n");
kset_unregister(my_kset);
return -ENOMEM;
}
/* 初始化kobject */
kobject_init(&my_dev->kobj, &my_ktype);
/* 设置kobject的父对象为my_kset */
my_dev->kobj.kset = my_kset;
/* 设置kobject名称 */
ret = kobject_add(&my_dev->kobj, NULL, "my_device");
if (ret) {
pr_err("Failed to add kobject\n");
kfree(my_dev);
kset_unregister(my_kset);
return ret;
}
/* 手动创建属性文件 */
ret = sysfs_create_file(&my_dev->kobj, &value_attr.attr);
if (ret) {
pr_err("Failed to create value attribute\n");
goto err_attr;
}
ret = sysfs_create_file(&my_dev->kobj, ¶m_attr.attr);
if (ret) {
pr_err("Failed to create param attribute\n");
goto err_param;
}
/* 初始化设备值 */
my_dev->value = 0;
mutex_init(&my_dev->lock);
pr_info("My module initialized\n");
return 0;
err_param:
sysfs_remove_file(&my_dev->kobj, &value_attr.attr);
err_attr:
kobject_put(&my_dev->kobj);
kset_unregister(my_kset);
return ret;
}
/* 模块退出 */
static void __exit my_module_exit(void)
{
/* 手动移除属性文件 */
sysfs_remove_file(&my_dev->kobj, &value_attr.attr);
sysfs_remove_file(&my_dev->kobj, ¶m_attr.attr);
/* 释放kobject */
kobject_put(&my_dev->kobj);
/* 释放kset */
kset_unregister(my_kset);
pr_info("My module exited\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cmy");
MODULE_DESCRIPTION("kobject and kset example");
四、源码解析与使用说明
1. kset 的创建与初始化
c
my_kset = kset_create_and_add("my_kset", &my_uevent_ops, kernel_kobj);
创建一个名为 "my_kset" 的 kset 父对象设置为kernel_kobj,对应 sysfs 中的/sys/kernel目录 注册 uevent 回调函数,处理热插拔事件
2. kobject 的创建与属性
c
kobject_init(&my_dev->kobj, &my_ktype);
ret = kobject_add(&my_dev->kobj, NULL, "my_device");
创建一个名为 "my_device" 的 kobject,作为 my_kset 的子对象 通过__ATTR宏定义两个属性:value和param 属性对应 sysfs 中的文件,可通过读写操作与内核交互
3. sysfs 属性操作
c
static ssize_t value_show(struct kobject *kobj, ...)
static ssize_t value_store(struct kobject *kobj, ...)
value_show():处理用户读取value属性的请求 value_store():处理用户写入value属性的请求 通过container_of宏从 kobject 获取设备私有数据
4. 用户空间访问示例

五、kobject 与 kset 的高级应用
1. 设备层次结构组织
可以创建多层 kset 和 kobject,构建复杂的设备树 例如:总线→设备→功能部件
2. 热插拔支持
通过 uevent 机制通知用户空间设备变化 支持动态加载和卸载驱动
3. 电源管理集成
通过 kobject 属性暴露设备电源状态 实现设备的挂起和恢复
六、总线注册与驱动开发
1. 注册自定义总线
在 Linux 设备模型中,总线(Bus)是连接设备(Device)和驱动(Driver)的核心组件,负责管理两者的匹配与通信。以下是注册自定义总线的完整实现:
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/device.h>
/* 总线匹配函数 */
static int my_bus_match(struct device *dev, struct device_driver *drv)
{
pr_info("My bus match: dev=%s, drv=%s\n", dev_name(dev), drv->name);
/* 匹配逻辑示例:检查设备和驱动的名称 */
if (strncmp(dev_name(dev), drv->name, strlen(drv->name)) == 0)
return 1;
return 0;
}
/* uevent事件处理 */
static int my_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
add_uevent_var(env, "MY_BUS_EVENT=1");
return 0;
}
/* 驱动探测函数 */
static int my_bus_probe(struct device *dev)
{
pr_info("My bus probe: device %s detected\n", dev_name(dev));
return 0;
}
/* 驱动移除函数 */
static int my_bus_remove(struct device *dev)
{
pr_info("My bus remove: device %s removed\n", dev_name(dev));
return 0;
}
/* 驱动关闭函数 */
static void my_bus_shutdown(struct device *dev)
{
pr_info("My bus shutdown: device %s shutdown\n", dev_name(dev));
}
/* 自定义总线类型 */
struct bus_type my_bus_type = {
.name = "my_bus", // 总线名称
.dev_groups = NULL, // 设备属性组
.match = my_bus_match, // 设备与驱动匹配函数
.uevent = my_bus_uevent, // uevent事件处理
.probe = my_bus_probe, // 驱动探测函数
.remove = my_bus_remove, // 驱动移除函数
.shutdown = my_bus_shutdown, // 驱动关闭函数
};
/* 导出总线结构体,使其可被其他模块使用 */
EXPORT_SYMBOL_GPL(my_bus_type);
/* 模块初始化 */
static int __init my_bus_init(void)
{
int ret;
/* 注册自定义总线 */
ret = bus_register(&my_bus_type);
if (ret) {
pr_err("Failed to register my_bus\n");
return ret;
}
pr_info("My bus registered successfully\n");
return 0;
}
/* 模块退出 */
static void __exit my_bus_exit(void)
{
/* 注销总线 */
bus_unregister(&my_bus_type);
pr_info("My bus unregistered\n");
}
module_init(my_bus_init);
module_exit(my_bus_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cmy");
MODULE_DESCRIPTION("My bus driver example");
2. 总线注册过程解析
总线注册的核心步骤如下:
定义总线结构体:
c
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_bus_match,
.probe = my_bus_probe,
.remove = my_bus_remove,
.shutdown = my_bus_shutdown,
.uevent = my_bus_uevent,
};
name:总线名称,用于标识总线 match:关键函数,决定设备与驱动是否匹配 probe:驱动探测函数,设备匹配后调用 remove:驱动移除时调用 uevent:总线相关 uevent 事件处理 注册总线到内核:
c
ret = bus_register(&my_bus_type);
该函数会: 创建总线对应的 kset 和 kobject 在 sysfs 中生成/sys/bus/my_bus目录 注册总线的 uevent 处理机制 注销总线:
c
bus_unregister(&my_bus_type);
清理总线相关的所有资源,包括 sysfs 节点和注册的回调函数。
3. 在自定义总线下注册驱动
以下是在已注册的总线下开发并注册驱动的示例:
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
/* 自定义总线类型(假设已注册) */
extern struct bus_type my_bus_type;
/* 驱动探测函数 */
static int my_driver_probe(struct device *dev)
{
pr_info("My driver probed: device %s\n", dev_name(dev));
return 0;
}
/* 驱动移除函数 */
static int my_driver_remove(struct device *dev)
{
pr_info("My driver removed: device %s\n", dev_name(dev));
return 0;
}
/* 驱动结构体 */
static struct device_driver my_driver = {
.name = "my_device", // 驱动名称
.bus = &my_bus_type, // 关联的总线
.probe = my_driver_probe,// 探测函数
.remove = my_driver_remove,// 移除函数
.shutdown = NULL,
.suspend = NULL,
.resume = NULL,
};
/* 模块初始化 */
static int __init my_driver_init(void)
{
int ret;
/* 在自定义总线下注册驱动 */
ret = driver_register(&my_driver);
if (ret) {
pr_err("Failed to register my_driver\n");
return ret;
}
pr_info("My driver registered on my_bus\n");
return 0;
}
/* 模块退出 */
static void __exit my_driver_exit(void)
{
/* 注销驱动 */
driver_unregister(&my_driver);
pr_info("My driver unregistered my_driver.bus = %x\n", my_driver.bus);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cmy");
MODULE_DESCRIPTION("My driver example");

4. 在自定义总线下注册设备
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
/* 自定义总线类型(假设已注册) */
extern struct bus_type my_bus_type;
/* 设备释放函数 */
static void my_device_release(struct device *dev)
{
pr_info("My device released\n");
}
/* 设备结构体 */
static struct device my_device = {
.init_name = "my_device", // 设备初始名称
.bus = &my_bus_type, // 关联的总线
.parent = NULL, // 父设备
.release = my_device_release, // 释放函数
};
/* 模块初始化 */
static int __init my_device_init(void)
{
int ret;
/* 在自定义总线下注册设备 */
ret = device_register(&my_device);
if (ret) {
pr_err("Failed to register my_device\n");
return ret;
}
pr_info("My device registered on my_bus\n");
return 0;
}
/* 模块退出 */
static void __exit my_device_exit(void)
{
/* 注销设备 */
device_unregister(&my_device);
pr_info("My device unregistered\n");
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cmy");
MODULE_DESCRIPTION("My device example");

5. 总线、设备与驱动的匹配机制
当设备和驱动都注册到同一总线后,总线的match函数会被调用,典型匹配流程:
设备注册时:总线会遍历所有已注册的驱动,调用match函数检查是否匹配 驱动注册时:总线会遍历所有已注册的设备,调用match函数检查是否匹配 匹配成功后:自动调用驱动的probe函数初始化设备
c
/* 总线匹配函数 */
static int my_bus_match(struct device *dev, struct device_driver *drv)
{
pr_info("My bus match: dev=%s, drv=%s\n", dev_name(dev), drv->name);
/* 匹配逻辑示例:检查设备和驱动的名称 */
if (strncmp(dev_name(dev), drv->name, strlen(drv->name)) == 0)
return 1;
return 0;
}
6. 查看总线相关信息
sysfs 中的总线目录:
bash
ls /sys/bus/my_bus/

查看已注册的设备和驱动:
bash
# 设备列表
ls /sys/bus/my_bus/devices/
# 驱动列表
ls /sys/bus/my_bus/drivers/

七、总线注册与 kobject/kset 的关系
每个总线对应一个 kset,位于/sys/bus/[bus name] 总线的 kset 包含两个子 kset:devices和drivers 设备和驱动注册时,会自动添加到总线的对应 kset 中 总线的 uevent 机制基于 kobject 的 uevent 扩展实现
通过总线机制,Linux 设备模型实现了设备与驱动的分离管理,使得驱动可以动态加载并自动匹配对应的设备,这是热插拔功能的核心基础。