Linux 设备模型框架 kobject 和 kset
- 一、Linux 设备模型概述
- 二、kobject 与 kset 的核心概念
- 1. kobject
- 2. kset
- 3. 关键数据结构
- 三、kobject 与 kset 的实现源码
- 四、源码解析与使用说明
- 1. kset 的创建与初始化
- 2. kobject 的创建与属性
- 3. sysfs 属性操作
- 4. 用户空间访问示例
- 五、kobject 与 kset 的高级应用
- 1. 设备层次结构组织
- 2. 热插拔支持
- 3. 电源管理集成
- 六、总线注册与驱动开发
- 1. 注册自定义总线
- 2. 总线注册过程解析
- 3. 在自定义总线下注册驱动
- 4. 在自定义总线下注册设备
- 5. 总线、设备与驱动的匹配机制
- 6. 查看总线相关信息
- 七、总线注册与 kobject/kset 的关系
在 Linux 内核中,设备模型是连接硬件和软件的桥梁。理解 kobject 和 kset 这两个核心概念,对于开发高质量的内核驱动至关重要。本文将深入解析这两个概念,并通过实例代码展示如何在内核模块中使用它们。
一、Linux 设备模型概述
Linux 设备模型的核心目标是:
提供统一的设备管理机制
实现设备与驱动的分离
支持热插拔和电源管理
通过 sysfs 文件系统向用户空间暴露设备层次结构
而这一切的基础,就是kobject和kset。
二、kobject 与 kset 的核心概念
1. kobject
内核中最基本的对象结构
提供引用计数、父子关系和生命周期管理
是所有设备模型对象的基础
对应 sysfs 中的一个目录
2. kset
kobject 的集合,用于组织 kobject
本身也是一个 kobject
提供对象分组和过滤机制
对应 sysfs 中的一个子目录树
3. 关键数据结构
// include/linux/kobject.h
struct kobject {const char *name; // 对象名称struct list_head entry; // 链表节点struct kobject *parent; // 父对象struct kset *kset; // 所属ksetstruct kobj_type *ktype; // 对象类型struct sysfs_dirent *sd; // sysfs目录项struct kref kref; // 引用计数unsigned int state_initialized:1; // 初始化状态标志...
};
// kset定义
struct kset {struct list_head list; // 包含的kobject链表spinlock_t list_lock; // 链表锁struct kobject kobj; // 嵌入的kobjectconst struct kset_uevent_ops *uevent_ops; // uevent操作...
};
三、kobject 与 kset 的实现源码
下面通过一个完整的内核模块示例,展示如何使用 kobject 和 kset 创建自定义设备模型:
#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; // 嵌入的kobjectint 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 的创建与初始化
my_kset = kset_create_and_add("my_kset", &my_uevent_ops, kernel_kobj);
创建一个名为 “my_kset” 的 kset
父对象设置为kernel_kobj,对应 sysfs 中的/sys/kernel目录
注册 uevent 回调函数,处理热插拔事件
2. kobject 的创建与属性
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 属性操作
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)的核心组件,负责管理两者的匹配与通信。以下是注册自定义总线的完整实现:
#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. 总线注册过程解析
总线注册的核心步骤如下:
定义总线结构体:
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 事件处理
注册总线到内核:
ret = bus_register(&my_bus_type);
该函数会:
创建总线对应的 kset 和 kobject
在 sysfs 中生成/sys/bus/my_bus目录
注册总线的 uevent 处理机制
注销总线:
bus_unregister(&my_bus_type);
清理总线相关的所有资源,包括 sysfs 节点和注册的回调函数。
3. 在自定义总线下注册驱动
以下是在已注册的总线下开发并注册驱动的示例:
#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. 在自定义总线下注册设备
#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函数初始化设备
/* 总线匹配函数 */
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 中的总线目录:
ls /sys/bus/my_bus/
查看已注册的设备和驱动:
# 设备列表
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 设备模型实现了设备与驱动的分离管理,使得驱动可以动态加载并自动匹配对应的设备,这是热插拔功能的核心基础。