YAOTU INSIGHTS

11-linux设备模型

11-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);#includelinux/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来实现与设备模型的集成和设备模型有关的文件夹为busclassdevices。完整路径为如下所示/sys/bus /sys/class /sys/devices1/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 kobjectstruct 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结构体#includelinux/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{ .releasedynamic_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{ .namemy_group, .attrsattrs, }; 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 *drvdev-driver; if(drv-probe) drv-probe(dev); return 0; } struct bus_type mybus{ .namemybus, //总线的名称 .matchmybus_match, //设备和驱动程序匹配的回调函数 .probemybus_probe, //设备探测的回调函数 }; static intbus_init(void) { int ret; retbus_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次设备号0MKDEV是标准宏 }; // 模块初始化函数 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.ksetdevices_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分支默认正常IDdev_set_name(pdev-dev, %s.%d, pdev-name, pdev-id) → 格式name.idPLATFORM_DEVID_NONE无IDdev_set_name(pdev-dev, %s, pdev-name) → 格式仅namePLATFORM_DEVID_AUTO自动分配IDida_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; // 返回注册结果 }