概念

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;
    mykobject01 = kobject_create_and_add("mykobject01", NULL);
    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>

struct kobject *mykobject01;
struct kobject *mykobject02;
struct kset *mykset;
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 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    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);
}

设备模型简介

设备模型通过提供统一的设备描述和管理机制,简化了 驱动的编写和维护过程,提高了代码的复用性和可维护性,设备模型包含以下四个概念:

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/devices

/sys/devices:该目录主要展示设备的层次架构

2:/sys/bus

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

3:/sys/class:

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

4:sys 目录层次图解

引用计数器(open,close哪种差不多引用)

引用计数器(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的时候自动执行Mytype里面的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,
};

第一种方法创建Kobject下面创建属性文件

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;
}

2:多个属性文件添加

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);

总线

实现一个自己的总线

struct bus_type {
    const char      *name;         // 1. 总线名字(如 platform、i2c、spi
    // ========== 【三个最核心!你天天用的!】==========
    int (*match)(struct device *dev, struct device_driver *drv); 
                                    // 3. 匹配函数:设备 <-> 驱动 配对
    int (*probe)(struct device *dev); 
                                    // 4. 总线兜底probe(几乎不用)
    int (*remove)(struct device *dev);
};

代码如下:

int mybus_match(struct device *dev,struct device_driver *drv)
{
    //检查设备名称和驱动程序名称是否匹配
    return(strcmp(dev_name(dev),drv->name)==0);
};

int mybus_probe(struct device*dev)
{
    struct device_driver *drv=dev->driver;
    if(drv->probe)
    drv->probe(dev);
    return 0;
}

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

字符串一样返回1代表匹配成功,就执行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);

bus_register()函数理论分析:
bus_register()函数理论分析
一、函数基本概述
bus_register()是Linux内核设备模型中总线注册的核心入口函数,作用是将自定义struct bus_type总线结构体完整注册到Linux内核设备模型子系统中,完成总线初始化、内核对象注册、链表初始化、sysfs目录创建、设备驱动管理框架搭建等全部工作。
总线注册完成后,该总线才可以挂载设备、挂载驱动、执行设备驱动匹配逻辑。

二、总线对应核心结构体struct bus_type
struct bus_type {
    const char      *name;         //总线名称,对应sysfs目录名
    int             (*match)(struct device *dev, struct device_driver *drv); //设备驱动匹配函数
    int             (*probe)(struct device *dev); //总线兜底probe初始化函数
    void            *p;            //总线私有数据指针,内核自动分配
};
总线私有数据struct subsys_private内部包含:总线kobject内核对象、设备链表klist_devices、驱动链表klist_drivers、总线互斥锁、sysfs属性相关数据。

三、bus_register()内部完整执行流程&函数调用顺序
1. 内核分配总线私有数据struct subsys_private内存空间
2. 初始化总线内部两个klist双向链表:
   klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
   设备链表绑定get/put引用计数函数,支持节点增删改查遍历操作
   klist_init(&priv->klist_drivers, NULL, NULL);
   驱动链表不绑定get/put引用计数函数,无引用保护,内核禁止增删改查遍历,仅做占位记录
3. 初始化总线并发访问互斥锁,防止设备驱动注册并发竞态问题
4. 初始化总线对应的kobject内核对象,设置总线名称,绑定父节点到/sys/bus目录
5. 调用subsystem_register()函数注册总线子系统内核对象
6. 在sysfs文件系统/sys/bus/目录下创建对应总线名称的总线根目录
7. 在总线根目录下自动创建devices、drivers两个子目录
8. 调用bus_create_file()函数创建总线默认sysfs属性文件(uevent等)
9. 将总线私有数据priv赋值给bus->p,完成总线和私有数据、链表、kobject的绑定关联
10. 返回注册结果:成功返回0,失败返回负数错误码

四、内部关键子函数调用关系
bus_register()
├─ alloc_ordered_workqueue()        //创建总线事件工作队列
├─ kobject_init()                   //初始化总线kobject内核对象
├─ klist_init()                    //初始化设备链表、驱动链表
├─ subsystem_register()             //注册总线子系统,创建sysfs总线目录
├─ bus_create_file()               //创建总线sysfs属性文件
└─ kset_register()                  //注册kset,管理总线子节点

五、链表引用计数核心知识点
1. klist_devices设备链表:传入get/put引用计数函数
取出节点自动增加设备引用计数,用完节点自动减少引用计数,保证操作时设备不会被销毁,支持安全增删改查遍历
2. klist_drivers驱动链表:传入NULL、NULL无引用计数函数
无法对驱动节点做引用保护,遍历操作会出现野指针崩溃风险,内核直接禁止一切增删改查操作,仅可静态记录驱动列表

六、总线注册完成后的作用
1. 内核拥有了该总线的完整管理框架
2. 总线可以接收device_register()注册的设备挂载
3. 总线可以接收driver_register()注册的驱动挂载
4. 总线match函数可以自动完成设备和驱动的匹配
5. 匹配成功后执行驱动probe初始化硬件
6. sysfs生成完整总线目录结构,用户态可查看总线、设备、驱动信息

七、总结
bus_register()是Linux总线驱动的基础入口函数,完整完成总线内核对象初始化、管理链表搭建、sysfs目录创建、设备驱动管理框架构建,是设备模型总线-设备-驱动三层结构的基础。
platform总线注册流程实例分析
platform总线注册流程实例分析

一、概述
platform总线是Linux虚拟总线,管理无物理总线的设备(LED、按键、LCD等)。
由内核启动自动调用platform_bus_init()完成注册。

二、核心注册函数
int __init platform_bus_init(void)
{
    int err;
    err = bus_register(&platform_bus_type);
    platform_devpool_init();
    return err;
}

三、核心结构体platform_bus_type
struct bus_type platform_bus_type = {
    .name      = "platform",
    .match     = platform_match,
    .probe     = platform_bus_probe,
    .remove    = platform_bus_remove,
    .shutdown  = platform_bus_shutdown,
    .pm        = &platform_dev_pm_ops,
};

四、注册流程步骤
1. 内核启动调用platform_bus_init()
2. 调用bus_register(&platform_bus_type)注册总线
3. 分配总线私有数据,包含kobject、设备链表、驱动链表
4. 初始化klist_devices(带引用计数,可操作)
5. 初始化klist_drivers(无引用计数,不可操作)
6. 初始化总线互斥锁
7. 初始化总线kobject,创建sysfs目录
8. 创建/sys/bus/platform/、devices、drivers目录
9. 绑定platform_match匹配函数
10. 执行platform_devpool_init()注册设备号
11. 注册完成,返回0

五、关键函数调用
platform_bus_init() → bus_register() → 链表初始化 → kobject初始化 → sysfs创建

六、注册成功效果
1. 生成/sys/bus/platform目录结构
2. 可注册platform_device和platform_driver
3. 自动匹配设备与驱动
4. 匹配成功执行驱动probe

七、总结
platform总线是虚拟总线,内核启动自动注册。
注册完成链表、kobject、sysfs、匹配函数,是平台设备驱动模型的基础。

在总线下注册设备

因为两个实验分开写的,所以你要导出上个实验的你自定义的总线类型

EXPORT_SYMBOL_GPL(mybus); //新加导出总线符号
extern struct bus_type mybus;
// 定义一个设备释放函数(必须实现,否则内核崩溃)
void myrelease(struct device *dev)
{
    printk("设备释放\n");
}
// 定义一个设备结构体
struct device mydevice = {
    .init_name = "mydevice",    // 1. 设备名称(会出现在/sys/devices/xxx)
    .bus = &mybus,              // 2. 设备挂载到哪条总线上
    .release = myrelease,       // 3. 设备释放回调函数(必须实现!)
    .devt = MKDEV(255, 100),      // 4. 设备号:主设备号255,次设备号0(MKDEV是标准宏)
};
// 模块初始化函数
static int device_init(void)
{
    int ret;
    // 注册设备到内核设备模型
    ret = device_register(&mydevice);
    // 注册失败必须返回错误码(你原来直接return 0是错的)
    if (ret) {
        printk("设备注册失败\n");
        return ret;
    }
    return 0;
}

device_register(struct device* dev)函数
int device_register(struct device* dev)
{
     device_initialize(dev);
     return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);
//数据结构初始化,属性文件初始化,添加软链接
void device_initialize(struct device* dev)
{
    dev->kobj.kset=devices_kset; //关联全局设备集合
    kobject_init(&dev->kobj,&device_ktype);//初始化内核对象,绑定设备释放
    ...
}

初始化链表,把设备加入到链表

platform_device_register(struct platform_device* pdev)函数
int platform_device_register(struct platform_device* pdev)
{
    device_initialize(&pdev->dev);
   // arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}

步骤1:检查设备基本信息 + 设置默认父设备

核心代码逻辑:


if (!pdev) return -EINVAL; // 检查pdev指针是否为空(合法性校验) if (!pdev->dev.parent) pdev->dev.parent = &platform_bus; // 无父设备则默认指向platform_bus

关键作用:确保设备指针合法;统一所有平台设备的父设备,为sysfs节点统一路径(/sys/devices/platform)打下基础。

步骤2:设置设备总线类型

核心代码逻辑:


pdev->dev.bus = &platform_bus_type; // 将设备挂载到platform总线上

关键作用:告知内核该设备是“平台总线设备”,后续将走platform总线的驱动匹配逻辑。

步骤3:设置设备名称(3种格式)

核心代码逻辑(switch分支):

  • 默认(正常ID):dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id) → 格式:name.id

  • PLATFORM_DEVID_NONE(无ID):dev_set_name(&pdev->dev, "%s", pdev->name) → 格式:仅name

  • PLATFORM_DEVID_AUTO(自动分配ID):ida_simple_get获取ID,格式:name.id.auto

关键作用:保证设备名唯一,避免sysfs节点冲突,便于内核识别和管理设备。

步骤4:添加设备资源(内存/IO等)

核心代码逻辑:


for (i = 0; i < pdev->num_resources; i++) { struct resource *r = &pdev->resource[i]; if (!r->name) r->name = dev_name(&pdev->dev); // 资源无名则用设备名 // 按资源类型(内存/IO)设置默认父资源,插入资源树避免冲突 if (resource_type(r) == IORESOURCE_MEM) p = &iomem_resource; if (resource_type(r) == IORESOURCE_IO) p = &ioport_resource; if (p && insert_resource(p, r)) return -EBUSY; }

关键作用:登记设备的硬件资源(如内存地址、IO端口),避免资源冲突,为后续驱动操作硬件提供依据。

步骤5:注册到内核设备模型 + 触发驱动匹配

核心代码逻辑:


ret = device_add(&pdev->dev); // 将设备加入内核设备模型 if (ret == 0) return ret;

关键补充:device_add() 内部会调用 bus_probe_device(),触发platform总线的驱动匹配逻辑,匹配成功则执行驱动的probe函数。

核心总结

流程闭环:检查合法性 → 设父设备 → 挂平台总线 → 设唯一设备名 → 登记资源 → 内核注册+驱动匹配,完成平台设备的完整注册。

为什么注册platform总线之前先注册设备
platform_bus_init()
{
    // 第一步:注册 platform_bus 设备 → 先创建 /sys/devices/platform
    device_register(&platform_bus);
    
    // 第二步:注册 platform_bus_type 总线 → 让总线可以管理设备
    bus_register(&platform_bus_type);
}

应为总线上的设备必须要有一个父设备来管理这些设备,所以先创建一个父设备,

就是说,当通过platform_device_register()注册设备时,其dev.parent 会默认设置为 &platform_bus。platform_bus 设备注册后,会在 sysfs 中创建/sys/devices/platform 目录,所有 平台设备的sysfs节点都会在/sys/devices/platform 下创建子目录,所以在platform_bus_init()中 先注册设备后注册总线的顺序,目的是先构建platform顶层目录

在自己的总线下注册驱动

// 定义驱动结构体
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;                      // 返回注册结果
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐