• Linux 内核:设备驱动模型(3)class与device


    Linux 内核:设备驱动模型(3)class与device

    背景

    前面我们知道了设备如何通过总线与驱动匹配,也了解了设备插拔时与用户空间是如何通过uevent基于环境变量进行交互的。

    前面看过了设备驱动模型中的bus、device、driver,这3种对象都很好理解,drv与dev通过bus找到对方:

    • bus:总线
    • device:设备和接口
    • driver:驱动

    现在,我们继续来说一些比较抽象的概念吧。

    实际上,设备驱动模型中,还有一个 抽象概念 叫做类(CLass),准确来说,叫做设备类

    所谓设备类,是指提供的用户接口相似的一类设备的集合,常见的设备类的有block、tty、input、usb等等。

    举个例子,一些年龄相仿、需要获取的知识相似的人,聚在一起学习,就构成了一个班级(Class)。这个班级可以有自己的名称(如295),但如果离开构成它的学生(device),它就没有任何存在意义。另外,班级存在的最大意义是什么呢?是由老师讲授的每一个课程!因为老师只需要讲一遍,一个班的学生都可以听到。不然的话(例如每个学生都在家学习),就要为每人请一个老师,讲授一遍。而讲的内容,大多是一样的,这就是极大的浪费。

    设备模型中的Class所提供的功能也一样了,例如一些相似的device(学生),需要向用户空间提供相似的接口(课程),如果每个设备的驱动都实现一遍的话,就会导致内核有大量的冗余代码,这就是极大的浪费。

    系列:Linux 内核:设备驱动模型 学习总结

    参考:

    设备类

    设备类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。

    设备类是用来抽象设备的共性而诞生的。

    类成员通常由上层代码所控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。

    设备类的作用

    让我们先看一下现有Linux系统中有关class的状况(这里以input class为例):

    root@android:/ # ls /sys/class/input/ -l
    lrwxrwxrwx root     root              2014-04-23 03:39 event0 -> ../../devices/platform/i2c-gpio.17/i2c-17/17-0066/max77693-muic/input/input0/event0
    lrwxrwxrwx root     root              2014-04-23 03:39 event1 -> ../../devices/platform/gpio-keys.0/input/input1/event1
    ...
    ...
    lrwxrwxrwx root     root              2014-04-23 03:39 mice -> ../../devices/virtual/input/mice
    
    root@android:/ # ls /sys/devices/platform/s3c2440-i2c.3/i2c-3/3-0048/input/input2/event2/ -l
    -r--r--r-- root     root         4096 2014-04-23 04:08 dev
    lrwxrwxrwx root     root              2014-04-23 04:08 device -> ../../input2
    drwxr-xr-x root     root              2014-04-23 04:08 power
    lrwxrwxrwx root     root              2014-04-23 04:08 subsystem -> ../../../../../../../../class/input
    -rw-r--r-- root     root         4096 2014-04-23 04:08 uevent
    
    
    root@android:/ # ls /sys/devices/virtual/input/mice/ -l                                      
    -r--r--r-- root     root         4096 2014-04-23 03:57 dev
    drwxr-xr-x root     root              2014-04-23 03:57 power
    lrwxrwxrwx root     root              2014-04-23 03:57 subsystem -> ../../../../class/input
    

    看上面的例子,发现input class也没做什么实实在在的事儿,它(input class)的功能,仅仅是:

    1、在/sys/class/目录下,创建一个本class的目录(input)

    2、在本目录下,创建每一个属于该class的设备的符号链接,这样就可以在本class目录下,访问该设备的所有特性(即attribute)

    如,把“/sys/devices/platform/s3c2440-i2c.3/i2c-3/3-0048/input/input2/event2”设备链接到”/sys/class/input/event2”。

    3、device在sysfs的目录下,也会创建一个subsystem的符号链接,链接到本class的目录

    算了,我们还是先分析一下Class的核心逻辑都做了哪些事情,至于class到底有什么用处,可以在后续具体的子系统里面(如input子系统),更为细致的探讨。

    class原型

    其实struct classstruct bus很类似

    // include/linux/device.h
    /**
     * struct class - device classes
     * @name:   Name of the class.
     * @owner:  The module owner.
     * @class_attrs: Default attributes of this class.
     * @dev_groups: Default attributes of the devices that belong to the class.
     * @dev_kobj:   The kobject that represents this class and links it into the hierarchy.
     * @dev_uevent: Called when a device is added, removed from this class, or a
     *      few other things that generate uevents to add the environment
     *      variables.
     * @devnode:    Callback to provide the devtmpfs.
     * @class_release: Called to release this class.
     * @dev_release: Called to release the device.
     * @suspend:    Used to put the device to sleep mode, usually to a low power
     *      state.
     * @resume: Used to bring the device from the sleep mode.
     * @ns_type:    Callbacks so sysfs can detemine namespaces.
     * @namespace:  Namespace of the device belongs to this class.
     * @pm:     The default device power management operations of this class.
     * @p:      The private data of the driver core, no one other than the
     *      driver core can touch this.
     *
     * 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.
     */
    struct class {
        /* 名称 */
        const char      *name;
        // owner是class所属的模块,虽然class是涉及一类设备,但也是由相应的模块注册的。
        // 比如usb类就是由usb模块注册的。
        struct module       *owner;
    
        /* 属性 */
        struct class_attribute      *class_attrs;
        const struct attribute_group    **dev_groups;
        /* 内部对象 */
        struct kobject          *dev_kobj;
    
        // 设备发出uevent消息时添加环境变量用的
        // 还记得在core.c中的dev_uevent()函数,其中就包含对设备所属bus或class中dev_uevent()方法的调用,
        // 只是bus结构中定义方法用的函数名是uevent。
        int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
        // 返回设备节点的相对路径名,在core.c的device_get_devnode()中有调用到。
        char *(*devnode)(struct device *dev, umode_t *mode);
    
        /* 释放方法 */
        void (*class_release)(struct class *class);
        void (*dev_release)(struct device *dev);
    
        /*电源管理有关,略*/
        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);
    
        /* 命名空间,略*/
        const struct kobj_ns_type_operations *ns_type;
        const void *(*namespace)(struct device *dev);
    
        /* 电源管理用的函数集合 */
        const struct dev_pm_ops *pm;
    
        /* 私有数据 */
        struct subsys_private *p;
    };
    

    大部分的成员通过注释就可以搞懂了,但是有几个需要进行特别说明。

    名称name

    name:设备类的名称,会在“/sys/class/”目录下体现。

    实际使用的是内部kobj包含的动态创建的名称。

    内部对象kobject

    dev_kobj: 在device注册时,会在/sys/dev下创建名为自己设备号的软链接。

    但设备不知道自己属于块设备还是字符设备,所以会请示自己所属的class;class就是用dev_kobj记录本类设备应属于的哪种设备。

    属性attribute

    class_attrs:该class的默认attribute,会在class注册到内核时,自动在“/sys/class/xxx_class”下创建对应的attribute文件。

    class_attrs:该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件。

    释放方法release

    class_release:用于回收这个设备类本身自身的回调函数。

    dev_release:用于release class内设备的回调函数。在device_release接口中,会依次检查Device、Device Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针。

    具体在drivers/base/core.cdevice_release()函数中调用。

    私有数据subsys_private

    // drivers/base/base.h
    /**
     * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
     *
     * @subsys - the struct kset that defines this subsystem
     * @devices_kset - the list of devices associated
     *
     * @drivers_kset - the list of drivers associated
     * @klist_devices - the klist to iterate over the @devices_kset
     * @klist_drivers - the klist to iterate over the @drivers_kset
     * @bus_notifier - the bus notifier list for anything that cares about things
     *                 on this bus.
     * @bus - pointer back to the struct bus_type that this structure is associated
     *        with.
     *
     * @class_interfaces - list of class_interfaces associated
     * @glue_dirs - "glue" directory to put in-between the parent device to
     *              avoid namespace conflicts
     * @mutex - mutex to protect the children, devices, and interfaces lists.
     * @class - pointer back to the struct class that this structure is associated
     *          with.
     *
     * This structure is the one that is the actual kobject allowing struct
     * bus_type/class to be statically allocated safely.  Nothing outside of the
     * driver core should ever touch these fields.
     */
    struct subsys_private {
        struct kset subsys; //代表 class 在sysfs中的类型
        struct kset *devices_kset; //代表 class目录下的drivers子目录
        // ...
    
        //代表 class目录下的 devices 子目录
        struct kset *drivers_kset;
        // class下的设备链表
        struct klist klist_devices;
        // bus的驱动链表
        struct klist klist_drivers;
        用于在xx发送变化时调用特定的函数
        struct blocking_notifier_head bus_notifier;
        
        unsigned int drivers_autoprobe:1;
    
        struct bus_type *bus;
    
        // 设备类接口稍后会介绍
        struct list_head class_interfaces;
        // 胶水目录
        struct kset glue_dirs;
        // 互斥信号量,用于保护class内部的数据结构。
        struct mutex mutex;
        // 指回struct class的指针
        struct class *class;
    };
    

    glue_dirs:

    它并未实际在sysfs中体现,反而是其下链接了一系列胶水kobject。

    记得在core.c中的get_device_parent()函数,好像小蝌蚪找妈妈一样,我们在为新注册的设备寻找sysfs中可以存放的位置。

    如果发现dev->class存在,而dev->parent->class不存在,就要建立一个胶水目录,在sysfs中隔离这两个实际上有父子关系的设备。

    linux这么做也是为了在sysfs显示时更清晰一些。但如果父设备下有多个属于同一类的设备,它们需要放在同一胶水目录下。

    怎么寻找这个胶水目录有没有建立过,就要从这里的glue_dirs下的kobject中找了。

    设备类的交界面机制

    我不喜欢翻译这个 interface接口,因为class_interface实际上做的是在class driverclass下有设备添加或移除的时候,调用预先设置好的回调函数(add_devremove_dev)。

    相当于一个类的设备进出类的表面所触发的获取信息的机制。

    class_interface原型

    // include/linux/device.h
    struct class_interface {
        // 
        struct list_head    node;
        // 指向所属class的指针
        struct class        *class;
    
        // 有设备添加到所属class时调用的函数
        int (*add_dev)      (struct device *, struct class_interface *);
        // 设备删除时调用。
        void (*remove_dev)  (struct device *, struct class_interface *);
    };
    

    关于add_dev()还有一点要说明,如果class_interface比设备更晚添加到class,也会在添加到class的时候执行。

    那调用add_dev/remove_dev做什么呢?想做什么都行(例如修改设备的名称),由具体的class driver实现。

    设备类的sysfs属性

    class_attribute原型

    bus_attribute,到driver_attribute,到device_attribute,当然也少不了这里的class_attribute

    // include/linux/device.h
    struct class_attribute {
    	struct attribute attr;
    	ssize_t (*show)(struct class *class, char *buf);
    	ssize_t (*store)(struct class *class, const char *buf, size_t count);
    };
    
    #define CLASS_ATTR(_name, _mode, _show, _store)			
        struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)
    

    struct attribute封装这种东西,既简单又耐用,何乐而不为?不过呢,等到讲到sysfs的时候,我们再来细讲这个。

    device注册时和class有关的动作

    我们有讲过struct device和struct device_driver这两个数据结构,其中struct device结构会包含一个struct class指针(这从侧面说明了class是device的集合,甚至,class可以是device的driver)。当某个class driver向内核注册了一个class后,需要使用该class的device,通过把自身的class指针指向该class即可,剩下的事情,就由内核在注册device时处理了。

    再看device_add

    device的注册最终是由device_add接口()实现了,该接口中和class有关的动作包括:

    // drviers/base/core.c
    /**
     * device_add - add device to device hierarchy.
     * @dev: device.
     *
     * This is part 2 of device_register(), though may be called
     * separately _iff_ device_initialize() has been called separately.
     *
     * This adds @dev to the kobject hierarchy via kobject_add(), adds it
     * to the global and sibling lists for the device, then
     * adds it to the other relevant subsystems of the driver model.
     *
     * Do not call this routine or device_register() more than once for
     * any device structure.  The driver model core is not designed to work
     * with devices that get unregistered and then spring back to life.
     * (Among other things, it's very hard to guarantee that all references
     * to the previous incarnation of @dev have been dropped.)  Allocate
     * and register a fresh new struct device instead.
     *
     * NOTE: _Never_ directly free @dev after calling this function, even
     * if it returned an error! Always use put_device() to give up your
     * reference instead.
     */
    int device_add(struct device *dev)
    {
        struct device *parent = NULL;
        struct kobject *kobj;
        struct class_interface *class_intf;
        int error = -EINVAL;
    
        // ...
    
        // 创建类符号链接,相互创建dev和class之间的链接文件
        error = device_add_class_symlinks(dev);
        // 创建sys目录下设备其他属性文件
        error = device_add_attrs(dev);
    
        // ...
    
        // 如果 设备属于某个类,添加到类的设备列表中。
        // 如果这个类的interface 指定了 添加类设备 的方法,则执行
        if (dev->class) {
            mutex_lock(&dev->class->p->mutex);
            /* tie the class to the device */
            klist_add_tail(&dev->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;
    
    }
    EXPORT_SYMBOL_GPL(device_add);
    
    // drivers/base/core.c
    static int device_add_class_symlinks(struct device *dev)
    {
        int error;
    
        if (!dev->class)
            return 0;
    
        error = sysfs_create_link(&dev->kobj,
                      &dev->class->p->subsys.kobj,
                      "subsystem");
    
        if (dev->parent && device_is_not_partition(dev)) {
            error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
                          "device");
            if (error)
                goto out_subsys;
        }
    
    #ifdef CONFIG_BLOCK
        /* /sys/block has directories and does not need symlinks */
        if (sysfs_deprecated && dev->class == &block_class)
            return 0;
    #endif
    
        /* link in the class directory pointing to the device */
        error = sysfs_create_link(&dev->class->p->subsys.kobj,
                      &dev->kobj, dev_name(dev));
    
    
        return 0;
    }
    

    class.c解析

    class_sysfs_ops

    #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;
    }
     
    static const struct sysfs_ops class_sysfs_ops = {
        .show      = class_attr_show,
        .store     = class_attr_store,
    };
    

    class_sysfs_ops就是class定义的sysfs读写函数集合。

    class_ktype

    static void class_release(struct kobject *kobj)
    {
        struct subsys_private *cp = to_subsys_private(kobj);
        struct class *class = cp->class;
    
        pr_debug("class '%s': release.
    ", class->name);
    
        if (class->class_release)
            class->class_release(class);
        else
            pr_debug("class '%s' does not have a release() function, "
                 "be careful
    ", class->name);
    
        kfree(cp);
    }
     
    static struct kobj_type class_ktype = {
        .sysfs_ops  = &class_sysfs_ops,
        .release    = class_release,
        .child_ns_type  = class_child_ns_type,
    };
    

    class_release()是在class引用计数降为零时调用的释放函数。因为class在结构中提供了class_release的函数指针,所以可以由具体的class调用相应的处理方法。

    class_ktype是为class对应的kobject(也可以说kset)定义的kobj_type。

    class_kset与初始化

    /* Hotplug events for classes go to the class subsys */
    static struct kset *class_kset;
     
    int __init classes_init(void)
    {
        class_kset = kset_create_and_add("class", NULL, NULL);
        if (!class_kset)
            return -ENOMEM;
        return 0;
    }
    
    ////////////////////////////////////////////////////////////
    // drivers/base/init.c
    void __init driver_init(void)
    {
        /* These are the core pieces */
        devtmpfs_init();
        devices_init();
        buses_init();
        classes_init();  // 初始化 class
        firmware_init();
        hypervisor_init();
    
        /* These are also core pieces, but must come after the
         * core core pieces.
         */
        platform_bus_init();
        cpu_dev_init();
        memory_dev_init();
        container_dev_init();
    }
    

    class_kset代表了/sys/class对应的kset,在classes_init()中创建。

    classes_init()的作用,和之前见到的buses_init()devices_init()作用相似,都是构建/sys下的主要目录结构。

    创建/删除class的属性

    // include/linux/device.h
    static inline int __must_check class_create_file(struct class *class,
                        const struct class_attribute *attr)
    {
        return class_create_file_ns(class, attr, NULL);
    }
    
    static inline void class_remove_file(struct class *class,
                         const struct class_attribute *attr)
    {
        return class_remove_file_ns(class, attr, NULL);
    }
    
    ////////////////////////////////////////////////////////////
    int class_create_file_ns(struct class *cls, const struct class_attribute *attr,
                 const void *ns)
    {
        int error;
        if (cls)
            error = sysfs_create_file_ns(&cls->p->subsys.kobj,
                             &attr->attr, ns);
        else
            error = -EINVAL;
        return error;
    }
    
    void class_remove_file_ns(struct class *cls, const struct class_attribute *attr,
                  const void *ns)
    {
        if (cls)
            sysfs_remove_file_ns(&cls->p->subsys.kobj, &attr->attr, ns);
    }
    

    class_create_file():创建class的属性文件。

    class_remove_files():删除class的属性文件。

    对kobj的封装

    static struct class *class_get(struct class *cls)
    {
        if (cls)
            kset_get(&cls->p->subsys);
        return cls;
    }
    
    static void class_put(struct class *cls)
    {
        if (cls)
            kset_put(&cls->p->subsys);
    }
    

    class_get()增加对cls的引用计数;class_put()减少对cls的引用计数,并在计数降为零时调用相应的释放函数,也就是之前见过的class_release函数。

    class的引用计数是由subsys_private结构中的kset来管的,kset又是由其内部kobject来管的,kobject又是调用其结构中的kref来管的。这是一种嵌套的封装技术。

    static int add_class_attrs(struct class *cls)
    {
        int i;
        int error = 0;
    
        if (cls->class_attrs) {
            for (i = 0; cls->class_attrs[i].attr.name; i++) {
                error = class_create_file(cls, &cls->class_attrs[i]);
                if (error)
                    goto error;
            }
        }
    done:
        return error;
    error:
        while (--i >= 0)
            class_remove_file(cls, &cls->class_attrs[i]);
        goto done;
    }
    
    static void remove_class_attrs(struct class *cls)
    {
        int i;
    
        if (cls->class_attrs) {
            for (i = 0; cls->class_attrs[i].attr.name; i++)
                class_remove_file(cls, &cls->class_attrs[i]);
        }
    }
    

    add_class_attrs()把cls->class_attrs中的属性加入sysfs。

    remove_class_attrs()把cls->class_attrs中的属性删除。

    到了class这个级别,就和bus一样,除了自己,没有其它结构能为自己添加属性。

    节点对应设备的引用操作

    static void klist_class_dev_get(struct klist_node *n)
    {
        struct device *dev = container_of(n, struct device, knode_class);
    
        get_device(dev);
    }
    
    static void klist_class_dev_put(struct klist_node *n)
    {
        struct device *dev = container_of(n, struct device, knode_class);
    
        put_device(dev);
    }
    

    klist_class_dev_get()增加节点对应设备的引用计数,klist_class_dev_put()减少节点对应设备的引用计数。

    这是class的设备链表,在节点添加和删除时调用的。相似的klist链表,还有驱动的设备链表,不过由于linux对驱动不太信任,所以没有让驱动占用设备的引用计数。还有总线的设备链表,在添加释放节点时分别调用klist_devices_get()和list_devices_put(),是在bus.c中定义的。还有设备的子设备链表,在添加释放节点时分别调用klist_children_get()和klist_children_put(),是在device.c中定义的。看来klist中的get()/put()函数,是在初始化klist时设定的,也由创建方负责实现。

    注册class

    // include/linux/device.h
    /* This is a #define to keep the compiler from merging different
     * instances of the __key variable */
    #define class_register(class)           
    ({                      
        static struct lock_class_key __key; 
        __class_register(class, &__key);    
    })
    
     ////////////////////////////////////////////////////////////
    int __class_register(struct class *cls, struct lock_class_key *key)
    {
        struct subsys_private *cp;
        int error;
    
        pr_debug("device class '%s': registering
    ", 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 = add_class_attrs(class_get(cls));
        class_put(cls);
        return error;
    }
    EXPORT_SYMBOL_GPL(__class_register);
    

    class_register()将class注册到系统中。

    之所以把class_register()写成宏定义的形式,似乎是为了__key的不同实例合并,在__class_register()中确实使用了__key,但是是为了调试class中使用的mutex用的。__

    key的类型lock_class_key是只有使用LOCKDEP定义时才会有内容,写成这样也许是为了在lock_class_key定义为空时减少一些不必要的空间消耗。总之这类trick的做法,是不会影响我们理解代码逻辑的。

    __class_register()中进行实际的class注册工作:

    先是分配和初始化class_private结构。

    可以看到对cp->glue_dirs,只是调用kset_init()定义,并未实际注册到sysfs中。

    调用kobject_set_name()创建kobj中实际的类名。

    cls->dev_kobj如果未设置,这里会被设为sysfs_dev_char_kobj。
    调用kset_register()将class注册到sysfs中,所属kset为class_kset,使用类型为class_ktype。因为没有设置parent,会以/sys/class为父目录。

    最后调用add_class_attrs()添加相关的属性文件。

    在bus、device、driver、class中,最简单的注册过程就是class的注册,因为它不仅和bus一样属于一种顶层结构,而且连通用的属性文件都不需要,所有的操作就围绕在class_private的创建初始化与添加到sysfs上面。

    void class_unregister(struct class *cls)
    {
        pr_debug("device class '%s': unregistering
    ", cls->name);
        remove_class_attrs(cls);
        kset_unregister(&cls->p->subsys);
    }
    

    class_unregister()取消class的注册。它的操作也简单到了极点。

    只是这里的class注销也太懒了些。无论是class_unregister(),还是在计数完全释放时调用的class_release(),都找不到释放class_private结构的地方。这是bug吗?

    我怀着敬畏的心情,查看了linux-3.0.4中的drivers/base/class.c,发现其中的class_release()函数最后添加了释放class_private结构的代码。看来linux-2.6.32还是有较为明显的缺陷。奈何木已成舟,只能先把这个bug在现有代码里改正,至少以后自己编译内核时不会再这个问题上出错。

    不过说起来,像bus_unregister()、class_unregister()这种函数,估计只有在关机时才可能调用得到,实在是无关紧要。

    // include/linux/device.h
    /* This is a #define to keep the compiler from merging different
     * instances of the __key variable */
    #define class_create(owner, name)		
    ({						
    	static struct lock_class_key __key;	
    	__class_create(owner, name, &__key);	
    })
    ////////////////////////////////////////////////////////////
    
    /**
     * class_create - create a struct class structure
     * @owner: pointer to the module that is to "own" this struct class
     * @name: pointer to a string for the name of this class.
     * @key: the lock_class_key for this class; used by mutex lock debugging
     *
     * This is used to create a struct class pointer that can then be used
     * in calls to device_create().
     *
     * Returns &struct class pointer on success, or ERR_PTR() on error.
     *
     * Note, the pointer created here is to be destroyed when finished by
     * making a call to class_destroy().
     */
    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_create()是提供给外界快速创建class的API。应该说,class中可以提供的一系列函数,这里都没有提供,或许可以在创建后再加上。

    相似的函数是在core.c中的device_create(),那是提供一种快速创建device的API。

    static void class_create_release(struct class *cls)
    {
    	pr_debug("%s called for %s
    ", __func__, cls->name);
    	kfree(cls);
    }
     
    /**
     * class_destroy - destroys a struct class structure
     * @cls: pointer to the struct class that is to be destroyed
     *
     * Note, the pointer to be destroyed must have been created with a call
     * to class_create().
     */
    void class_destroy(struct class *cls)
    {
    	if ((cls == NULL) || (IS_ERR(cls)))
    		return;
     
    	class_unregister(cls);
    }
    

    class_destroy()是与class_create()相对的删除class的函数。

    虽然在class_destroy()中没有看到释放class内存的代码,但这是在class_create_release()中做的。class_create_release()之前已经在class_create()中被作为class结构中定义的class_release()函数,会在class引用计数降为零时被调用。

    在class中,class结构和class_private结构都是在class引用计数降为零时才释放的。这保证了即使class已经被注销,仍然不会影响其下设备的正常使用。但在bus中,bus_private结构是在bus_unregister()中就被释放的。没有了bus_private,bus下面的device和driver想必都无法正常工作了吧。这或许和bus对应与实际总线有关。总线都没了,下面的设备自然没人用了。

    遍历

    class为了遍历设备链表,特意定义了专门的结构和遍历函数,实现如下。

    struct class_dev_iter {
    	struct klist_iter		ki;
    	const struct device_type	*type;
    };
     
    /**
     * class_dev_iter_init - initialize class device iterator
     * @iter: class iterator to initialize
     * @class: the class we wanna iterate over
     * @start: the device to start iterating from, if any
     * @type: device_type of the devices to iterate over, NULL for all
     *
     * Initialize class iterator @iter such that it iterates over devices
     * of @class.  If @start is set, the list iteration will start there,
     * otherwise if it is NULL, the iteration starts at the beginning of
     * the list.
     */
    void class_dev_iter_init(struct class_dev_iter *iter, struct class *class,
    			 struct device *start, const struct device_type *type)
    {
    	struct klist_node *start_knode = NULL;
     
    	if (start)
    		start_knode = &start->knode_class;
    	klist_iter_init_node(&class->p->class_devices, &iter->ki, start_knode);
    	iter->type = type;
    }
     
    struct device *class_dev_iter_next(struct class_dev_iter *iter)
    {
    	struct klist_node *knode;
    	struct device *dev;
     
    	while (1) {
    		knode = klist_next(&iter->ki);
    		if (!knode)
    			return NULL;
    		dev = container_of(knode, struct device, knode_class);
    		if (!iter->type || iter->type == dev->type)
    			return dev;
    	}
    }
     
    void class_dev_iter_exit(struct class_dev_iter *iter)
    {
    	klist_iter_exit(&iter->ki);
    }
    

    之所以要如此费一番周折,在klist_iter外面加上这一层封装,完全是为了对链表进行选择性遍历。选择的条件就是device_type。device_type是在device结构中使用的类型,其中定义了相似设备使用的一些处理操作,可以说比class的划分还要小一层。class对设备链表如此遍历,也是用心良苦啊。

    int class_for_each_device(struct class *class, struct device *start,
    			  void *data, int (*fn)(struct device *, void *))
    {
    	struct class_dev_iter iter;
    	struct device *dev;
    	int error = 0;
     
    	if (!class)
    		return -EINVAL;
    	if (!class->p) {
    		WARN(1, "%s called for class '%s' before it was initialized",
    		     __func__, class->name);
    		return -EINVAL;
    	}
     
    	class_dev_iter_init(&iter, class, start, NULL);
    	while ((dev = class_dev_iter_next(&iter))) {
    		error = fn(dev, data);
    		if (error)
    			break;
    	}
    	class_dev_iter_exit(&iter);
     
    	return error;
    }
    struct device *class_find_device(struct class *class, struct device *start,
    				 void *data,
    				 int (*match)(struct device *, void *))
    {
    	struct class_dev_iter iter;
    	struct device *dev;
     
    	if (!class)
    		return NULL;
    	if (!class->p) {
    		WARN(1, "%s called for class '%s' before it was initialized",
    		     __func__, class->name);
    		return NULL;
    	}
     
    	class_dev_iter_init(&iter, class, start, NULL);
    	while ((dev = class_dev_iter_next(&iter))) {
    		if (match(dev, data)) {
    			get_device(dev);
    			break;
    		}
    	}
    	class_dev_iter_exit(&iter);
     
    	return dev;
    }
    
    
    

    class_for_each_device()是对class的设备链表上的每个设备调用指定的函数。

    class_find_device()查找class设备链表上的某个设备,使用指定的匹配函数。

    注册class_interface

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

    class_interface_register()把class_interface添加到指定的class上。

    调用class_get()获取class的引用计数。

    使用class->mutex进行保护。

    将classs_intf添加到class的接口列表中。

    对已经添加到class上的设备补上add_dev()操作。

    这里使用的class->mutex是用来保护class的类接口链表。对于简单的list_head来说,这种mutex保护是应该的。但对于武装到牙齿的klist来说,就完全不必要了,因为klist内置了spinlock来完成互斥的操作。所以之前其它的klist链表操作都没有mutex保护。

    比较spinlock和mutex的话,spinlock操作要比mutex快很多,因为对mutex的操作本身就需要spinlock来保护。但mutex的好处是它可以阻塞。使用spinlock时间太长的话,一是浪费cpu时间,二是禁止了任务抢占。klist是使用spinlock来保护的,这适合大部分情况,但在klist遍历时也可能调用一些未知的操作,它们可能很耗时,甚至可能阻塞,这时最好能使用mutex加以替换。

    void class_interface_unregister(struct class_interface *class_intf)
    {
        struct class *parent = class_intf->class;
        struct class_dev_iter iter;
        struct device *dev;
    
        if (!parent)
            return;
    
        mutex_lock(&parent->p->mutex);
        list_del_init(&class_intf->node);
        if (class_intf->remove_dev) {
            class_dev_iter_init(&iter, parent, NULL, NULL);
            while ((dev = class_dev_iter_next(&iter)))
                class_intf->remove_dev(dev, class_intf);
            class_dev_iter_exit(&iter);
        }
        mutex_unlock(&parent->p->mutex);
    
        class_put(parent);
    }
    

    class_interface_register()把class_interface添加到指定的class上。

    调用class_get()获取class的引用计数。

    使用class->mutex进行保护。

    将classs_intf添加到class的接口列表中。

    对已经添加到class上的设备补上add_dev()操作。

    这里使用的class->mutex是用来保护class的类接口链表。对于简单的list_head来说,这种mutex保护是应该的。但对于武装到牙齿的klist来说,就完全不必要了,因为klist内置了spinlock来完成互斥的操作。所以之前其它的klist链表操作都没有mutex保护。

    比较spinlock和mutex的话,spinlock操作要比mutex快很多,因为对mutex的操作本身就需要spinlock来保护。但mutex的好处是它可以阻塞。使用spinlock时间太长的话,一是浪费cpu时间,二是禁止了任务抢占。klist是使用spinlock来保护的,这适合大部分情况,但在klist遍历时也可能调用一些未知的操作,它们可能很耗时,甚至可能阻塞,这时最好能使用mutex加以替换。

    class_interface_unregister()从class中去除指定的class_interface。对于这些class_interface来说,自己注销和设备注销效果是一样的,都会调用相应的remove_dev()。

    class_compat

    struct class_compat {
        struct kobject *kobj;
    };
     
    /**
     * class_compat_register - register a compatibility class
     * @name: the name of the class
     *
     * Compatibility class are meant as a temporary user-space compatibility
     * workaround when converting a family of class devices to a bus devices.
     */
    struct class_compat *class_compat_register(const char *name)
    {
        struct class_compat *cls;
    
        cls = kmalloc(sizeof(struct class_compat), GFP_KERNEL);
        if (!cls)
            return NULL;
        cls->kobj = kobject_create_and_add(name, &class_kset->kobj);
        if (!cls->kobj) {
            kfree(cls);
            return NULL;
        }
        return cls;
    }
    EXPORT_SYMBOL_GPL(class_compat_register);
     
    /**
     * class_compat_unregister - unregister a compatibility class
     * @cls: the class to unregister
     */
    void class_compat_unregister(struct class_compat *cls)
    {
        kobject_put(cls->kobj);
        kfree(cls);
    }
    EXPORT_SYMBOL_GPL(class_compat_unregister);
    

    在/sys/class下面,除了class类型的,还有表现起来和class相同的class_compat类型。

    其实class_compat就是单单为了显示一个目录,不会定义对应的属性或者函数。

    /**
     * class_compat_create_link - create a compatibility class device link to
     *			      a bus device
     * @cls: the compatibility class
     * @dev: the target bus device
     * @device_link: an optional device to which a "device" link should be created
     */
    int class_compat_create_link(struct class_compat *cls, struct device *dev,
    			     struct device *device_link)
    {
    	int error;
     
    	error = sysfs_create_link(cls->kobj, &dev->kobj, dev_name(dev));
    	if (error)
    		return error;
     
    	/*
    	 * Optionally add a "device" link (typically to the parent), as a
    	 * class device would have one and we want to provide as much
    	 * backwards compatibility as possible.
    	 */
    	if (device_link) {
    		error = sysfs_create_link(&dev->kobj, &device_link->kobj,
    					  "device");
    		if (error)
    			sysfs_remove_link(cls->kobj, dev_name(dev));
    	}
     
    	return error;
    }
     
    void class_compat_remove_link(struct class_compat *cls, struct device *dev,
    			      struct device *device_link)
    {
    	if (device_link)
    		sysfs_remove_link(&dev->kobj, "device");
    	sysfs_remove_link(cls->kobj, dev_name(dev));
    }
    

    class_compat_create_link()的目的是在class_compat目录下建立类似于class目录下的,对设备的软链接。这个不是在标准的设备注册时调用的。

    总结

    本节我们分析完了设备驱动模型中的class,对设备驱动模型的分析也告一段落。现在,我相信大家对于设备驱动模型的框架也有了一个基础的认识。

    若是要加深对它的认识,就要在此基础上不断充实细节,用具体的设备驱动来理解。

    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    哈希表,开放地址法之线性探测代码(JAVA)
    Java中几个关键字的意思
    java学习——File类操作
    Map三种遍历方法
    Spring Data Binding
    哈希表的基础理解
    选择排序
    map集合使用
    排序
    关于LinkedList
  • 原文地址:https://www.cnblogs.com/schips/p/linux_device_model_3.html
Copyright © 2020-2023  润新知