本文主要介绍设备模型之class结构体、初始化及其工作流程。
感谢您关注Linux源码阅读公众号,本公众号持续发布Linux 5.4版本相关的文章,您也可以在https://gitee.com/zgrxmm/linux_5.4_codeview下载源码和注释,以及所有文章链接。
阅读代码,知识总结不易,感谢大家点赞、关注和支持!
欢迎一起学习、总结、巩固、进阶!
如有疑问,欢迎留言或私信!
前面几篇介绍了Linux内核设备管理模型device、device_driver、bus总线、uevent机制,以及sysfs和kernfs,有了这些基础知识之后,再来认识class就比较顺其自然,因为它是这些设备和驱动的一层抽象结构体。
class是deive的高级视图,它抽象出底层的实现细节。驱动程序可能会看到SCSI磁盘或者ATA磁盘,但是从类视角来看,它们都是磁盘。class允许用户空间根据设备的功能(而不是设备的连接方式或工作方式)来使用设备。

按照惯例,理解一个模块,先了解它实现所用的数据结构体。
struct class定义在include/linux/device.h头文件中。
struct class {const char *name; // 类的名称,会在/sys/class目录下体现struct module *owner;const struct attribute_group **class_groups; // 该class默认的attributesconst struct attribute_group **dev_groups; // 该class下每个设备的attributesstruct kobject *dev_kobj; // 该class下的设备在/sys/dev/下的目录,现在由char和block两个目录// class也有uevent机制,当该class下有设备发生变化时,回调该函数int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, umode_t *mode);void (*class_release)(struct class *class); // 该class的释放函数void (*dev_release)(struct device *dev); // 该class内设备的回调函数,在devive_release过程中,会依次检查device、device_type以及device所在class的释放回调int (*shutdown_pre)(struct device *dev);const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev);void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);const struct dev_pm_ops *pm;struct subsys_private *p; // 私有指针};
struct subsys_private是struct class中的一个成员变量,定义在drivers/base/base.h。
该结构体集合了class模块所需的私有数据,是一个实际的kobject,允许安全地静态分配 struct class。
驱动程序核心之外的任何内容都不应触及这些字段。
struct subsys_private {struct kset subsys; // 定义该子系统的struct ksetstruct kset *devices_kset; // 子系统的设备目录struct list_head interfaces; // 关联的子系统接口列表struct mutex mutex; // 保护设备和接口列表的锁struct kset *drivers_kset; // 关联的驱动程序列表struct klist klist_devices; // 用于迭代driver_kset的kliststruct klist klist_drivers; // 用于迭代drivers_kset的kliststruct blocking_notifier_head bus_notifier; // 总线通知列表,列出所有关心该总线事件的通知对象unsigned int drivers_autoprobe:1;struct bus_type *bus; // 指向该结构体关联的struct bus_type的指针struct kset glue_dirs; // “glue”目录放置在父设备之间以避免命名空间冲突struct class *class; // 指向该结构体关联的struct class的指针};
struct class_attribute与struct device_attribute和struct driver_attribute功能类似,内部都是struct attribute attr,给class类attribute文件提供了读写接口。
struct class_attribute {struct attribute attr;ssize_t (*show)(struct class *class, struct class_attribute *attr,char *buf);ssize_t (*store)(struct class *class, struct class_attribute *attr,const char *buf, size_t count);};
对应的函数接口:
static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr,char *buf){struct class_attribute *class_attr = to_class_attr(attr);struct subsys_private *cp = to_subsys_private(kobj);ssize_t ret = -EIO;if (class_attr->show)ret = class_attr->show(cp->class, class_attr, buf);return ret;}static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr,const char *buf, size_t count){struct class_attribute *class_attr = to_class_attr(attr);struct subsys_private *cp = to_subsys_private(kobj);ssize_t ret = -EIO;if (class_attr->store)ret = class_attr->store(cp->class, class_attr, buf, count);return ret;}
struct class_interface结构体被串在struct class->p->interfaces上,提供了添加设备和移除设备的接口。
当有设备添加到所属class类时,调用add_dev函数。如果是先注册了设备,后创建了class,那么函数也会执行一次。
同理,当有设备从class类移除时,调用remove_dev函数。
struct class_interface {struct list_head node;struct class *class;int (*add_dev) (struct device *, struct class_interface *);void (*remove_dev) (struct device *, struct class_interface *);};
进入到/sys/class目录,可以看到有各种各样的class。

每个class目录下又有很多设备。我们以block目录为例。

可以看到block目录下的所有文件都是链接文件,且都是/sys/devices/目录中的设备的超链接。
struct class初始化是调用了kset_create_and_add函数接口,创建了一个全局的static struct kset *class_kset的kset变量,并将“class”加入到sysfs文件系统中,即完成了/sys/class目录的创建。
int __init classes_init(void){class_kset = kset_create_and_add("class", NULL, NULL);if (!class_kset)return -ENOMEM;return 0;}
class_create是个宏函数,定义在include/linux/device.h。
({ \static struct lock_class_key __key; \__class_create(owner, name, &__key); \})
它内部是调用__class_create函数创建class:调用kzalloc申请struct class结构体,并将name和class_release函数接口赋值,最后调用__class_register完成注册。
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key){struct class *cls;int retval;cls = kzalloc(sizeof(*cls), GFP_KERNEL);if (!cls) {retval = -ENOMEM;goto error;}cls->name = name;cls->owner = owner;cls->class_release = class_create_release;retval = __class_register(cls, key);if (retval)goto error;return cls;error:kfree(cls);return ERR_PTR(retval);}EXPORT_SYMBOL_GPL(__class_create);
(1)调用kzalloc申请struct subsys_private结构体变量,并初始化其内部变量,比如klist_devices、interfaces、glue_dirs、mutex、subsys.kobj,然后作为struct class的私有变量。
(2)调用kset_register注册该class,此时/sys/class/目录下便有了要注册class的目录。
(3)最后调用class_add_groups将class结构体中的class_groups指向的attribute注册到内核中。
int __class_register(struct class *cls, struct lock_class_key *key){struct subsys_private *cp;int error;pr_debug("device class '%s': registering\n", cls->name);cp = kzalloc(sizeof(*cp), GFP_KERNEL);if (!cp)return -ENOMEM;klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);INIT_LIST_HEAD(&cp->interfaces);kset_init(&cp->glue_dirs);__mutex_init(&cp->mutex, "subsys mutex", key);error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);if (error) {kfree(cp);return error;}/* set the default /sys/dev directory for devices of this class */if (!cls->dev_kobj)cls->dev_kobj = sysfs_dev_char_kobj;#if defined(CONFIG_BLOCK)/* let the block class directory show up in the root of sysfs */if (!sysfs_deprecated || cls != &block_class)cp->subsys.kobj.kset = class_kset;#elsecp->subsys.kobj.kset = class_kset;#endifcp->subsys.kobj.ktype = &class_ktype;cp->class = cls;cls->p = cp;error = kset_register(&cp->subsys);if (error) {kfree(cp);return error;}error = class_add_groups(class_get(cls), cls->class_groups);class_put(cls);return error;}EXPORT_SYMBOL_GPL(__class_register);
struct class的销毁是创建的逆过程,接口函数是class_destroy,内部实现是调用了class_unregister接口。
void class_destroy(struct class *cls){if ((cls == NULL) || (IS_ERR(cls)))return;class_unregister(cls);}void class_unregister(struct class *cls){pr_debug("device class '%s': unregistering\n", cls->name);class_remove_groups(cls, cls->class_groups);kset_unregister(&cls->p->subsys);}
向class中添加设备,其实是调用了device_add函数接口,和class相关的主要逻辑是调用device_add_class_symlinks()在dev下创建一个软链接subsystem,指向相对应的class。

struct device结构体调用device_initialize()函数完成初始化后,调用device_add()函数将其添加到设备驱动模型中:
(1) 调用get_device()来增加dev的引用计数.
(2) 然后调用device_private_init()进行dev->p的分配和初始化.
(3) 调用dev_set_name()对dev的名字进行设置.
接下来,需要将dev添加到sysfs设备文件系统中:
(4) 首先是调用get_device()增加对paren的引用计数(无论是直接挂在parent下还是通过一个类层挂在parent下都要增加parent的引用计数).
(5) 然后调用get_device_parent()找到实际要加入的父kobject,并调用kobject_add()将dev->kobj加入到dev->kobj.parent的层次结构中.
接下来是完成属性和属性集合的添加:
(6) 调用device_create_file()添加uevent属性文件.
(7) 然后调用device_add_class_symlinks()在dev下创建一个软链接subsystem,指向相对应的class.
(8) 然后继续调用device_add_attrs()添加属性和属性集合.
(9) 调用bus_add_device()添加设备的总线属性,在dev与bus之间创建软链接,并将dev挂入到总线的设备链表中去.
(10) dpm_sysfs_add()用于增加dev下的power属性集合.
(11) 调用device_pm_add()将设备添加到dpm_list链表中去。
(12) 如果设备被分配了主设备号,调用device_create_file()添加dev属性文件.
(13) 然后调用device_create_sys_dev_entry()在/sys/dev下创建相应的软链接.
(14) 调用devtmpfs_create_node()在/dev下添加对应的设备节点文件。
(15) 函数开始调用kobject_uevent()向用户空间发布KOBJ_ADD消息通知.
(16) 调用bus_probe_device()为设备探测寻找合适的驱动程序.
(17) 如果设备有父节点的话,则把dev->p->knode_parent挂入到parent->p->klist_children链表中.
(18) 如果设备有所属的class,则将dev->knode_class挂入class->p>class_devices上,并调用可能的类设备接口add_dev()函数.
(19) 对于直接在bus上的设备来讲,可以调用bus_probe_device()来查找驱动程序.
(20) 但是不与bus直接接触的设备,则靠class来去寻找驱动,便使用了class_interface内的add_dev()方式.
(21) 函数最后调用put_device()减少在开头增加的引用计数并返回。
int device_add(struct device *dev){struct device *parent;struct kobject *kobj;struct class_interface *class_intf;int error = -EINVAL;struct kobject *glue_dir = NULL;dev = get_device(dev); // 增加引用计数if (!dev)goto done;if (!dev->p) {error = device_private_init(dev); // 分配和初始化device->pif (error)goto done;}/** for statically allocated devices, which should all be converted* some day, we need to initialize the name. We prevent reading back* the name, and force the use of dev_name()*/if (dev->init_name) {dev_set_name(dev, "%s", dev->init_name); // 设置设备名称dev->init_name = NULL;}/* subsystems can specify simple device enumeration */if (!dev_name(dev) && dev->bus && dev->bus->dev_name)dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);if (!dev_name(dev)) {error = -EINVAL;goto name_error;}pr_debug("device: '%s': %s\n", dev_name(dev), __func__);parent = get_device(dev->parent); // 增加对父节点设备的引用计数kobj = get_device_parent(dev, parent);if (IS_ERR(kobj)) {error = PTR_ERR(kobj);goto parent_error;}if (kobj)dev->kobj.parent = kobj; // 设置dev->kobj父节点的kobj/* use parent numa_node */if (parent && (dev_to_node(dev) == NUMA_NO_NODE))set_dev_node(dev, dev_to_node(parent));/* first, register with generic layer. *//* we require the name to be set before, and pass NULL */error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); // 将device内嵌的kobj加入到kobject拓扑结构中if (error) {glue_dir = get_glue_dir(dev);goto Error;}/* notify platform of device entry */error = device_platform_notify(dev, KOBJ_ADD);if (error)goto platform_error;error = device_create_file(dev, &dev_attr_uevent); // 添加uevent属性文件if (error)goto attrError;error = device_add_class_symlinks(dev); // 创建dev与class软链接if (error)goto SymlinkError;error = device_add_attrs(dev); // 添加属性文件if (error)goto AttrsError;error = bus_add_device(dev); // 将设备添加到总线上,创建dev与bus之间的软连接if (error)goto BusError;error = dpm_sysfs_add(dev); // 添加dev下power的属性集合if (error)goto DPMError;device_pm_add(dev);if (MAJOR(dev->devt)) { // 主设备号存在error = device_create_file(dev, &dev_attr_dev); // 添加dev属性if (error)goto DevAttrError;error = device_create_sys_dev_entry(dev); // 在/sys/dev下添加对应的软链接if (error)goto SysEntryError;devtmpfs_create_node(dev); // 在/dev下添加相应的设备节点}/* Notify clients of device addition. This call must come* after dpm_sysfs_add() and before kobject_uevent().*/if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);kobject_uevent(&dev->kobj, KOBJ_ADD); // 将kobject发布到KOBJ_ADD消息到用户空间bus_probe_device(dev); // 为设备找到合适的驱动if (parent)klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);if (dev->class) {mutex_lock(&dev->class->p->mutex);/* tie the class to the device */klist_add_tail(&dev->p->knode_class,&dev->class->p->klist_devices); // 将节点插入到链表中/* notify any interfaces that the device is here */list_for_each_entry(class_intf,&dev->class->p->interfaces, node)if (class_intf->add_dev)class_intf->add_dev(dev, class_intf);mutex_unlock(&dev->class->p->mutex);}done:put_device(dev);return error;SysEntryError:if (MAJOR(dev->devt))device_remove_file(dev, &dev_attr_dev);DevAttrError:device_pm_remove(dev);dpm_sysfs_remove(dev);DPMError:bus_remove_device(dev);BusError:device_remove_attrs(dev);AttrsError:device_remove_class_symlinks(dev);SymlinkError:device_remove_file(dev, &dev_attr_uevent);attrError:device_platform_notify(dev, KOBJ_REMOVE);platform_error:kobject_uevent(&dev->kobj, KOBJ_REMOVE);glue_dir = get_glue_dir(dev);kobject_del(&dev->kobj);Error:cleanup_glue_dir(dev, glue_dir);parent_error:put_device(parent);name_error:kfree(dev->p);dev->p = NULL;goto done;}EXPORT_SYMBOL_GPL(device_add);
向class中添加interface是调用了class_interface_register函数接口,它首先是将class_intf添加到parent->p->interfaces链表中,如果class_intf->add_dev函数接口存在,则遍历该class下的所有设备,对device执行class_intf->add_dev。
int class_interface_register(struct class_interface *class_intf){struct class *parent;struct class_dev_iter iter;struct device *dev;if (!class_intf || !class_intf->class)return -ENODEV;parent = class_get(class_intf->class);if (!parent)return -EINVAL;mutex_lock(&parent->p->mutex);list_add_tail(&class_intf->node, &parent->p->interfaces);if (class_intf->add_dev) {class_dev_iter_init(&iter, parent, NULL, NULL);while ((dev = class_dev_iter_next(&iter)))class_intf->add_dev(dev, class_intf);class_dev_iter_exit(&iter);}mutex_unlock(&parent->p->mutex);return 0;}


