设备驱动中的class(kernel-4.7)

本文详细介绍了Linux内核4.7版本中设备驱动模型中的设备类(class)概念,包括class在设备驱动模型中的地位、结构、作用以及sysfs中的体现。class是一个抽象的概念,用于归类提供相似用户接口的设备,如block、tty、input等。文章还探讨了class的结构、引用计数管理、设备链表操作以及注册和注销过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设备驱动中bus代表实际的总线,device代表实际的设备和接口,而device_driver则对应存在的驱动。而class,是设备类,完全是抽象出来的概念,没有对应的实体。所谓设备类,是指提供的用户接口相似的一类设备的集合,常见的设备类的有block、tty、input、usb等等。

struct class就是设备驱动模型中通用的设备类结构。
class对应的代码在drivers/base/class.c中,对应的头文件在include/Linux/device.h和drivers/base/base.h中。还是先来看class涉及的结构:


/**
 * 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;//代表类名称,和bus/device/driver中的名称一样,是初始名称,实际使用的是内部kobj包含的动态创建的名称。
    struct module       *owner;//class所属的模块,虽然class是涉及一类设备,但也是由相应的模块注册的

    struct class_attribute      *class_attrs;//class给自己添加的属性,dev_groupsclass给所包含的设备添加的属性
    const struct attribute_group    **dev_groups;
    struct kobject          *dev_kobj; //一个kobject指针

    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); //在设备发出uevent消息时添加环境变量用的
    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; //指向subsys_private结构的指针
};

p是指向struct subsys_private 的指针,老的内核版本中是class_private表示,即class的私有数据:


/**
 * 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 subsystem's 'devices' directory
 * @interfaces - list of subsystem interfaces associated
 * @mutex - protect the devices, and interfaces lists.
 *
 * @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.
 *
 * @glue_dirs - "glue" directory to put in-between the parent device to
 *              avoid namespace conflicts
 * @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;//kset类型,用来表示class在sysfs中的位置
    struct kset *devices_kset; //指向device的kset指针
    struct list_head interfaces; //是list_head类型的类接口链表
    struct mutex mutex;

    struct kset *drivers_kset;
    struct klist klist_devices;  //klist设备链
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;

    struct kset glue_dirs;
    struct class *class;
};

#define to_subsys_private(obj) container_of(obj, struct subsys_private, subsys.kobj)

struct class_interface是之前被串在class->p->interface上的类接口的结构。用于描述设备类对外的一种接口。

struct class_interface {
    struct list_head    node; //链表上的节点
    struct class        *class; //指向所属class的指针

    int (*add_dev)      (struct device *, struct class_interface *);//在有设备添加到所属class时调用的函数
    void (*remove_dev)  (struct device *, struct class_interface *);//在设备删除时调用的
};

bus_attribute,到driver_attribute,到device_attribute,当然也少不了这里的class_attribute。使用struct 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);
};

继续查看drivers/base/class.c中的class的作用和实现,可以以自上而下的顺序结构查看:


#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读写函数集合。


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.\n", class->name);

    if (class->class_release)
        class->class_release(class);
    else
        pr_debug("class '%s' does not have a release() function, "
             "be careful\n", class->name);

    kfree(cp);
}

static const struct kobj_ns_type_operations *class_child_ns_type(struct kobject *kobj)
{
    struct subsys_private *cp = to_subsys_private(kobj);
    struct class *class = cp->class;

    return class->ns_type;
}

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_child_ns_type 是kernel新版本中增加的队kobject命名空间中子类型操作。

/* Hotplug events for classes go to the class subsys */
static struct kset *class_kset;

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

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_create_file_ns()在kobject命名空间创建class的属性文件。
class_remove_file_ns() 在kobject命名空间删除class的属性文件。
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时设定的,也由创建方负责实现。


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); //分配和初始化subsys_private结构
    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);

    //调用kobject_set_name()创建kobj中实际的类名
    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); //将class注册到sysfs中
    if (error) {
        kfree(cp);
        return error;
    }
    error = add_class_attrs(class_get(cls));//添加相关的属性文件
    class_put(cls);
    return error;
}

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

__class_register()将class注册到系统中。
先是分配和初始化subsys_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一样属于一种顶层结构,而且连通用的属性文件都不需要,所有的操作就围绕在subsys_private的创建初始化与添加到sysfs上面。
class_unregister()取消class的注册。


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

/**
 * 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_create()是提供给外界快速创建class的API。应该说,class中可以提供的一系列函数,这里都没有提供,或许可以在创建后再加上。
相似的函数是在core.c中的device_create(),那是提供一种快速创建device的API。
class_destroy()是与__class_create()相对的删除class的函数。

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


/**
 * 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->klist_devices, &iter->ki, start_knode);
    iter->type = type;
}

/**
 * class_dev_iter_next - iterate to the next device
 * @iter: class iterator to proceed
 *
 * Proceed @iter to the next device and return it.  Returns NULL if
 * iteration is complete.
 *
 * The returned device is referenced and won't be released till
 * iterator is proceed to the next device or exited.  The caller is
 * free to do whatever it wants to do with the device including
 * calling back into class code.
 */
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;
    }
}

/**
 * class_dev_iter_exit - finish iteration
 * @iter: class iterator to finish
 *
 * Finish an iteration.  Always call this function after iteration is
 * complete whether the iteration ran till the end or not.
 */
void class_dev_iter_exit(struct class_dev_iter *iter)
{
    klist_iter_exit(&iter->ki);
}

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


/**
 * class_for_each_device - device iterator
 * @class: the class we're iterating
 * @start: the device to start with in the list, if any.
 * @data: data for the callback
 * @fn: function to be called for each device
 *
 * Iterate over @class's list of devices, and call @fn for each,
 * passing it @data.  If @start is set, the list iteration will start
 * there, otherwise if it is NULL, the iteration starts at the
 * beginning of the list.
 *
 * We check the return of @fn each time. If it returns anything
 * other than 0, we break out and return that value.
 *
 * @fn is allowed to do anything including calling back into class
 * code.  There's no locking restriction.
 */
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;
} 

/**
 * class_find_device - device iterator for locating a particular device
 * @class: the class we're iterating
 * @start: Device to begin with
 * @data: data for the match function
 * @match: function to check device
 *
 * This is similar to the class_for_each_dev() function above, but it
 * returns a reference to a device that is 'found' for later use, as
 * determined by the @match callback.
 *
 * The callback should return 0 if the device doesn't match and non-zero
 * if it does.  If the callback returns non-zero, this function will
 * return to the caller and not iterate over any more devices.
 *
 * Note, you will need to drop the reference with put_device() after use.
 *
 * @match is allowed to do anything including calling back into class
 * code.  There's no locking restriction.
 */
struct device *class_find_device(struct class *class, struct device *start,
                 const void *data,
                 int (*match)(struct device *, const 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设备链表上的某个设备,使用指定的匹配函数。


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);//获取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;
}

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->class_mutex进行保护。将classs_intf添加到class的接口列表中。对已经添加到class上的设备补上add_dev()操作。这里使用的class->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()


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

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

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

/**
 * class_compat_remove_link - remove 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 was previously
 *       created
 */
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));
}

在/sys/class下面,除了class类型的,还有表现起来和class相同的class_compat类型。其实class_compat就是单单为了显示一个目录,不会定义对应的属性或者函数。
class_compat_create_link()的目的是在class_compat目录下建立类似于class目录下的,对设备的软链接。这个不是在标准的设备注册时调用的。

最后就是class_init()函数:


int __init classes_init(void)
{
    class_kset = kset_create_and_add("class", NULL, NULL);
    if (!class_kset)
        return -ENOMEM;
    return 0;
}

其中class_kset代表了/sys/class对应的kset,在classes_init()中创建。
classes_init()的作用,和buses_init()devices_init()作用相似,都是构建/sys下的主要目录结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值