文章目录
- 什么是设备模型?
- 设备模型的主要组成部分
- 设备模型的关键功能
- 设备模型的实现结构
- 设备模型的重要性
- kset和 kobject介绍
- 1. kobject
- 2. kset
- 3. kobject 和 kset 的关系
- 4. 应用场景
- kobject中parent概念
- 1. parent 字段的作用
- 2. parent 字段的使用示例
- 3. sysfs 中的反映
- 4. 实际场景中的应用
- 创建kobject 实验
- 示例代码
- `kobject_create_and_add`函数解析
- `kobject_init_and_add`解析
- 示例效果
- 创建kset 实验
- 示例代码
- 参数解析
- 示例效果
什么是设备模型?
设备模型(Device Model)是 Linux 内核中的一个抽象层,用于统一管理和组织系统中的各种硬件设备及其驱动程序。它为设备、驱动、总线和电源管理等提供了一个统一的接口和结构,使得内核能够更加高效和一致地管理系统中的硬件资源。
设备模型的主要组成部分
-
设备(Device)
- 表示系统中的一个硬件设备,比如一个硬盘驱动器、网络适配器、USB 设备等。每个设备在内核中通常用一个
struct device
结构体来表示。设备与驱动程序、总线都有关系,通常设备会挂载在某个总线上。
- 表示系统中的一个硬件设备,比如一个硬盘驱动器、网络适配器、USB 设备等。每个设备在内核中通常用一个
-
驱动(Driver)
- 表示控制设备的程序代码。每个驱动程序都与特定类型的设备关联,驱动程序通过特定的总线接口与设备进行交互。驱动程序在内核中通常用
struct device_driver
结构体来表示。
- 表示控制设备的程序代码。每个驱动程序都与特定类型的设备关联,驱动程序通过特定的总线接口与设备进行交互。驱动程序在内核中通常用
-
总线(Bus)
- 设备和驱动程序之间的通信通道。总线将设备与相应的驱动程序连接起来,内核通过总线来匹配设备和驱动程序。总线在内核中用
struct bus_type
结构体来表示。
- 设备和驱动程序之间的通信通道。总线将设备与相应的驱动程序连接起来,内核通过总线来匹配设备和驱动程序。总线在内核中用
-
类(Class)
- 表示一组具有相似特性的设备,这些设备可能分布在不同的总线上。类为用户空间提供了一种查看和管理设备的方式。每个类在内核中用
struct class
结构体来表示。
- 表示一组具有相似特性的设备,这些设备可能分布在不同的总线上。类为用户空间提供了一种查看和管理设备的方式。每个类在内核中用
-
电源管理(Power Management)
- 设备模型还负责管理设备的电源状态,包括休眠、唤醒等功能,以实现系统的节能和性能优化。
设备模型的关键功能
-
设备与驱动的自动匹配:
- 内核设备模型负责将设备和驱动程序匹配起来,即找到合适的驱动程序并将其绑定到设备上。这是通过总线、设备和驱动程序之间的关系来实现的。
-
设备的层次结构管理:
- 设备模型允许将设备组织成层次结构。例如,一个 PCI 总线可以包含多个设备,这些设备又可以有自己的子设备。内核通过设备模型来管理这些设备的父子关系。
-
sysfs 文件系统:
- 设备模型与 sysfs 紧密结合,所有的设备、驱动、总线等信息都可以通过 sysfs 文件系统导出到用户空间。sysfs 是用户查看和管理系统硬件的关键接口。
-
统一的电源管理:
- 设备模型提供了统一的电源管理接口,使得内核可以在系统进入不同电源状态(如挂起、休眠)时,对所有设备进行相应的处理。
设备模型的实现结构
在 Linux 内核中,设备模型主要通过以下几种核心结构体来实现:
struct device
:表示具体的设备。struct device_driver
:表示设备驱动程序。struct bus_type
:表示设备总线类型。struct class
:表示设备的类别。struct kobject
:基础对象,用于实现对象的层次结构管理。
设备模型的重要性
设备模型使得 Linux 内核能够以一种模块化和可扩展的方式来管理硬件设备。它提供了抽象接口,使得内核和驱动程序开发者可以更方便地实现设备的管理、控制和交互,而不必关注底层的硬件细节。设备模型的引入极大地简化了 Linux 内核中的设备管理逻辑,并提高了系统的可维护性和扩展性。
kset和 kobject介绍
在Linux内核中,设备模型框架主要通过kobject
和kset
来组织和管理系统中的各种设备和子系统。这些抽象提供了一种统一的方式来表示内核对象,支持系统中设备和内核组件的层次化组织。以下是kobject
和kset
的详细介绍:
1. kobject
kobject
是 Linux 内核中表示一个对象的基础结构体,几乎所有的内核对象都可以用 kobject
来表示。kobject
提供了内核对象的基本属性和功能,包括:
- 引用计数:
kobject
通过引用计数来管理对象的生命周期,防止对象在未释放前被删除。 - 名字和路径:每个
kobject
都有一个唯一的名字,并可以在内核的 sysfs 文件系统中显示。 - 关联的 kset:
kobject
可以被添加到一个kset
中,这样就能将kobject
组织成一个集合。 - 回调函数:
kobject
允许在其被释放时执行特定的回调函数。
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
2. kset
kset
是 kobject
的集合,表示一组相关联的 kobject
。kset
提供了一种将相关对象组织在一起的机制,这些对象通常共享相同的父对象,并具有相似的操作。
- kset 的结构:
kset
本质上是一个包含多个kobject
的容器,并且还可以定义一些与这些对象相关的操作。 - 管理机制:
kset
管理kobject
的创建和销毁,还能够在集合中添加或移除kobject
。
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
3. kobject 和 kset 的关系
kobject
通常被添加到一个 kset
中以便更好地组织管理。kset
本身也是一个 kobject
,因此 kset
可以嵌套,也就是说一个 kset
可以包含其他 kset
。通过这种方式,内核能够建立起设备、驱动和子系统的层次结构。
4. 应用场景
- 设备和驱动模型:
kobject
和kset
在 Linux 设备模型中被广泛使用,用于组织设备(例如 PCI 设备、USB 设备)和驱动程序,并在 sysfs 文件系统中展示它们的关系。 - sysfs 目录结构:
kobject
和kset
是 sysfs 文件系统的基础,内核中的许多对象(如设备、驱动程序、子系统)都会在 sysfs 中以文件或目录的形式表示,而这些文件和目录背后就是kobject
和kset
的实现。
kobject中parent概念
kobject
结构体中的 parent
字段表示该 kobject
的父对象。这一字段的作用是帮助构建和维护内核对象的层次结构,使得不同的内核对象能够以树形结构组织起来。这种层次结构有助于理清内核对象之间的关系,并在文件系统(如 sysfs)中反映这些关系。
1. parent 字段的作用
-
层次结构:
parent
字段将当前kobject
与它的父对象连接起来,形成一个层次化的结构。通过这种结构,内核对象可以形成类似树的组织形式,其中根节点是最高层的kobject
,而每个子节点都是其父节点的一个kobject
。 -
sysfs 映射:在 sysfs 文件系统中,这种层次结构会映射为目录和文件结构。例如,如果一个设备对象
kobject
的parent
字段指向一个总线对象kobject
,那么在 sysfs 中该设备将会显示在相应总线目录的子目录中。
2. parent 字段的使用示例
假设内核中有如下的 kobject
层次关系:
kobject_root
(根对象)kobject_bus
(总线对象)kobject_device
(设备对象)
在这种情况下:
kobject_device
的parent
字段指向kobject_bus
,表示它隶属于kobject_bus
。kobject_bus
的parent
字段指向kobject_root
,表示它隶属于根对象。
struct kobject kobject_root;
struct kobject kobject_bus;
struct kobject kobject_device;
kobject_device.parent = &kobject_bus;
kobject_bus.parent = &kobject_root;
3. sysfs 中的反映
假设内核对象按照上述关系组织,sysfs 文件系统中会出现以下目录结构:
/sys/kobject_root/kobject_bus/kobject_device/
kobject_root
目录表示根kobject
。kobject_bus
目录是kobject_root
下的子目录,表示总线对象。kobject_device
目录是kobject_bus
下的子目录,表示设备对象。
4. 实际场景中的应用
在实际的 Linux 内核开发中,parent
字段的应用场景包括但不限于:
- 设备树:设备和子设备的层次结构可以通过
parent
字段来表示。例如,某个总线上挂载的设备可以通过parent
字段将它们组织在一起。 - 驱动模型:驱动程序中的设备对象通常会通过
parent
字段链接到其父对象,比如总线对象或设备类对象。 - 模块组织:内核模块的内部对象可以通过
parent
字段建立相互之间的层次关系。
创建kobject 实验
示例代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
struct kobject *mykobj_root;
struct kobject *mykobj_child;
struct kobject *mykobj_other_root;
struct kobj_type *myktype;
static int __init mykobj_init(void)
{
int ret = 0;
// 1.创建kobject第一种方法
mykobj_root = kobject_create_and_add("mykobj_root",NULL);
mykobj_child = kobject_create_and_add("mykobj_child",mykobj_root);
//第二种方法
mykobj_other_root = kzalloc(sizeof(struct kobject),GFP_KERNEL);
ret = kobject_init_and_add(mykobj_other_root,myktype,NULL,"%s","mykobj_other_root");
return ret ;
}
static void __exit pmykobj_exit(void)
{
kobject_put(mykobj_child);
kobject_put(mykobj_root);
kobject_put(mykobj_other_root);
}
module_init(mykobj_init); // 注意这里的分号
module_exit(pmykobj_exit); // 注意这里的分号
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("a simple of make kobject");
kobject_create_and_add
函数解析
mykobj_root = kobject_create_and_add("mykobj_root", NULL);
mykobj_child = kobject_create_and_add("mykobj_child", mykobj_root);
-
kobject_create_and_add
函数:这是一个便利函数,用于创建并初始化一个kobject
,然后将其添加到内核对象的层次结构中。 -
参数解释:
- 第一个参数
"mykobj_root"
和"mykobj_child"
是kobject
的名字。这些名字将在kobject
被创建时赋值,并用于在 sysfs 文件系统中表示相应的对象。 - 第二个参数用于指定新创建的
kobject
的父对象:- 对于
mykobj_root
,父对象是NULL
,表示这个kobject
是根对象,不从属于任何其他kobject
。 - 对于
mykobj_child
,父对象是mykobj_root
,表示mykobj_child
是mykobj_root
的子对象。
- 对于
- 第一个参数
-
执行结果:
mykobj_root
是根kobject
,将在 sysfs 中创建根目录mykobj_root
。mykobj_child
是mykobj_root
的子对象,将在 sysfs 中作为子目录出现,即路径为/sys/mykobj_root/mykobj_child
。
kobject_init_and_add
解析
mykobj_other_root = kzalloc(sizeof(struct kobject), GFP_KERNEL);
ret = kobject_init_and_add(mykobj_other_root, myktype, NULL, "%s", "mykobj_other_root");
-
手动分配内存:首先,通过
kzalloc
函数分配了kobject
所需的内存空间,并将内存初始化为零。mykobj_other_root = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kzalloc
:分配内存并将其清零。sizeof(struct kobject)
确保分配的内存大小足够存放一个kobject
结构体。GFP_KERNEL
:表示这是在内核空间中分配内存,并且允许在分配过程中进行阻塞(通常在内核模块中使用)。
-
kobject_init_and_add
函数:这个函数手动初始化一个kobject
,并将其添加到内核对象的层次结构中。ret = kobject_init_and_add(mykobj_other_root, myktype, NULL, "%s", "mykobj_other_root");
- 第一个参数是已经分配好的
kobject
内存 (mykobj_other_root
)。 - 第二个参数
myktype
是一个指向kobj_type
结构体的指针,定义了kobject
的行为,包括它在 sysfs 中的属性和操作。这里假设myktype
已经在其他地方定义。 - 第三个参数是父
kobject
。NULL
表示它是一个根对象。 - 第四个参数是格式化字符串,用于指定
kobject
的名字。在这里,"%s"
会被替换为"mykobj_other_root"
,从而为kobject
指定名称。
- 第一个参数是已经分配好的
-
返回值:
kobject_init_and_add
返回一个整数ret
。如果返回值为 0,表示操作成功;如果返回负数,则表示出错,通常是由于内存分配失败或无效参数等原因。
-
执行结果:
mykobj_other_root
是另一个根kobject
,将出现在 sysfs 文件系统中,路径为/sys/mykobj_other_root
。
-
第一种方法:使用
kobject_create_and_add
简化了kobject
的创建、初始化和添加过程,非常适合快速创建kobject
并自动将其挂载到内核层次结构中。 -
第二种方法:使用
kobject_init_and_add
提供了更细粒度的控制,适合在需要手动管理内存或自定义kobject
类型 (kobj_type
) 的场景下使用。
两种方法都能创建 kobject
并将其添加到内核对象层次中,不过第一种方法更简便,而第二种方法则更灵活。
示例效果
在sys目录下创建了mkobj_root
与 mkobj_other_root
创建kset 实验
示例代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
struct kobject *mykobj_root;
struct kobject *mykobj_child;
struct kobject *mykobj_other_root;
struct kset *mykset;
struct kobj_type *mytype;
static int __init mykobj_init(void)
{
int ret = 0;
mykset = kset_create_and_add("myset",NULL,NULL);
//申请内存
mykobj_other_root = kzalloc(sizeof(struct kobject),GFP_KERNEL);
mykobj_other_root->kset = mykset;
ret = kobject_init_and_add(mykobj_other_root,mytype,NULL,"%s","mykobj_other_root");
mykobj_root = kzalloc(sizeof(struct kobject),GFP_KERNEL);
mykobj_root->kset = mykset;
ret = kobject_init_and_add(mykobj_root,mytype,NULL,"%s","mykobj_root");
return 0;
}
static void __exit pmykobj_exit(void)
{
printk("bye bye ref -1\n");
kobject_put(mykobj_root);
kobject_put(mykobj_other_root);
kset_unregister(mykset);
}
module_init(mykobj_init);
module_exit(pmykobj_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("a simple example of making kset");
kset_create_and_add
是 Linux 内核中用于创建并添加一个 kset
的函数。它的原型如下:
c复制代码struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent);
参数解析
const char \*name
:kset
的名称,用于标识该kset
。这个名称将用于 sysfs 中对应的目录名。
const struct kset_uevent_ops \*uevent_ops
:- 这是一个指向
kset_uevent_ops
结构体的指针,该结构体包含了kset
处理用户空间事件(如 uevent)的回调函数指针。如果不需要处理 uevent,这里可以传NULL
。
- 这是一个指向
struct kobject \*parent
:- 该
kset
的父kobject
,用于指定该kset
在 sysfs 中的层次结构。如果这个kset
是一个顶层对象,则可以传NULL
。
- 该
示例效果
因为创建kobject的时候,传入的参数为NULL,因此交给了kest管理,所以在myset这个目录下能找到这两个obj