• Linux 内核:设备驱动模型(1)sysfs与kobject基类


    Linux 内核:设备驱动模型(1)sysfs与kobject基类

    背景

    学习Linux 设备驱动模型时,对 kobject 不太理解。因此,学习了一下。

    现在我知道了:kobj/kset是如何作为统一设备模型的基础,以及到底提供了哪些功能。

    以后我们就知道,在具体应用过程中,如device、bus甚至platform_device等是如何使用kobj/kset的。

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

    参考:

    内核版本:3.14

    kobject, kset和ktype

    要分析sysfs,首先就要分析kobject和kset,因为驱动设备的层次结构的构成就是由这两个东东来完成的。

    sysfs与kobject密不可分。

    kobject

    kobject是组成设备模型的基本结构,是所有用来描述设备模型的数据结构的基类。

    kobject是一个对象的抽象,它用于管理对象。

    kobject被用来控制访问一个更大的,具有特定作用域的对象;为了达到这个作用,kobject将会被嵌入到其他结构体中。

    如果你用面向对象的角度来看,kobject结构可以被看做顶层的抽象类,其他类都是从这个类派生出来的。

    在sysfs中,每个kobject对应着一个目录。

    // include/linux/kobject.h
    struct kobject {
         /* 对应sysfs的目录名 */
        const char      *name;
        /*用于连接到所属kset的链表中,用于将kobj挂在kset->list中*/
        struct list_head    entry;
        /*指向 父对象,形成层次结构,在sysfs中表现为父子目录的关系*/
        struct kobject      *parent;
        /*
            属于哪个kset
            表征该kobj所属的kset。
            kset可以作为parent的“候补”:当注册时,传入的parent为空时,可以让kset来担当。
        */
        struct kset     *kset;
         /*类型属性,每个kobj或其嵌入的结构对象应该都对应一个kobj_type。 */
        struct kobj_type    *ktype;
        /*sysfs中与该对象对应的文件节点对象*/
        // 在3.14以后的内核中,sysfs基于kernfs来实现。 
        struct kernfs_node  *sd;
        /*对象的应用计数*/
        struct kref     kref;
    
        /* 记录初始化与否。调用kobject_init()后,会置位。 */
        unsigned int state_initialized:1;
        /* 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。 */
        unsigned int state_in_sysfs:1;
        /* 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。*/
        unsigned int state_add_uevent_sent:1;
        /* 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。*/
        unsigned int state_remove_uevent_sent:1;
        /* 如果该字段为1,则表示忽略所有上报的uevent事件。 */
        unsigned int uevent_suppress:1;
    };
    
    //include/linux/kref.h
    struct kref {
        atomic_t refcount;
    };
    

    目前为止,Kobject主要提供如下功能:

    1. 构成层次结构:通过parent指针,可以将所有Kobject以层次结构的形式组合起来。
    2. 生命周期管理:使用引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放。
    3. 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。

    没有一个结构会嵌入多于一个kobject结构,如果这么做了,关于这个对象的引用计数肯定会一团糟,你的code也会充满bug,所以千万不要这么做。

    实际上,kobject对自身实现什么功能并不感兴趣,它存在的意义在于把高级的对象链接到设备模型上。因此内核代码很少去创建一个单独的kobject对象,相反,kobject用于控制对大型域相关对象的访问(通过container_of)。

    ktype

    每个kobject对象都内嵌有一个ktype,该结构定义了kobject在创建和删除时所采取的行为,可以理解为对象的属性。

    实际上,Kobject的生命周期管理就是由关联的ktype来实现的。

    注意,每个kobject必须关联一个ktype。

    由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了。

    // include/linux/kobject.h
    struct kobj_type {
        /* 处理对象终结的回调函数。该接口应该由具体对象负责填充。 */
        void (*release)(struct kobject *kobj);
        /* 该类型kobj的sysfs操作接口。 */
        const struct sysfs_ops *sysfs_ops;
        /* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
        struct attribute **default_attrs;
        /*child_ns_type/namespace 是 文件系统命名空间相关)略*/
        const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
        const void *(*namespace)(struct kobject *kobj);
    };
    
    // linux/sysfs.h
    struct sysfs_ops {
        ssize_t (*show)(struct kobject *, struct attribute *, char *);
        ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
    };
    
    struct attribute {
        const char      *name;
        umode_t         mode;
    };
    

    当kobject的引用计数为0时,通过release方法来释放相关的资源。

    attribute为属性,每个属性在sysfs中都有对应的属性文件。

    sysfs_op的两个方法用于实现读取和写入属性文件时应该采取的行为。

    kset

    kset是一些kobject的集合,这些kobject可以有相同的ktype(属性),也可以不同。

    同时,kset自己也包含一个kobject;因此在sysfs中,kset也是对应这一个目录,但是目录下面包含着其他的kojbect。

    也有一种说法,把Kset看是一个特殊的Kobject。

    每个Kobject不一定出现在sys中,但Kset中的每个Kobject会出现在sys中。

    // include/linux/kobject.h
    /**
     * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
     *
     * A kset defines a group of kobjects.  They can be individually
     * different "types" but overall these kobjects all want to be grouped
     * together and operated on in the same manner.  ksets are used to
     * define the attribute callbacks and other common events that happen to
     * a kobject.
     *
     * @list: the list of all kobjects for this kset
     * @list_lock: a lock for iterating over the kobjects
     * @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
     * @uevent_ops: the set of uevent operations for this kset.  These are
     * called whenever a kobject has something happen to it so that the kset
     * can add new environment variables, or filter out the uevents if so
     * desired.
     */
    struct kset {
        /*属于该kset的kobject链表,与kobj->entry对应,用来组织本kset管理的kobj*/
        struct list_head list;
        spinlock_t list_lock;
        /*该kset内嵌的kobj*/
        struct kobject kobj;
        
        /*
            kset用于发送消息的操作函数集。
            需要指出的是,kset能够发送它所包含的各种子kobj、孙kobj的消息;
            即kobj或其父辈、爷爷辈,都可以发送消息;优先父辈,然后是爷爷辈,以此类推。 
        */
        const struct kset_uevent_ops *uevent_ops;
    };
    

    kset通过标准的内核链表(struct list_head list)来管理它的所有子节点,kobject通过kset成员变量指向它的kset容器。

    大多数情况下,kobject都是它所属kset(或者严格点,kset内嵌的kobject)的子节点。

    kobject与kset的关系

    下面这张图非常经典。最下面的kobj都属于一个kset,同时这些kobj的父对象就是kset内嵌的kobj

    通过链表,kset可以获取所有属于它的kobj

    sysfs角度而言,kset代表一个文件夹,而下面的kobj就是这个文件夹里面的内容,而内容有可能是文件也有可能是文件夹。

    kobjkset没有严格的层级关系,也并不是完全的父子关系。如何理解这句话?

    • kobject之间可以组成一个树形结构:Kobj.parent = kobj'

    • kset之间也可以组成一个树形结构,但这是基于kobject实现的:Kset.kobj.parent = Kset'.kobj

    正因为kobj和kset并不是完全的父子关系,因此在注册kobj时,将同时对parent及其所属的kset增加引用计数。

    若parent和kset为同一对象,则会对kset增加两次引用计数。

    kset算是kobj的“接盘侠”:

    • 当kobj没有所属的parent时,才让kset来接盘当parent;
    • 如果连kset也没有,那该kobj属于顶层对象,其sysfs目录将位于/sys/下。

    待会我们看kobject_add_internal中是如何做的。

    kset内部本身也包含一个kobj对象,在sysfs中也表现为目录;所不同的是,kset要承担kobj状态变动消息的发送任务。因此,首先kset会将所属的kobj组织在kset.list下,同时,通过uevent_ops在合适时候发送消息。

    对于kobject_add()来说,它的输入信息是:kobj-parentkobj-namekobject_add()优先使用传入的parent作为kobj->parent;其次,使用kset作为kobj->parent

    kobj状态变动后,必须依靠所关联的kset来向用户空间发送消息;若无关联kset(kobj向上组成的树中,任何成员都无所属的kset),则kobj无法发送用户消息。

    kobject 结构可能的层次结构如图:

    举例:通过kobject创建bus目录

    分析如何通过kobject/sys创建bus目录。

    // drivers/base/bus.c
    static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
    {
        struct kobj_type *ktype = get_ktype(kobj);
    
        if (ktype == &bus_ktype)
            return 1;
        return 0;
    }
    
    static const struct kset_uevent_ops bus_uevent_ops = {
        .filter = bus_uevent_filter,
    };
    
    int __init buses_init(void)
    {
        // 直接调用kset_create_and_add,第一个参数为要创建的目录的名字,而第三个参数指定父对象。
        bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
    
        // ...
    
        return 0;
    }
    

    kset_create_and_add

    kset_create_and_addkobject_createkobject_add的组合

    // lib/kobject.c
    /**
     * kset_create_and_add - create a struct kset dynamically and add it to sysfs
     *
     * @name: the name for the kset
     * @uevent_ops: a struct kset_uevent_ops for the kset
     * @parent_kobj: the parent kobject of this kset, if any.
     *
     * This function creates a kset structure dynamically and registers it
     * with sysfs.  When you are finished with this structure, call
     * kset_unregister() and the structure will be dynamically freed when it
     * is no longer being used.
     *
     * If the kset was not able to be created, NULL will be returned.
     */
    /**
     * kobject_create_and_add - 动态创建一个kobject结构并注册到sysfs
     *
     * @name: kobject的名称
     * @parent: kobject的parent kobject of this kobject, 如果有的话
     *
     * 该方法动态创建一个kobject结构并注册到sysfs。当你完成该结构
     * 之后. kobject_del(),这样该结构在不再使用时将会动态的释放。
     *
     * 如果该kobject无法被创建,将会返回NULL。
     */
    struct kset *kset_create_and_add(const char *name,
                     const struct kset_uevent_ops *uevent_ops,
                     struct kobject *parent_kobj)
    {
        struct kset *kset;
        int error;
        /*创建kset实例,设置某些字段*/
        kset = kset_create(name, uevent_ops, parent_kobj);
        if (!kset)
            return NULL;
        /*添加kset到sysfs*/
        error = kset_register(kset);
        if (error) {
            kfree(kset);
            return NULL;
        }
        return kset;
    }
    EXPORT_SYMBOL_GPL(kset_create_and_add);
    
    
    /**
     * kobject_add - the main kobject add function
     * @kobj: the kobject to add
     * @parent: pointer to the parent of the kobject.
     * @fmt: format to name the kobject with.
     *
     * The kobject name is set and added to the kobject hierarchy in this
     * function.
     *
     * If @parent is set, then the parent of the @kobj will be set to it.
     * If @parent is NULL, then the parent of the @kobj will be set to the
     * kobject associated with the kset assigned to this kobject.  If no kset
     * is assigned to the kobject, then the kobject will be located in the
     * root of the sysfs tree.
     *
     * If this function returns an error, kobject_put() must be called to
     * properly clean up the memory associated with the object.
     * Under no instance should the kobject that is passed to this function
     * be directly freed with a call to kfree(), that can leak memory.
     *
     * Note, no "add" uevent will be created with this call, the caller should set
     * up all of the necessary sysfs files for the object and then call
     * kobject_uevent() with the UEVENT_ADD parameter to ensure that
     * userspace is properly notified of this kobject's creation.
     */
    int kobject_add(struct kobject *kobj, struct kobject *parent,
            const char *fmt, ...)
    {
        va_list args;
        int retval;
    
        if (!kobj)
            return -EINVAL;
    
        if (!kobj->state_initialized) {
            printk(KERN_ERR "kobject '%s' (%p): tried to add an "
                   "uninitialized object, something is seriously wrong.
    ",
                   kobject_name(kobj), kobj);
            dump_stack();
            return -EINVAL;
        }
        va_start(args, fmt);
        retval = kobject_add_varg(kobj, parent, fmt, args);
        va_end(args);
    
        return retval;
    }
    EXPORT_SYMBOL(kobject_add);
    

    这里主要调用了两个函数,接下分别来看下。

    kset_create

    建立kset,设置名字与父对象。

    路径:lib/kobject.c

    /**
     * kset_create - create a struct kset dynamically
     *
     * @name: the name for the kset
     * @uevent_ops: a struct kset_uevent_ops for the kset
     * @parent_kobj: the parent kobject of this kset, if any.
     *
     * This function creates a kset structure dynamically.  This structure can
     * then be registered with the system and show up in sysfs with a call to
     * kset_register().  When you are finished with this structure, if
     * kset_register() has been called, call kset_unregister() and the
     * structure will be dynamically freed when it is no longer being used.
     *
     * If the kset was not able to be created, NULL will be returned.
     */
    static struct kset *kset_create(const char *name,
                    const struct kset_uevent_ops *uevent_ops,
                    struct kobject *parent_kobj)
    {
        struct kset *kset;
        int retval;
    
        /*创建 kset 实例*/
        kset = kzalloc(sizeof(*kset), GFP_KERNEL);
    
        /*设置kobj->name*/
        retval = kobject_set_name(&kset->kobj, "%s", name);
    
        kset->uevent_ops = uevent_ops;
        /*设置父对象*/
        kset->kobj.parent = parent_kobj;
    
        /*
         * The kobject of this kset will have a type of kset_ktype and belong to
         * no kset itself.  That way we can properly free it when it is
         * finished being used.
         */
        kset->kobj.ktype = &kset_ktype;
        /*本keset不属于任何kset*/
        kset->kobj.kset = NULL;
    
        return kset;
    }
    

    这个函数中,动态分配了kset实例,调用kobject_set_name设置kset->kobj->name为bus,也就是我们要创建的目录bus

    同时这里kset->kobj.parentNULL,也就是没有父对象。

    因为要创建的bus目录是在sysfs所在的根目录创建的,自然没有父对象。

    随后简要看下由kobject_set_name函数调用引发的一系列调用。

    // lib/kobject.c
    /**
     * kobject_set_name_vargs - Set the name of an kobject
     * @kobj: struct kobject to set the name of
     * @fmt: format string used to build the name
     * @vargs: vargs to format the string.
     */
    int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                      va_list vargs)
    {
        const char *old_name = kobj->name;
        char *s;
    
        if (kobj->name && !fmt)
            return 0;
    
        kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
        if (!kobj->name) {
            kobj->name = old_name;
            return -ENOMEM;
        }
    
        /* ewww... some of these buggers have '/' in the name ... */
        while ((s = strchr(kobj->name, '/')))
            s[0] = '!';
    
        kfree(old_name);
        return 0;
    }
    
    // lib/kasprintf.c
    /* Simplified asprintf. */
    char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
    {
        unsigned int len;
        char *p;
        va_list aq;
    
        va_copy(aq, ap);
        len = vsnprintf(NULL, 0, fmt, aq);
        va_end(aq);
    
        p = kmalloc_track_caller(len+1, gfp);
        if (!p)
            return NULL;
    
        vsnprintf(p, len+1, fmt, ap);
    
        return p;
    }
    EXPORT_SYMBOL(kvasprintf);
    

    kset_register

    // lib/kobject.c
    /**
     * kset_register - initialize and add a kset.
     * @k: kset.
     */
    int kset_register(struct kset *k)
    {
        int err;
    
        if (!k)
            return -EINVAL;
    
        /*初始化kset*/
        kset_init(k);
         /*在sysfs中建立目录*/
        err = kobject_add_internal(&k->kobj);
        if (err)
            return err;
        
        /* 向用户空间发送 KOBJ_ADD事件。 */
        kobject_uevent(&k->kobj, KOBJ_ADD);
        return 0;
    }
    

    这里面调用了3个函数。这里先介绍前两个函数。kobject_uevent实现的是用户空间事件传递,留到以后再说。

    kset_init

    该函数用于初始化kset。

    /**
     * kset_init - initialize a kset for use
     * @k: kset
     */
    void kset_init(struct kset *k)
    {
        /*初始化kobject的某些字段*/
        kobject_init_internal(&k->kobj);
        /*初始化链表头*/
        INIT_LIST_HEAD(&k->list);
        /*初始化自旋锁*/
        spin_lock_init(&k->list_lock);
    }
    
    /* 设置初始化状态 */
    static void kobject_init_internal(struct kobject *kobj)
    {
        if (!kobj)
            return;
        /*初始化引用基计数*/
        kref_init(&kobj->kref);
        /*初始化链表头*/
        INIT_LIST_HEAD(&kobj->entry);
        // 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。
        kobj->state_in_sysfs = 0;
        // 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。
        kobj->state_add_uevent_sent = 0;
        // 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。
        kobj->state_remove_uevent_sent = 0;
        // 标记已经初始化了。
        kobj->state_initialized = 1;
    }
    
    kobject_add_internal

    该函数将在sysfs中建立目录。

    kobject_add_internal()会根据kobj.parentkobj.kset来综合决定父对象(相当于/sysfs中的父级目录):

    • 如果有parent则默认parent为父对象;
    • 如果没有parent作为父对象,但是有当前的kobj位于kset中,则使用kset中的kobj作为父对象
    • 如果没有parent也没有kset作为父对象,那该kobj属于顶层对象:在sysfs中,我们可以看到kobj位于/sys/下。
    static int kobject_add_internal(struct kobject *kobj)
    {
        int error = 0;
        struct kobject *parent;
    
        if (!kobj)
            return -ENOENT;
    
        /*检查name字段是否存在*/
        if (!kobj->name || !kobj->name[0]) {
            return -EINVAL;
        }
    
        /*有父对象则增加父对象引用计数*/
        parent = kobject_get(kobj->parent);
    
        /* join kset if set, use it as parent if we do not already have one */
        /*
            由于在kset_create中有kset->kobj.kset = NULL,
            因此if (kobj->kset)条件不满足。
        */
        if (kobj->kset) {
            if (!parent)
                /*kobj属于某个kset,但是该kobj没有父对象,则以kset的kobj作为父对象*/
                parent = kobject_get(&kobj->kset->kobj);
            /*将kojbect添加到kset结构中的链表当中*/
            kobj_kset_join(kobj);
            /* 根据kobj.parent和kobj.kset来综合决定父对象*/
            kobj->parent = parent;
        }
    
        pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'
    ",
             kobject_name(kobj), kobj, __func__,
             parent ? kobject_name(parent) : "<NULL>",
             kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
    
        /*根据kobj->name在sys中建立目录*/
        error = create_dir(kobj);
        // 如果出错时,回收资源
        if (error) {
            /* 把kobj从kobj->kset的链表中去除 */
            // 对应于 kobj_kset_join
            kobj_kset_leave(kobj);
            kobject_put(parent);
            kobj->parent = NULL;
    
            /* be noisy on error issues */
            if (error == -EEXIST)
                WARN(1, "%s failed for %s with "
                     "-EEXIST, don't try to register things with "
                     "the same name in the same directory.
    ",
                     __func__, kobject_name(kobj));
            else
                WARN(1, "%s failed for %s (error: %d parent: %s)
    ",
                     __func__, kobject_name(kobj), error,
                     parent ? kobject_name(parent) : "'none'");
        } else
            kobj->state_in_sysfs = 1;
    
        return error;
    }
    

    因此在这个函数中,对name进行了必要的检查之后,调用了create_dir在sysfs中创建目录。

    create_dir执行完成以后会在sysfs的根目录(/sys/)建立文件夹bus。该函数的详细分析将在后面给出。

    至此,对bus目录的建立有了简单而直观的了解。

    我们可以看出kset其实就是表示一个文件夹,而kset本身也含有一个kobject,而该kobject的name字段即为该目录的名字,本例中为bus。

    kobj/kset功能特性

    我们先大概提一下这些功能特性,在后面的文章中会详细说明:对象生命周期管理以及用户空间事件投递

    对象生命周期管理

    在创建一个kobj对象时,kobj中的引用计数管理成员kref被初始化为1;从此kobj可以使用下面的API函数来进行生命周期管理:

    // lib/kobject.c
    /**
     * kobject_get - increment refcount for object.
     * @kobj: object.
     */
    struct kobject *kobject_get(struct kobject *kobj)
    {
        if (kobj)
            kref_get(&kobj->kref);
        return kobj;
    }
    
    
    /**
     * kobject_put - decrement refcount for object.
     * @kobj: object.
     *
     * Decrement the refcount, and if 0, call kobject_cleanup().
     */
    void kobject_put(struct kobject *kobj)
    {
        if (kobj) {
            kref_put(&kobj->kref, kobject_release);
        }
    }
    
    static void kobject_release(struct kref *kref)
    {
        struct kobject *kobj = container_of(kref, struct kobject, kref);
    
        kobject_cleanup(kobj);
    }
    

    对于kobject_get(),它就是直接使用kref_get()接口来对引用计数进行加1操作;

    而对于kobject_put(),它不仅要使用kref_put()接口来对引用计数进行减1操作,还要对生命终结的对象执行release()操作:当引用计数减为0时,回收该对象的资源。

    然而kobject是高度抽象的实体,导致kobject不会单独使用,而是嵌在具体对象中。反过来也可以这样理解:凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求。

    kobject在kobject_put的资源回收是如何实现的?

    实际上,kobject_put()通常被具体对象做一个简单包装,如:bus_put(),它直接调用kset_put(),然后调用到kobject_put()。

    那对于这个bus_type对象而言,仅仅通过kobject_put(),如何来达到释放整个bus_type的目的呢?这里就需要kobject另一个成员struct kobj_type * ktype来完成。

    当引用计数为0时,kobject核心会调用kobject_release(),最后会调用kobj_type->release(kobj)来完成对象的释放。可是具体对象的释放,最后却通过kobj->kobj_type->release()来释放,那这个release()函数,就必须得由具体的对象来指定。

    还是拿bus_type举例:

    在通过bus_register(struct bus_type *bus)进行总线注册时,该API内部会执行priv->subsys.kobj.ktype = &bus_ktype操作;

    // driver/base/bus.c
    int bus_register(struct bus_type *bus)
    {
        // ...
        
        priv->subsys.kobj.ktype = &bus_ktype;
        
        // ...
    }
    

    有了该操作,那么前面的bus_put()在执行bus_type->p-> subsys.kobj->ktype->release()时,就会执行上面注册的bus_ktype.release = bus_release函数;

    // driver/base/bus.c
    static struct kobj_type bus_ktype = {
        .sysfs_ops  = &bus_sysfs_ops,
        .release    = bus_release,
    };
    
    static void bus_release(struct kobject *kobj)
    {
        // 获取整个 具体的 bus子系统 对象
        struct subsys_private *priv =
            container_of(kobj, typeof(*priv), subsys.kobj);
        struct bus_type *bus = priv->bus;
    
        // 释放资源
        kfree(priv);
        bus->p = NULL;
    }
    

    由于bus_release()函数由具体的bus子系统提供,它必定知道如何释放包括kobj在内的bus_type对象。

    sysfs文件系统的层次组织

    sysfs向用户空间展示了驱动设备的层次结构。这一个功能比较简单,先完全贴出来。

    我们都知道设备和对应的驱动都是由内核管理的,这些对于用户空间是不可见的。现在通过sysfs,可以在用户空间直观的了解设备驱动的层次结构。

    实际上,sysfs文件系统的组织功能是基于kobject实现的:该功能依靠kobj的parent、kset、sd等成员来完成。sysfs文件系统能够以友好的界面,将kobj所刻画的对象层次、所属关系、属性值,展现在用户空间。

    我们来看看sysfs的文件结构:

    $ uname -r
    4.15.0-142-generic
    
    $ ls /sys
    block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power
    
    目录 意义
    block 块设备
    bus 系统中的总线
    class 设备类型,比如输入设备
    dev 系统中已注册的设备节点的视图,有两个子目录char和block。
    devices 系统中所有设备拓扑结构视图
    fireware 固件
    fs 文件系统
    kernel 内核配置选项和状态信息
    module 模块
    power 系统的电源管理数据

    用户空间事件投递

    请参考:uevent与热插拔

    实际上,当具体对象有事件发生时,相应的操作函数中(如device_add()),会调用事件消息接口kobject_uevent()

    在该接口中,首先会添加一些共性的消息(路径、子系统名等),然后会找寻合适的kset->uevent_ops,并调用该ops->uevent(kset, kobj, env)来添加该对象特有的事件消息(如device对象的设备号、设备名、驱动名、DT信息);

    kobject总结

    kobject可以看成是一个基类,这个基类通过ktype属性实现了资源回收的功能,至于kset,负责记录一组kobject,我们将其看成是一个集合,负责事件管理。

    从此,其他对象就可以通过包含kobject来实现上层的功能。

    sysfs

    对象模型体系

    sysfs(system filesystem)是一个处于内存中的虚拟文件系统,为我们提供了kobject对象层次结构的视图。以一个简单的文件系统的方式来观察系统中各种设备的拓扑结构。

    sysfs是用于表现设备驱动模型的文件系统,它基于ramfs。要学习linux的设备驱动模型,就要先做好底层工作,总结sysfs提供给外界的API就是其中之一。

    sysfs文件系统中提供了四类文件的创建与管理,分别是目录、普通文件、软链接文件、二进制文件。

    • 目录层次往往代表着设备驱动模型的结构
    • 软链接文件则代表着不同部分间的关系。比如某个设备的目录只出现在/sys/devices下,其它地方涉及到它时只好用软链接文件链接过去,保持了设备唯一的实例。
    • 普通文件和二进制文件往往代表了设备的属性,读写这些文件需要调用相应的属性读写。

    sysfs是表现设备驱动模型的文件系统,它的目录层次实际反映的是对象的层次。为了配合这种目录,linux专门提供了两个结构作为sysfs的骨架,它们就是struct kobject和struct kset。

    我们知道,sysfs是完全虚拟的,它的每个目录其实都对应着一个kobject,要想知道这个目录下有哪些子目录,就要用到kset。从面向对象的角度来讲,kset继承了kobject的功能,既可以表示sysfs中的一个目录,还可以包含下层目录。

    dentry结构体表示目录项,通过连接kobject到指定的目录项上,无疑方便的将kobject映射到该目录上。

    因此,kobjects其实已经形成了一棵树了——就是对象模型体系。

    由于kobject被映射到目录项,同时对象层次结构也已经在内存中形成了一棵树,因此sysfs就诞生了。

    $ tree  /sys | head -n 300
    /sys
    ├── block
        ...
    ├── bus
    │   ├── cpu
    │   │   ├── devices
    │   │   │   └── cpu0 -> ../../../devices/system/cpu/cpu0
    │   │   ├── drivers
    │   │   │   └── processor
    │   │   │       ├── bind
    │   │   │       ├── cpu0 -> ../../../../devices/system/cpu/cpu0
    │   │   │       ├── uevent
    │   │   │       └── unbind
    │   │   ├── drivers_autoprobe
    │   │   ├── drivers_probe
    │   │   └── uevent
    ...
    
    $ ls /sys
    block  bus  class  dev  devices  firmware  fs  kernel  module  power
    

    sysfs的根目录下包含了七个目录:block、bus、class、devices、firmware、module和power。

    • block目录下的每个子目录都对应着系统的一个块设备。
    • bus目录提供了一个系统总线视图。
    • class目录包含了以高层功能逻辑组织起来的系统设备视图。
    • devices目录是系统中设备拓扑结构视图,它直接映射除了内核中设备结构体的组织层次。
    • firmware目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树。
    • power目录包含了系统范围的电源管理数据。
    • 最重要的目录是devices,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。

    kernel 包括了一些内核的可调参数等信息,和devices目录关联性没那么强。

    注意,其他目录中的很多数据都是将devices目录下的数据加以转换加工而得;

    因此,在class章节中,我们会看到一些软链接的创建,其实就是为了优雅地归对设备进行归类。

    sysfs中添加和删除kobject

    导入kobject到sysfs

    仅仅初始化kobjcet是不能自动将其导出到sysfs中的,想要把kobject导入sysfs,需要用到函数kobject_add():

    需要注意的是,并不是说每一个kobject对象都需要在sysfs中表示,但是每一个被注册到系统中的kset都会被添加到sysfs文件系统中。

    一个kset对象就对应一个/sys中的一个目录,kset中的每一个kobject成员,都对应sysfs中一个文件或者一个目录。

    #include <linux/kobject.h> 
    #if 0 // 下面两种操作等价
    void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
    int kobject_add(struct kobject *kobj);
    #else
    struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
    // 再也没有 kobject_register 这个函数了
    #endif
    

    kobject在sysfs中的位置取决于kobject在对象层次结构中的位置。如果kobject的父指针被设置,那么在sysfs中的kobject将被映射为其父目录下的子目录。如果parent没有设置,那么kobject将被映射为kset->kobj中的子目录。

    如果给定的kobject中parent或kset字段都没有设置,那么就认为kobject没有父对象,所以就会被映射成sysfs下的根级目录。但这往往不是你所需要的,所以在调用kobject_add()前parent或kset字段应该显示的设置。

    不管怎样,sysfs中代表kobject的目录名字是由kobj->k_name指定的。

    不过不需要调用kobject_init()kobject_add(),因为系统提供了函数kobject_create_and_add()

    该函数既初始化了给定的kobject对象,同时又将其加入到对象层次结构中。

    从sysfs删除kobject

    void kobject_del(struct kobject *kobj);
    

    从sysfs中删除一个kobject对应文件目录。需使用函数kobject_del(),内部会自动使用put来让引用减一。

    以前的版本需要再调用一次kobject_put;但现在不需要了。

    向sysfs中添加文件

    kobject已经被映射为文件目录。所有的对象层次一个不少的映射成sys下的目标结构。

    sysfs仅仅是一个漂亮的树,但是没有提供实际数据的文件。

    因此,需要kobj_type来进行支持,有关sysfs的成员如下:

    // include/linux/kobject.h
    struct kobj_type {
        // ...
        /* 该类型kobj的sysfs操作接口。 */
        const struct sysfs_ops *sysfs_ops;
        /* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
        struct attribute **default_attrs;
    };
    

    默认属性attribute

    默认的文件集合是通过Kobject和kset的ktype字段提供的。因此所有具有相同类型的kobject在它们对应的sysfs目录下都拥有相同的默认文件集合。

    kobj_type字段含有一个字段——default_attrs,它是一个attribute结构体数组。这些属性负责将内核数据映射成sysfs中的文件。

    attribute原型
    // include/linux/sysfs.h
    #include <linux/sysfs.h>
    struct attribute {
        /* 属性名称 */
        const char      *name;
        /* 访问权限 */
        umode_t         mode;
        // ...
    };
    
    struct attribute_group {
        const char      *name;
        umode_t         (*is_visible)(struct kobject *,
                              struct attribute *, int);
        struct attribute    **attrs;
        struct bin_attribute    **bin_attrs;
    };
    
    struct bin_attribute {
        struct attribute    attr;
        size_t          size;
        void            *private;
        ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
                char *, loff_t, size_t);
        ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
                 char *, loff_t, size_t);
        int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
                struct vm_area_struct *vma);
    };
    

    其中,name:提供了该属性的名称,最终出现在sysfs中的文件名就是它。

    owner(todo):在存在所属模块的情况下指向其所属module结构体。如果一个模块没有该属性,那么该字段为NULL。

    mode字段类型为mode_t,它表示了sysfs中该文件的权限。

    • 对于只读属性而言,如果是所有人都可读它,那么该字段设为S_IRUGO;
    • 如果只限于所有者可读,则该字段设置为S_IRUSR。
    • 同样对于可写属性,可能会设置该字段为S_IRUGO|S_IWUSR。
    • sysfs中的所有文件和目录的uid与gid标志均为零。

    如何理解属性?

    $ ls /sys/class/leds/input1::capslock
    brightness  device  max_brightness  power  subsystem  trigger  uevent
    

    其中,brightnesstrigger这些文件就是属性,以文件的形式提供。

    属性读写方法sysfs_ops

    虽然default_attrs列出了默认的属性,sysfs_ops则描述了如何使用它们。

    #include <linux/sysfs.h>
    struct sysfs_ops {
        /* 读取该 sysfs 文件时该方法被调用 */
        ssize_t (*show)(struct kobject *, struct attribute *, char *);
        /* 写入该 sysfs 文件时该方法被调用 */
        ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
    };
    

    struct sysfs_ops中包含show和store两个函数指针,它们分别在sysfs文件读和文件写时调用。

    创建/删除新属性

    一些特别情况下会碰到特殊的kobject实例,它希望(甚至必须)有自己的属性——也许是通用属性没包含那些需要的数据或者函数。

    因此使用sysfs_create_file()接口在默认集合上添加新属性:

    #include <linux/sysfs.h>
    int sysfs_create_file(struct kobject *kobj,
                          const struct attribute *attr);
    int sysfs_create_files(struct kobject *kobj,
                           const struct attribute **attr);
    
    void sysfs_remove_file(struct kobject *kobj,
                           const struct attribute *attr)
    

    这个接口通过attr参数指向相应的attribute结构体,而参数kobj则指定了属性所在的kobject对象。

    在该函数被调用之前,给定的属性将被赋值,如果成功,该函数返回零。否则返回负的错误码。

    kobject中ktype.sysfs_ops操作将负责处理新属性。现有的show()store()方法必须能够处理新属性。

    删除一个属性通过函数sysfs_remove_file()完成:一旦调用sysfs_remove_file,给定的属性将不复存在于给定的kobject目录中。

    创建/删除软链接

    除了添加文件外,可能还要创建符号连接。在sysfs中创建一个符号连接方式。

    int  sysfs_create_link(struct kobject *kobj, struct kobject *target,
                           const char *name);
    int sysfs_create_link_nowarn(struct kobject *kobj,
                                 struct kobject *target,
                                 const char *name);
    

    该函数创建的符号连接名由name指定,连接则由kobj对应的目录映射到target指定的目录。如果成功,则返回零,如果失败,返回负的错误码。

    而由sysfs_create_link()创建的符号连接可通过函数sysfs_remove_link()删除:

    void sysfs_remove_link(struct kobject *kobj, const char *name);
    // 一旦调用返回,给定的连接将不复存在于给定的kobject目录中。
    

    sysfs约定

    首先sysfs属性应该保证每个属性文件只导出一个值,该值应该是文本形式而且被映射为简单C类型。其次,在sysfs中要以一个清晰的层次组织数据。最后,记住sysfs提供内核到用户空间的服务,这多少有些用户空间的ABI(应用程序二进制接口)的作用。

    附录:kobject创建

    简单创建kobj以及添加

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/types.h>
    #include <linux/kobject.h>
    
    struct kobject *g_obj = NULL;
    struct kobject *g_parobj = NULL;
    
    static int __init  test_init(void)
    {    
        g_parobj  = kobject_create_and_add("dog",NULL);
        if(NULL == g_parobj)
        {
            printk("create kobj error
    ");
            return -1;
        }
    
        g_obj  = kobject_create_and_add("whitedog",g_parobj);
        if(NULL == g_obj)
        {
            if(g_parobj)
            {     
                kobject_del(g_parobj); 
                kobject_put(g_parobj);
            }
        }
        return 0;
    }
    
    static void __exit test_exit(void)
    {
        if(g_obj)
        {
            kobject_del(g_obj); 
            kobject_put(g_obj);
        }
        if(g_parobj)
        {
            kobject_del(g_parobj); 
            kobject_put(g_parobj);
        }
    
        return;
    }
    
    module_init(test_init);
    module_exit(test_exit); 
    MODULE_LICENSE("GPL");
    

    内核kobj例子

    kobject-example.c

    代码来自:samples/kobject/kobject-example.c

    可能会遇到error: negative width in bit-field ‘<anonymous>’这个错误,需要将__ATTR中的666权限改为664

    /*
     * Sample kobject implementation
     *
     * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
     * Copyright (C) 2007 Novell Inc.
     *
     * Released under the GPL version 2 only.
     *
     */
    #include <linux/kobject.h>
    #include <linux/string.h>
    #include <linux/sysfs.h>
    #include <linux/module.h>
    #include <linux/init.h>
    
    /*
     * This module shows how to create a simple subdirectory in sysfs called
     * /sys/kernel/kobject-example  In that directory, 3 files are created:
     * "foo", "baz", and "bar".  If an integer is written to these files, it can be
     * later read out of it.
     */
    
    static int foo;
    static int baz;
    static int bar;
    
    /*
     * The "foo" file where a static variable is read from and written to.
     */
    static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
    			char *buf)
    {
    	return sprintf(buf, "%d
    ", foo);
    }
    
    static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
    			 const char *buf, size_t count)
    {
    	sscanf(buf, "%du", &foo);
    	return count;
    }
    
    static struct kobj_attribute foo_attribute =
    	__ATTR(foo, 0664, foo_show, foo_store);
    
    /*
     * More complex function where we determine which variable is being accessed by
     * looking at the attribute for the "baz" and "bar" files.
     */
    static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
    		      char *buf)
    {
    	int var;
    
    	if (strcmp(attr->attr.name, "baz") == 0)
    		var = baz;
    	else
    		var = bar;
    	return sprintf(buf, "%d
    ", var);
    }
    
    static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
    		       const char *buf, size_t count)
    {
    	int var;
    
    	sscanf(buf, "%du", &var);
    	if (strcmp(attr->attr.name, "baz") == 0)
    		baz = var;
    	else
    		bar = var;
    	return count;
    }
    
    static struct kobj_attribute baz_attribute =
    	__ATTR(baz, 0664, b_show, b_store);
    static struct kobj_attribute bar_attribute =
    	__ATTR(bar, 0664, b_show, b_store);
    
    
    /*
     * Create a group of attributes so that we can create and destroy them all
     * at once.
     */
    static struct attribute *attrs[] = {
    	&foo_attribute.attr,
    	&baz_attribute.attr,
    	&bar_attribute.attr,
    	NULL,	/* need to NULL terminate the list of attributes */
    };
    
    /*
     * An unnamed attribute group will put all of the attributes directly in
     * the kobject directory.  If we specify a name, a subdirectory will be
     * created for the attributes with the directory being the name of the
     * attribute group.
     */
    static struct attribute_group attr_group = {
    	.attrs = attrs,
    };
    
    static struct kobject *example_kobj;
    
    static int __init example_init(void)
    {
    	int retval;
    
    	/*
    	 * Create a simple kobject with the name of "kobject_example",
    	 * located under /sys/kernel/
    	 *
    	 * As this is a simple directory, no uevent will be sent to
    	 * userspace.  That is why this function should not be used for
    	 * any type of dynamic kobjects, where the name and number are
    	 * not known ahead of time.
    	 */
    	example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
    	if (!example_kobj)
    		return -ENOMEM;
    
    	/* Create the files associated with this kobject */
    	retval = sysfs_create_group(example_kobj, &attr_group);
    	if (retval)
    		kobject_put(example_kobj);
    
    	return retval;
    }
    
    static void __exit example_exit(void)
    {
    	kobject_put(example_kobj);
    }
    
    module_init(example_init);
    module_exit(example_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
    

    对应的Makefile

    EXTRA_CFLAGS += $(DEBFLAGS)
    #EXTRA_CFLAGS += -I$(INCDIR)
    
    ########## change your module name here
    MODULE   = myKobj
    
    ########## change your obj file(s) here
    $(MODULE)-objs:= kobject-example.o
    #CROSS_COMPILE ?= arm-linux-gnueabihf-
    #ARCH 		  ?= arm
    
    ifneq ($(KERNELRELEASE), )
    	obj-m := $(MODULE).o
    
    else
    	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    	PWD := $(shell pwd)
    
    all:
    	$(MAKE_BEGIN)
    	@echo
    	@if 
    	$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; 
    	then $(MAKE_DONE);
    	else 
    	$(MAKE_ERR);
    	exit 1; 
    	fi
    
    endif
    
    show:
    	@echo "ARCH     :    ${ARCH}"
    	@echo "CC       :    ${CROSS_COMPILE}gcc"
    	@echo "KDIR     :    ${KERNELDIR}"
    	@echo "$(MODULE):    $(ALLOBJS)"
    clean:
    	$(CLEAN_BEGIN)
    	rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
    	$(CLEAN_END)
    
    .PHONY:all clean show
    #xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ### nothing
    #OFFSET=e[21G    # 21 col
    COLOR1=e[32m  # all --> bule
    COLOR2=e[33m  # clean --> brown
    COLOR3=e[31m  # error --> red
    RESET=e[0m
    
    CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
    CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"
    
    MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
    ### I do not forget "@", but it DOES NOT need "@"
    MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
    MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
    

    测试

    编译以后,加载模块:

    $ sudo insmod  myKobj.ko
    

    发现创建了/sys/kernel/kobject_example/目录,而且目录中有3个属性文件。

    $ ls /sys/kernel/kobject_example/
    bar  baz  foo
    

    读写这些属性

    $ cat /sys/kernel/kobject_example/bar
    0
    
    $ cat /sys/kernel/kobject_example/baz
    0
    
    $ cat /sys/kernel/kobject_example/foo
    0
    $ sudo su
    # echo "2021" > /sys/kernel/kobject_example/foo
    # cat  /sys/kernel/kobject_example/foo
    2021
    

    内核kset例子

    kset-example.c

    代码来自:samples/kobject/kset-example.c

    可能会遇到error: negative width in bit-field ‘<anonymous>’这个错误,需要将__ATTR中的666权限改为664

    /*
     * Sample kset and ktype implementation
     *
     * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
     * Copyright (C) 2007 Novell Inc.
     *
     * Released under the GPL version 2 only.
     *
     */
    #include <linux/kobject.h>
    #include <linux/string.h>
    #include <linux/sysfs.h>
    #include <linux/slab.h>
    #include <linux/module.h>
    #include <linux/init.h>
    
    /*
     * This module shows how to create a kset in sysfs called
     * /sys/kernel/kset-example
     * Then tree kobjects are created and assigned to this kset, "foo", "baz",
     * and "bar".  In those kobjects, attributes of the same name are also
     * created and if an integer is written to these files, it can be later
     * read out of it.
     */
    
    
    /*
     * This is our "object" that we will create a few of and register them with
     * sysfs.
     */
    struct foo_obj {
    	struct kobject kobj;
    	int foo;
    	int baz;
    	int bar;
    };
    #define to_foo_obj(x) container_of(x, struct foo_obj, kobj)
    
    /* a custom attribute that works just for a struct foo_obj. */
    struct foo_attribute {
    	struct attribute attr;
    	ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf);
    	ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count);
    };
    #define to_foo_attr(x) container_of(x, struct foo_attribute, attr)
    
    /*
     * The default show function that must be passed to sysfs.  This will be
     * called by sysfs for whenever a show function is called by the user on a
     * sysfs file associated with the kobjects we have registered.  We need to
     * transpose back from a "default" kobject to our custom struct foo_obj and
     * then call the show function for that specific object.
     */
    static ssize_t foo_attr_show(struct kobject *kobj,
    			     struct attribute *attr,
    			     char *buf)
    {
    	struct foo_attribute *attribute;
    	struct foo_obj *foo;
    
    	attribute = to_foo_attr(attr);
    	foo = to_foo_obj(kobj);
    
    	if (!attribute->show)
    		return -EIO;
    
    	return attribute->show(foo, attribute, buf);
    }
    
    /*
     * Just like the default show function above, but this one is for when the
     * sysfs "store" is requested (when a value is written to a file.)
     */
    static ssize_t foo_attr_store(struct kobject *kobj,
    			      struct attribute *attr,
    			      const char *buf, size_t len)
    {
    	struct foo_attribute *attribute;
    	struct foo_obj *foo;
    
    	attribute = to_foo_attr(attr);
    	foo = to_foo_obj(kobj);
    
    	if (!attribute->store)
    		return -EIO;
    
    	return attribute->store(foo, attribute, buf, len);
    }
    
    /* Our custom sysfs_ops that we will associate with our ktype later on */
    static const struct sysfs_ops foo_sysfs_ops = {
    	.show = foo_attr_show,
    	.store = foo_attr_store,
    };
    
    /*
     * The release function for our object.  This is REQUIRED by the kernel to
     * have.  We free the memory held in our object here.
     *
     * NEVER try to get away with just a "blank" release function to try to be
     * smarter than the kernel.  Turns out, no one ever is...
     */
    static void foo_release(struct kobject *kobj)
    {
    	struct foo_obj *foo;
    
    	foo = to_foo_obj(kobj);
    	kfree(foo);
    }
    
    /*
     * The "foo" file where the .foo variable is read from and written to.
     */
    static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
    			char *buf)
    {
    	return sprintf(buf, "%d
    ", foo_obj->foo);
    }
    
    static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
    			 const char *buf, size_t count)
    {
    	sscanf(buf, "%du", &foo_obj->foo);
    	return count;
    }
    
    static struct foo_attribute foo_attribute =
    	__ATTR(foo, 0664, foo_show, foo_store);
    
    /*
     * More complex function where we determine which variable is being accessed by
     * looking at the attribute for the "baz" and "bar" files.
     */
    static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
    		      char *buf)
    {
    	int var;
    
    	if (strcmp(attr->attr.name, "baz") == 0)
    		var = foo_obj->baz;
    	else
    		var = foo_obj->bar;
    	return sprintf(buf, "%d
    ", var);
    }
    
    static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
    		       const char *buf, size_t count)
    {
    	int var;
    
    	sscanf(buf, "%du", &var);
    	if (strcmp(attr->attr.name, "baz") == 0)
    		foo_obj->baz = var;
    	else
    		foo_obj->bar = var;
    	return count;
    }
    
    static struct foo_attribute baz_attribute =
    	__ATTR(baz, 0664, b_show, b_store);
    static struct foo_attribute bar_attribute =
    	__ATTR(bar, 0664, b_show, b_store);
    
    /*
     * Create a group of attributes so that we can create and destroy them all
     * at once.
     */
    static struct attribute *foo_default_attrs[] = {
    	&foo_attribute.attr,
    	&baz_attribute.attr,
    	&bar_attribute.attr,
    	NULL,	/* need to NULL terminate the list of attributes */
    };
    
    /*
     * Our own ktype for our kobjects.  Here we specify our sysfs ops, the
     * release function, and the set of default attributes we want created
     * whenever a kobject of this type is registered with the kernel.
     */
    static struct kobj_type foo_ktype = {
    	.sysfs_ops = &foo_sysfs_ops,
    	.release = foo_release,
    	.default_attrs = foo_default_attrs,
    };
    
    static struct kset *example_kset;
    static struct foo_obj *foo_obj;
    static struct foo_obj *bar_obj;
    static struct foo_obj *baz_obj;
    
    static struct foo_obj *create_foo_obj(const char *name)
    {
    	struct foo_obj *foo;
    	int retval;
    
    	/* allocate the memory for the whole object */
    	foo = kzalloc(sizeof(*foo), GFP_KERNEL);
    	if (!foo)
    		return NULL;
    
    	/*
    	 * As we have a kset for this kobject, we need to set it before calling
    	 * the kobject core.
    	 */
    	foo->kobj.kset = example_kset;
    
    	/*
    	 * Initialize and add the kobject to the kernel.  All the default files
    	 * will be created here.  As we have already specified a kset for this
    	 * kobject, we don't have to set a parent for the kobject, the kobject
    	 * will be placed beneath that kset automatically.
    	 */
    	retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);
    	if (retval) {
    		kobject_put(&foo->kobj);
    		return NULL;
    	}
    
    	/*
    	 * We are always responsible for sending the uevent that the kobject
    	 * was added to the system.
    	 */
    	kobject_uevent(&foo->kobj, KOBJ_ADD);
    
    	return foo;
    }
    
    static void destroy_foo_obj(struct foo_obj *foo)
    {
    	kobject_put(&foo->kobj);
    }
    
    static int __init example_init(void)
    {
    	/*
    	 * Create a kset with the name of "kset_example",
    	 * located under /sys/kernel/
    	 */
    	example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);
    	if (!example_kset)
    		return -ENOMEM;
    
    	/*
    	 * Create three objects and register them with our kset
    	 */
    	foo_obj = create_foo_obj("foo");
    	if (!foo_obj)
    		goto foo_error;
    
    	bar_obj = create_foo_obj("bar");
    	if (!bar_obj)
    		goto bar_error;
    
    	baz_obj = create_foo_obj("baz");
    	if (!baz_obj)
    		goto baz_error;
    
    	return 0;
    
    baz_error:
    	destroy_foo_obj(bar_obj);
    bar_error:
    	destroy_foo_obj(foo_obj);
    foo_error:
    	kset_unregister(example_kset);
    	return -EINVAL;
    }
    
    static void __exit example_exit(void)
    {
    	destroy_foo_obj(baz_obj);
    	destroy_foo_obj(bar_obj);
    	destroy_foo_obj(foo_obj);
    	kset_unregister(example_kset);
    }
    
    module_init(example_init);
    module_exit(example_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
    

    对应的Makefile

    EXTRA_CFLAGS += $(DEBFLAGS)
    #EXTRA_CFLAGS += -I$(INCDIR)
    
    ########## change your module name here
    MODULE   = myKset
    
    ########## change your obj file(s) here
    #$(MODULE)-objs:= kobject-example.o
    $(MODULE)-objs:= kset-example.o
    #CROSS_COMPILE ?= arm-linux-gnueabihf-
    #ARCH 		  ?= arm
    
    ifneq ($(KERNELRELEASE), )
    	obj-m := $(MODULE).o
    
    else
    	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    	PWD := $(shell pwd)
    
    all:
    	$(MAKE_BEGIN)
    	@echo
    	@if 
    	$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; 
    	then $(MAKE_DONE);
    	else 
    	$(MAKE_ERR);
    	exit 1; 
    	fi
    
    endif
    
    show:
    	@echo "ARCH     :    ${ARCH}"
    	@echo "CC       :    ${CROSS_COMPILE}gcc"
    	@echo "KDIR     :    ${KERNELDIR}"
    	@echo "$(MODULE):    $(ALLOBJS)"
    clean:
    	$(CLEAN_BEGIN)
    	rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
    	$(CLEAN_END)
    
    .PHONY:all clean show
    #xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ### nothing
    #OFFSET=e[21G    # 21 col
    COLOR1=e[32m  # all --> bule
    COLOR2=e[33m  # clean --> brown
    COLOR3=e[31m  # error --> red
    RESET=e[0m
    
    CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
    CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"
    
    MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
    ### I do not forget "@", but it DOES NOT need "@"
    MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
    MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
    

    测试

    编译以后,加载模块:

    $ sudo insmod  myKset.ko
    

    发现创建了/sys/kernel/kset_example/目录,而且目录中有3个子目录,每一个子目录中都有3个属性文件。

    $ tree /sys/kernel/kset_example/
    /sys/kernel/kset_example/
    ├── bar
    │   ├── bar
    │   ├── baz
    │   └── foo
    ├── baz
    │   ├── bar
    │   ├── baz
    │   └── foo
    └── foo
        ├── bar
        ├── baz
        └── foo
    

    读写就不再啰嗦了,是一样的。

    附录:一部分关于kobject的API

    由于我们现在关心的是设备驱动模型,因此暂不了解。等到具体的代码中,我们再进行解析。

    动态创建

    struct kobject *kobject_create(void);
    

    动态创建一个kobject结构,并将其设置为一个带默认释放方法(dynamic_kobj_ktype)的动态的kobject。

    如果无法创建kobject,将会返回NULL。从这里返回的kobject 结构释放必须调用kobject_put() 方法而不是kfree(),因为kobject_init()已经被调用过了。

    初始化

    创建kobject当然必须初始化kobject对象,kobject的一些内部成员需要(强制)通过kobject_init()初始化:

    void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
    

    该方法会正确的初始化一个kobject来保证它可以被传递给kobject_add()

    该功能被调用后,kobject必须通过调用kobject_put()来清理,而不是直接调用kfree,来保证所有的内存都可以被正确的清理。

    辅助函数kobject_init_and_add用来在同时初始化和添加kobject,它的参数和前面介绍的单独使用kobject_init()、kobject_add()这两个函数时一样

    int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
                             struct kobject *parent, const char *fmt, ...);
    

    添加到sysfs

    在调用kobject_init将kobject注册到sysfs之后,必须调用kobject_add()添加:

    /**
     * kobject_add - kobject添加主方法
     * @kobj: 要添加的kobject
     * @parent: 指向父kobject的指针
     * @fmt: kobject带的名称格式
     *
     * 本方法中会设置kobject名称并添加到kobject阶层。
     *
     * 如果设置了@parent, 那么@kobj的parent将会被设置为它.
     * 如果 @parent为空, 那么该@kobj的 parent会被设置为与该kobject
     * 相关联的.  如果没有kset分配给这个kobject,那么该kobject会放在
     * sysfs的根目录。
     *
     * 如果该方法返回错误,必须调用 kobject_put()来正确的清理该object
     * 相关联的内存。
     * 在任何情况下都不要直接通过调用to kfree()来直接释放传递给该方法
     * 的kobject,那样会导致内存泄露。
     *
     * 注意,该调用不会创建"add" uevent, 调用方需要为该object设置好所有
     * 必要的sysfs文件,然后再调用带UEVENT_ADD 参数的kobject_uevent() 
     * 来保证用户控件可以正确的收到该kobject创建的通知。
     */
    
    int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
    

    它正确设置了kobject的名称和父节点,如果这个kobject和一个特定的kset关联,则kobj->kset必须在调用kobject_add之前被指定。

    若kobject已经跟一个kset关联,在调用kobject_add时可以将这个kobject的父节点设置为NULL,这样它的父节点就会被设置为这个kset本身。

    在将kobject添加到kernel时,它的名称就已经被设定好了,代码中不应该直接操作kobject的名称。

    不过不需要调用kobject_init()kobject_add(),因为系统提供了函数kobject_create_and_add()

    #include <linux/kobject.h> 
    #if 0 // 下面两种操作等价
    struct kobject *kobject_create(void);
    int kobject_add(struct kobject *kobj);
    #else
    struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
    // 再也没有 kobject_register 这个函数了
    #endif
    

    创建简单的kobject

    有些时候,开发者希望的只是在sysfs中创建一个目录,并且不会被kset、show、store函数等细节的复杂概念所迷惑。

    这里有一个可以单独创建kobject的例外,为了创建这样一个入口,可以通过如下的函数来实现:

    struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
    

    这个函数会创建一个kobject,并把它的目录放到指定父节点在sysfs中目录里面。

    重命名

    如果你必须为kobject改名,则调用kobject_rename()函数实现:

    int kobject_rename(struct kobject *kobj, const char *new_name);
    
    // 如果 kobject 还没被 add 进 sysfs,
    // 那么还可以使用下列的函数,否则必须使用kobject_rename
    int kobject_set_name(struct kobject *kobj, const char *fmt, ...);
    int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                               va_list vargs);
    

    kobject_rename内部并不执行任何锁定操作,也没用何时名称是有效的概念。因此调用者必须自己实现完整性检查和保证序列性。

    可以通过kobject_name()函数来正确获取kobject的名称:

    const char *kobject_name(const struct kobject * kobj);
    

    附录:sysfs API

    见:Linux 内核:sysfs 有关的API

    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    LinqToXML~读XML文件
    C#~使用FileSystemWatcher来监视文件系统的变化
    HDU4144:Bacon's Cipher
    国产手机的路还很长
    同一个存储过程中,不能多次select into 到同一张表的问题
    IT行业为什么没有进度
    IOT(Index Organized Table)
    模版——容器,迭代器
    shell split分析日志文件
    VMware vSphere 服务器虚拟化之十七 桌面虚拟化之安装View链接服务器
  • 原文地址:https://www.cnblogs.com/schips/p/linux_device_model_1.html
Copyright © 2020-2023  润新知