Linux驱动开发—设备模型框架 kset和 kobject 详解

什么是设备模型?

设备模型(Device Model)是 Linux 内核中的一个抽象层,用于统一管理和组织系统中的各种硬件设备及其驱动程序。它为设备、驱动、总线和电源管理等提供了一个统一的接口和结构,使得内核能够更加高效和一致地管理系统中的硬件资源。

设备模型的主要组成部分

  1. 设备(Device)

    • 表示系统中的一个硬件设备,比如一个硬盘驱动器、网络适配器、USB 设备等。每个设备在内核中通常用一个 struct device 结构体来表示。设备与驱动程序、总线都有关系,通常设备会挂载在某个总线上。
  2. 驱动(Driver)

    • 表示控制设备的程序代码。每个驱动程序都与特定类型的设备关联,驱动程序通过特定的总线接口与设备进行交互。驱动程序在内核中通常用 struct device_driver 结构体来表示。
  3. 总线(Bus)

    • 设备和驱动程序之间的通信通道。总线将设备与相应的驱动程序连接起来,内核通过总线来匹配设备和驱动程序。总线在内核中用 struct bus_type 结构体来表示。
  4. 类(Class)

    • 表示一组具有相似特性的设备,这些设备可能分布在不同的总线上。类为用户空间提供了一种查看和管理设备的方式。每个类在内核中用 struct class 结构体来表示。
  5. 电源管理(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内核中,设备模型框架主要通过kobjectkset来组织和管理系统中的各种设备和子系统。这些抽象提供了一种统一的方式来表示内核对象,支持系统中设备和内核组件的层次化组织。以下是kobjectkset的详细介绍:

1. kobject

kobject 是 Linux 内核中表示一个对象的基础结构体,几乎所有的内核对象都可以用 kobject 来表示。kobject 提供了内核对象的基本属性和功能,包括:

  • 引用计数kobject 通过引用计数来管理对象的生命周期,防止对象在未释放前被删除。
  • 名字和路径:每个 kobject 都有一个唯一的名字,并可以在内核的 sysfs 文件系统中显示。
  • 关联的 ksetkobject 可以被添加到一个 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

ksetkobject 的集合,表示一组相关联的 kobjectkset 提供了一种将相关对象组织在一起的机制,这些对象通常共享相同的父对象,并具有相似的操作。

  • 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. 应用场景

  • 设备和驱动模型kobjectkset 在 Linux 设备模型中被广泛使用,用于组织设备(例如 PCI 设备、USB 设备)和驱动程序,并在 sysfs 文件系统中展示它们的关系。
  • sysfs 目录结构kobjectkset 是 sysfs 文件系统的基础,内核中的许多对象(如设备、驱动程序、子系统)都会在 sysfs 中以文件或目录的形式表示,而这些文件和目录背后就是 kobjectkset 的实现。

kobject中parent概念

kobject 结构体中的 parent 字段表示该 kobject 的父对象。这一字段的作用是帮助构建和维护内核对象的层次结构,使得不同的内核对象能够以树形结构组织起来。这种层次结构有助于理清内核对象之间的关系,并在文件系统(如 sysfs)中反映这些关系。

1. parent 字段的作用

  • 层次结构parent 字段将当前 kobject 与它的父对象连接起来,形成一个层次化的结构。通过这种结构,内核对象可以形成类似树的组织形式,其中根节点是最高层的 kobject,而每个子节点都是其父节点的一个 kobject

  • sysfs 映射:在 sysfs 文件系统中,这种层次结构会映射为目录和文件结构。例如,如果一个设备对象 kobjectparent 字段指向一个总线对象 kobject,那么在 sysfs 中该设备将会显示在相应总线目录的子目录中。

2. parent 字段的使用示例

假设内核中有如下的 kobject 层次关系:

  • kobject_root (根对象)
    • kobject_bus (总线对象)
      • kobject_device (设备对象)

在这种情况下:

  • kobject_deviceparent 字段指向 kobject_bus,表示它隶属于 kobject_bus
  • kobject_busparent 字段指向 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_childmykobj_root 的子对象。
  • 执行结果

    • mykobj_root 是根 kobject,将在 sysfs 中创建根目录 mykobj_root
    • mykobj_childmykobj_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 已经在其他地方定义。
    • 第三个参数是父 kobjectNULL 表示它是一个根对象。
    • 第四个参数是格式化字符串,用于指定 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_rootmkobj_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);

参数解析

  1. const char \*name:
    • kset 的名称,用于标识该 kset。这个名称将用于 sysfs 中对应的目录名。
  2. const struct kset_uevent_ops \*uevent_ops:
    • 这是一个指向 kset_uevent_ops 结构体的指针,该结构体包含了 kset 处理用户空间事件(如 uevent)的回调函数指针。如果不需要处理 uevent,这里可以传 NULL
  3. struct kobject \*parent:
    • kset 的父 kobject,用于指定该 kset 在 sysfs 中的层次结构。如果这个 kset 是一个顶层对象,则可以传 NULL

示例效果

在这里插入图片描述

因为创建kobject的时候,传入的参数为NULL,因此交给了kest管理,所以在myset这个目录下能找到这两个obj

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Trump. yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值