当前位置: 首页 > news >正文

【Linux 设备模型框架 kobject 和 kset】

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, &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 的创建与初始化

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

http://www.lqws.cn/news/550765.html

相关文章:

  • Java 大视界 -- Java 大数据在智能安防视频监控系统中的目标轨迹预测与防范策略制定(325)
  • 【k近邻】 K-Nearest Neighbors算法原理及流程
  • 机器学习3——参数估计之极大似然估计
  • C++并发编程-4.unique_lock,共享锁和递归锁
  • 详解HashMap底层原理
  • 电脑远程控制另一台电脑无法连接怎么办
  • PostgreSQL 容器化分布式技术方案
  • 基于51单片机-蜂鸣器演奏《飞雪玉花》
  • 什么是故障注入测试
  • 强化联邦学习的车联网 DDoS 攻击检测
  • 【图像处理入门】12. 综合项目与进阶:超分辨率、医学分割与工业检测
  • FLUX.1 Kontext(Dev 版)训练lora基础教程
  • TiDB AUTO_RANDOM 超大主键前端精度丢失排查:JavaScript Number 限制与解决方案
  • 内测开启!看海量化回测系统V2.0版本更新,基于miniQMT的回测系统问世!
  • Threejs开发指南(第七篇 利用AI进行threejs开发)
  • 封装nuxt3的SSR请求和CSR请求方法
  • 1 Studying《Is Parallel Programming Hard》6-9
  • 双指针技巧深度解析
  • C#系统学习第二章——第一个C#程序
  • P27:RNN实现阿尔茨海默病诊断
  • 华为云Flexus+DeepSeek征文|基于Dify+ModelArts开发AI智能会议助手
  • 本地部署 WordPress 博客完整指南(基于 XAMPP)
  • nt!MiFlushSectionInternal函数分析从nt!IoSynchronousPageWrite函数到Ntfs!NtfsFsdWrite函数
  • 三阶落地:腾讯云Serverless+Spring Cloud的微服务实战架构
  • React中的ErrorBoundary
  • 【经验】新版Chrome中Proxy SwitchyOmega2已实效,改为ZeroOmega
  • 车载诊断架构 --- 诊断与ECU平台工作说明书
  • SQL Server for Linux 如何实现高可用架构
  • 【策划所需编程知识】
  • 中国双非高校经费TOP榜数据分析