linux设备模型
概念
kobject和kset是Linux内核中用于管理内核对象的基本概念。都是一个结构体
struct kobject{
const char *name;
struct list_headentry;
struct kobject *parent;
...
}
kobject 全称是 kernel object,即内核对象。
- 它是 Linux 内核设备模型的基础基石,用于统一管理内核中的各种对象(设备、驱动、总线等)。
- 每一个 kobject 实例,都会在
/sys/目录下对应生成一个目录,通过 sysfs 文件系统向用户态暴露内核信息。 
kset(内核对象集合)是一种用于组织和管理一组相关kobject的容器,结构体如下:
struct kset {
struct list_head list; // 链接该集合下所有的 kobject
spinlock_t list_lock;
struct kobject kobj; // 自身作为一个内核对象
const struct kset_uevent_ops *uevent_ops;
...
};
kset 和kobject 的关系

kobject创建
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
#include<linux/kobject.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
/* 定义 3 个 kobject 指针,用来保存创建出来的内核对象 */
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
/* 定义 kobject 类型(必须要有,第二种创建方法需要) */
struct kobj_type mytype;
/* 模块初始化函数(加载模块时执行) */
static int mykobj_init(void)
{
int ret;
/* ====================== 第一种创建 kobject 的方法 ======================
* 函数:kobject_create_and_add
* 作用:创建 + 注册 一步完成
* 参数1:名字 → 在 /sys 下生成的目录名
* 参数2:父kobject → 决定目录放在哪里
*/
// 创建 mykobject01,父目录为 NULL → 放在 /sys/ 根目录下
mykobject01 = kobject_create_and_add("mykobject01", NULL);
// 创建 mykobject02,父目录是 mykobject01
// → 最终路径:/sys/mykobject01/mykobject02
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
/* ====================== 第二种创建 kobject 的方法 ======================
* 分两步:
* 1. 分配 kobject 内存
* 2. 初始化并添加到内核
*/
// 分配内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 初始化并添加到 /sys/ 根目录,名字 mykobject03
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
return 0;
}
/* 模块退出函数(卸载模块时执行) */
static void mykobj_exit(void)
{
/* 释放所有 kobject(必须做,否则内存泄漏) */
kobject_put(mykobject01);
kobject_put(mykobject02);
kobject_put(mykobject03);
}
/* 注册模块入口/出口 */
module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
创建kset
/**
* kset_create_and_add - 动态创建一个 struct kset 并将其添加到 sysfs 文件系统
*
* @name: 该 kset 的名称(对应 /sys/ 下的目录名)
* @uevent_ops: 该 kset 对应的 struct kset_uevent_ops 结构体指针
* (用于处理用户态事件通知,如热插拔等)
* @parent_kobj: 该 kset 的父 kobject(即父目录),如果没有则传 NULL
*
* 该函数会动态创建一个 kset 结构体,并将其注册到 sysfs 中。
* 当你不再使用该结构体时,需要调用 kset_unregister(),
* 该结构体会在不再被引用时被自动释放。
*
* 如果 kset 创建失败,函数将返回 NULL。
*/
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj);
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
// 定义kobject结构体指针,用于表示第一个自定义内核对象
struct kobject *mykobject01;
// 定义kobject结构体指针,用于表示第二个自定义内核对象
struct kobject *mykobject02;
// 定义kset结构体指针,用于表示自定义内核对象的集合
struct kset *mykset;
// 定义kobj_type结构体,用于定义自定义内核对象的类型
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建并添加kset,名称为mykset,父kobject为NULL,事件操作集为NULL
mykset = kset_create_and_add("mykset", NULL, NULL);
// 为mykobject01分配内核内存
mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykobject01归属到mykset集合中
mykobject01->kset = mykset;
// 初始化并添加mykobject01到sysfs,名称为mykobject01
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");
// 为mykobject02分配内核内存
mykobject02 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykobject02归属到mykset集合中
mykobject02->kset = mykset;
// 初始化并添加mykobject02到sysfs,名称为mykobject02
ret = kobject_init_and_add(mykobject02, &mytype, NULL, "%s", "mykobject02");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放mykobject01的引用计数
kobject_put(mykobject01);
// 释放mykobject02的引用计数
kobject_put(mykobject02);
kset_unregister(mykset);
}
// 指定模块的初始化函数
module_init(mykobj_init);
// 指定模块的退出函数
module_exit(mykobj_exit);
// 模块使用的许可证
MODULE_LICENSE("GPL");
// 模块的作者
MODULE_AUTHOR("topeet");
设备模型简介
设备模型通过提供统一的设备描述和管理机制,简化了 驱动的编写和维护过程,提高了代码的复用性和可维护性,设备模型包含以下四个概念:
1. 总线(Bus)
总线是 CPU 与设备之间进行信息交互的通道,所有设备都需要连接到总线上。总线分为两类:
- 虚拟总线:如 platform 总线,用于管理无真实物理总线的设备。
- 外设总线:如 I2C、SPI、USB、PCI 等真实物理总线。
2. 设备(Device)
将系统中所有硬件设备的共同属性(如名字、属性、从属关系、所属类等)抽象出来,就形成了 device 结构体,代表一个具体的硬件设备实例。
3. 类(Class)
具有相似功能或属性的设备集合,用于对设备进行逻辑分类。
- 类比理解:早饭、午饭、晚饭都属于 “饭” 这一类;输入设备(键盘、鼠标)都属于
input类。 - 在
/sys/class/下可以看到各类设备的目录。
4. 驱动(Driver)
对应硬件设备的驱动程序,负责与硬件交互、实现设备功能,并与内核其他子系统协作。
- 驱动会向总线注册,当总线上有匹配的设备时,总线会自动将设备与驱动绑定。

什么是sysfs文件系统
它以一种层次结构的方式组织数据,并将这些数据表示为文 件和目录,使得用户空间可以通过文件系统接口访问和操作内核对象的属性
比如:当使用kobject 时,通常不会单独使用它,而是将其嵌入到一个数据结构中。这样做的目 的是将高级对象接入到设备模型中。比如cdev结构体和platform_device结构体,如下所示: cdev 结构体如下所示,其成员有kobject
所以我们也可以把总线,设备,驱动看作是kobject的派生类。因为他们都是设备模型中 的实体,通过继承或扩展kobject来实现与设备模型的集成
和设备模型有关的文件夹为bus,class,devices。完整路径为如下所示:
/sys/bus
/sys/class
/sys/devices

1:/sys/class
/sys/devices:该目录包含了系统中所有设备的子目录。每个设备子目录代表一个具体的设
备,通过其路径层次结构和符号链接反映设备的关系和拓扑结构。每个设备子目录中包含了设
备的属性、状态和其他相关信息。如下所示:

2:/sys/bus
该目录包含了总线类型的子目录。每个子目录代表一个特定类型的总线,例如 PCI、USB 等。每个总线子目录中包含与该总线相关的设备和驱动程序的信息


3:/sys/class:
该目录包含了设备类别的子目录。每个子目录代表一个设备类别,例如磁盘、 网络接口等。每个设备类别子目录中包含了属于该类别的设备的信息。如下图所示:

4:sys 目录层次图解

引用计数器
引用计数器(referencecounting)是一种内存管理技术,用于跟踪对象或资源的引用数量。 它通过在对象被引
引用计数器的基本原理如下:
1:对象或资源被创建时,引用计数器初始化为1。
2:当有新的引用指向对象或资源时,引用计数器增加。
3:当引用不再指向对象或资源时(引用被删除、超出作用域等),引用计数器减少。
4:当引用计数器的值为0时,表示没有任何引用指向对象或资源,可以安全地释放对象或资 源,并进行相关的清理操作。
structkref定义在include/linux/kref.h 头文件当中,本质是一个int型变量
struct kref {
refcount_t refcount;
};
typedef struct {
atomic_t refs;
} refcount_t;
typedef struct {
int counter;
} atomic_t;
在 Linux 系统中,引用计数器用结构体 kref 来表示。
struct kref 定义在 include/linux/kref.h 头文件当中,本质是一个 int 型变量。
在使用引用计数器时,通常会将结构体kref嵌入到其他结构体中,例如struct kobject
struct kobject {
const char *name; // 该 kobject 的名称(对应 sysfs 目录名)
...
struct kref kref; // 引用计数器,用于管理对象生命周期
};
图解如下:

释放Kobject
一、两种创建方法对比 - kobject_create_and_add:
自动申请内存,自动使用内置 ktype - kobject_init_and_add:
手动申请内存,需自己实现 ktype -
本质:底层流程一致,最终都会在 sysfs 中创建目录
二、kobject_create_and_add 调用流程
kobject_create_and_add └── kobject_create ├── kzalloc (自动分配内存)
└── kobject_init (使用内置 dynamic_kobj_ktype) └── kobject_add └── kobject_add_varg └── kobject_add_internal └── create_dir └── sysfs_create_dir_ns (创建 sysfs 目录)
三、kobject_init_and_add 调用流程
kobject_init_and_add ├── kobject_init (需自己提供 ktype) └── kobject_add_varg └── kobject_add_internal └── create_dir └── sysfs_create_dir_ns (创建 sysfs 目录) 四、释放逻辑(两种方法一致) - 统一调用 kobject_put() - 引用计数归 0 时,自动执行 kobj_type->release() - 完成内存与资源的释放
当引用计数器变为0的时候,调用kobject_type里面的release函数释放内存
完善kobject_type结构体
#include<linux/kobject.h>
struct kobject *mykobject03;
/*
* 动态创建的 kobject 释放函数
* 作用:当 kobject 引用计数为 0 时,内核自动调用此函数
*/
static void dynamic_kobj_release(struct kobject *kobj)
{
/* 打印调试信息:__func__ 代表当前函数名 */
printk("kobject:(%p):%s\n", kobj, __func__);
/* 释放 kobject 占用的内核内存 */
kfree(kobj);
}
//自定义Type
struct kobj_type mytype{
.release=dynamic_kobj_release;
}
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建 kobject 的第二种方法
// 1. 使用 kzalloc 函数分配了一个 kobject 对象的内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2. 初始化并添加到内核中,名为 "mykobject03"
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
return 0;
}
在计数器为0的时候自动执行release函数
第二种方法创建属性文件并实现读写
1:所有属性文件读写函数走一个代码
代码如下:
attribute是kobject下面的属性,然后sysfs对kobject和属性进行读写,所有的属性文件读写都会调用一个函数,所以要加if判断
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
// 自定义的kobject结构体,包含一个kobject对象和两个整型值
struct mykobj
{
struct kobject kobj;
int value1;
int value2;
};
// 定义mykobj结构体指针变量mykobject01
struct mykobj *mykobject01;
// 自定义的kobject释放函数
static void dynamic_kobj_release(struct kobject *kobj)
{
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
printk("kobject:(%p):%s\n", kobj, __func__);
kfree(mykobject01);
}
// 自定义的attribute对象value1和value2
struct attribute value1 = {
.name = "value1",
.mode = 0666,
};
struct attribute value2 = {
.name = "value2",
.mode = 0666,
};
// 将attribute对象放入数组中
struct attribute *myattr[] = {
&value1,
&value2,
NULL,
};
// 自定义的show函数,用于读取属性值
ssize_t myshow(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count;
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
if (strcmp(attr->name, "value1") == 0)
{
count = sprintf(buf, "%d\n", mykobject01->value1);
}
else if (strcmp(attr->name, "value2") == 0)
{
count = sprintf(buf, "%d\n", mykobject01->value2);
}
else
{
count = 0;
}
return count;
}
// 自定义的store函数,用于写入属性值
ssize_t mystore(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size)
{
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
if (strcmp(attr->name, "value1") == 0)
{
sscanf(buf, "%d\n", &mykobject01->value1);
}
else if (strcmp(attr->name, "value2") == 0)
{
sscanf(buf, "%d\n", &mykobject01->value2);
}
return size;
}
// 自定义的sysfs_ops结构体,包含show和store函数指针
struct sysfs_ops myops = {
.show = myshow,
.store = mystore,
};
// 自定义的kobj_type结构体,包含释放函数、默认属性和sysfs_ops
static struct kobj_type mytype = {
.release = dynamic_kobj_release,
.default_attrs = myattr,
.sysfs_ops = &myops,
};
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 分配并初始化mykobject01
mykobject01 = kzalloc(sizeof(struct mykobj), GFP_KERNEL);
mykobject01->value1 = 1;
mykobject01->value2 = 1;
// 初始化并添加mykobject01到内核中,名为"mykobject01"
ret = kobject_init_and_add(&mykobject01->kobj, &mytype, NULL, "%s", "mykobject01");
return 0;
}

2:所有属性文件读写函数走自己的代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
// 自定义的show函数,用于读取属性value1
ssize_t show_myvalue1(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count;
count = sprintf(buf, "show_myvalue1\n");
return count;
}
// 自定义的store函数,用于写入属性value1
ssize_t store_myvalue1(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
printk("buf is %s\n", buf);
return count;
}
// 自定义的show函数,用于读取属性value2
ssize_t show_myvalue2(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count;
count = sprintf(buf, "show_myvalue2\n");
return count;
}
// 自定义的store函数,用于写入属性value2
ssize_t store_myvalue2(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
printk("buf is %s\n", buf);
return count;
}
// 定义attribute对象 value1 和 value2,后面参数为读写函数
struct kobj_attribute value1 = __ATTR(value1, 0664, show_myvalue1, store_myvalue1);
struct kobj_attribute value2 = __ATTR(value2, 0664, show_myvalue2, store_myvalue2);
// 自定义的kobject释放函数
static void dynamic_kobj_release(struct kobject *kobj)
{
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
printk("kobject: (%p): %s\n", kobj, __func__);
kfree(mykobject01);
}
// 将attribute对象放入数组中
struct attribute *myattr[] = {
&value1.attr,
&value2.attr,
NULL,
};
// 统一的show分发函数
ssize_t myshow(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count;
struct kobj_attribute *kobj_attr = container_of(attr, struct kobj_attribute, attr);
count = kobj_attr->show(kobj, kobj_attr, buf);
return count;
}
// 统一的store分发函数
ssize_t mystore(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size)
{
struct kobj_attribute *kobj_attr = container_of(attr, struct kobj_attribute, attr);
return kobj_attr->store(kobj, kobj_attr, buf, size);
}
// 自定义的sysfs_ops结构体
struct sysfs_ops myops = {
.show = myshow,
.store = mystore,
};
// 自定义的kobj_type结构体
static struct kobj_type mytype = {
.release = dynamic_kobj_release,
.default_attrs = myattr,
.sysfs_ops = &myops,
};

第一种方法读写属性文件
1:单个属性文件添加
在上一个代码上做实验,api,
/*
*struct kobject kobj→ 你要给谁创建文件(目录)
*const struct attribute attr→ 你要创建的属性文件(名字、权限、show/store)
*/
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
// 定义attribute对象 value1 和 value2,后面参数为读写函数
struct kobj_attribute value1 = __ATTR(value1, 0664, show_myvalue1, store_myvalue1);
struct kobj_attribute value2 = __ATTR(value2, 0664, show_myvalue2, store_myvalue2);
static int mykobj_init(void)
{
int ret;
mykobject01 = kobject_create_and_add("mykobject01", NULL);
ret = sysfs_create_file(mykobject01, &value1.attr);
ret = sysfs_create_file(mykobject01, &value2.attr);
return ret;
}
1:多个属性文件添加
api函数
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
struct attribute_group {
const char *name;
const struct attribute **attrs;
mode_t (*is_visible)(struct kobject *kobj, struct attribute *attr, int index);
};
代码如下:
struct attribute *attrs[] = {
&value1.attr,
&value2.attr,
NULL,
};
const struct attribute_group attr_group={
.name="my_group",
.attrs=attrs,
};
sysfs_create_group(kobj,&attr_group);

总线
int mybus_match(struct device *dev,struct device_driver *drv)
{
//检查设备名称和驱动程序名称是否匹配
return(strcmp(dev_name(dev),drv->name)==0);
};
int mybus_probe(structdevice*dev)
{
struct device_driver *drv=dev->driver;
if(drv->probe)
drv->probe(dev);
return0;
}
struct bus_type mybus={
.name="mybus", //总线的名称
.match=mybus_match, //设备和驱动程序匹配的回调函数
.probe=mybus_probe, //设备探测的回调函数
};
static intbus_init(void)
{
int ret;
ret=bus_register(&mybus); //注册总线
}
这样就在/sys/bus/下面注册了一个你自己的总线,匹配规则就是match,字符串一样返回真就执行probe函数
总线下创建属性API函数
//bus_create_file() 函数用于在总线的 sysfs 目录下创建一个属性文件。
// 示例
// 正确的、驱动用的 bus_create_file —— 只有 2 个参数!
int bus_create_file(struct bus_type *bus, const struct bus_attribute *attr);
// 定义总线属性结构体
struct bus_attribute mybus_attr = {
.attr = {
.name = "value",
.mode = 0664,
},
.show = mybus_show,
};
// 创建总线 sysfs 文件
ret = bus_create_file(&mybus, &mydevice.kobj, &mybus_attr.attr);

在总线下注册设备
首先要把你的总线驱动加载内核,并且导出符号列表,这样其他驱动模块才能用到你的总线
EXPORT_SYMBOL_GPL(mybus); //新加导出总线符号
// 定义一个设备结构体
struct device mydevice = {
.init_name = "mydevice", // 设备的初始化名称
.bus = &mybus, // 该设备属于哪条总线
.release = myrelease, // 设备释放回调函数
.devt = ((255 << 20) | 0), // 设备号(主设备号 << 20 | 次设备号)
};
// 模块初始化函数
static int device_init(void)
{
int ret;
ret = device_register(&mydevice); // 注册设备到内核
return 0;
}

在自己的总线下注册驱动
// 定义驱动结构体
struct device_driver mydriver = {
.name = "mydevice", // 驱动名称(和设备名匹配用)
.bus = &mybus, // 驱动属于哪条总线
.probe = mydriver_probe, // 匹配成功后调用的探测函数
.remove = mydriver_remove, // 卸载驱动时调用
};
// 模块初始化函数
static int mydriver_init(void)
{
int ret;
ret = driver_register(&mydriver); // 注册驱动到内核
return ret; // 返回注册结果
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)