Linux 设备模型框架 kobject 和 kset

在 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, &param_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, &param_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 设备模型实现了设备与驱动的分离管理,使得驱动可以动态加载并自动匹配对应的设备,这是热插拔功能的核心基础。

相关推荐
namehu44 分钟前
阿里云 acme.sh install timeout(超时)问题解析与解决方案
linux·前端·https
南极浮冰1 小时前
【无标题】
linux·人工智能·python
小孙姐1 小时前
Linux-Day02.Linux指令
linux·运维·服务器
@BreCaspian1 小时前
Kazam产生.movie.mux后恢复视频为.mp4
linux·ubuntu·音视频
搞不懂语言的程序员2 小时前
Linux Epool的作用
linux·服务器
jzy37112 小时前
主机管理优化方案:安全加固、资源整合与跨团队协作
linux·tomcat
Neng_Miao2 小时前
文件与目录操作命令
linux·运维
倔强的石头1062 小时前
【Linux指南】软件安装全解析:从源码到包管理器的进阶之路
linux·运维·服务器
爱地球的曲奇2 小时前
Linux环境下(Ubuntu)Fortran语言如何安装配置NetCDF
linux·ubuntu·netcdf
freshman_y2 小时前
15个命令上手Linux!
linux·运维·服务器