推广 热搜: 采购方式  滤芯  甲带  气动隔膜泵  减速机  减速机型号  带式称重给煤机  履带  无级变速机  链式给煤机 

Linux设备模型之class

   日期:2023-08-14 01:06:01     来源:网络整理    作者:本站编辑    评论:0    
摘要:

本文主要介绍设备模型之class结构体、初始化及其工作流程。

关键词: Linux5.4、class

感谢您关注Linux源码阅读公众号,本公众号持续发布Linux 5.4版本相关的文章,您也可以在https://gitee.com/zgrxmm/linux_5.4_codeview下载源码和注释,以及所有文章链接。

阅读代码,知识总结不易,感谢大家点赞、关注和支持!

欢迎一起学习、总结、巩固、进阶!

如有疑问,欢迎留言或私信!


0、前言

前面几篇介绍了Linux内核设备管理模型device、device_driver、bus总线、uevent机制,以及sysfs和kernfs,有了这些基础知识之后,再来认识class就比较顺其自然,因为它是这些设备和驱动的一层抽象结构体。

A class is a higher-level view of a device that abstracts out low-level implementation details. Drivers may see a SCSI disk or an ATA disk, but,  at the class level, they are all simply disks. Classes allow user space to work with devices based on what they do, rather than how they are  connected or how they work.

class是deive的高级视图,它抽象出底层的实现细节。驱动程序可能会看到SCSI磁盘或者ATA磁盘,但是从类视角来看,它们都是磁盘。class允许用户空间根据设备的功能(而不是设备的连接方式或工作方式)来使用设备。


1、相关结构体

按照惯例,理解一个模块,先了解它实现所用的数据结构体。

1.1 struct class

struct class定义在include/linux/device.h头文件中。

struct class {    const char        *name;  // 类的名称,会在/sys/class目录下体现    struct module        *owner;
const struct attribute_group **class_groups; // 该class默认的attributes const struct attribute_group **dev_groups; // 该class下每个设备的attributes struct 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; // 私有指针};

1.2 struct subsys_private

struct subsys_private是struct class中的一个成员变量,定义在drivers/base/base.h。

该结构体集合了class模块所需的私有数据,是一个实际的kobject,允许安全地静态分配 struct class。

驱动程序核心之外的任何内容都不应触及这些字段。

struct subsys_private {    struct kset subsys;           // 定义该子系统的struct kset    struct kset *devices_kset;    // 子系统的设备目录    struct list_head interfaces;  // 关联的子系统接口列表    struct mutex mutex;           // 保护设备和接口列表的锁
struct kset *drivers_kset; // 关联的驱动程序列表 struct klist klist_devices; // 用于迭代driver_kset的klist struct klist klist_drivers; // 用于迭代drivers_kset的klist struct 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的指针};

1.3 struct class_attribute

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

对应的函数接口:

#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)
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;}

1.4 struct class_interface

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

2、struct class在用户空间的展示

进入到/sys/class目录,可以看到有各种各样的class。

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

可以看到block目录下的所有文件都是链接文件,且都是/sys/devices/目录中的设备的超链接。

3、struct class相关函数接口

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

3.1 struct class的创建class_create

class_create是个宏函数,定义在include/linux/device.h。

#define class_create(owner, name)        \({                        \    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);
    
    __class_register的主要逻辑包括:

(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;#else cp->subsys.kobj.kset = class_kset;#endif cp->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);

3.2 struct class的销毁class_destroy

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);}
3.3 向class中添加设备

向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->p if (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);

3.4 向class中添加interface

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


 
打赏
 
更多>同类资讯
0相关评论

推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  皖ICP备20008326号-18
Powered By DESTOON