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

(todo)Linux 内核:设备驱动模型(0)sysfs与kobject

背景

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

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

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

参考:

内核版本:3.14

kobject, kset和ktype

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

sysfs与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,所以千万不要这么做。

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就是这个文件夹里面的内容,而内容有可能是文件也有可能是文件夹。

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

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 结构可能的层次结构如图:

(todo)Linux 内核:设备驱动模型(0)sysfs与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.
 */
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.\n",
               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;
}

Issues

读源码时有个疑问,当调用kset_create()时,为何kset->kobj.kset=NULL;
源码中的解释如下:

/* 
 * 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. 
*/

但是当第三个参数parent_kobj有值时,既然kset->kobj.parent=parent_kobj; 那么说明kset->kobj是有上层kset的,而这个上层kset应该可以用container_of由parent_kobj得出。

但为何在源码中直接就将kset->kobj.kset置为NULL了呢。是因为kset不会从属于其它kset么。谢谢

wowo

@Issues:

首先,kset是一个特殊的kobject,它是kobject的容器,用于将多个kobject收集在一起。那么kset是否还会从属于另一个kset?即容器是否会嵌套?从代码注释上可以看出,答案是否定的。从实际逻辑上考虑,这也是合理的。这就是kset->kobj.kset为NULL的原因。
其次,虽然kset->kobj.kset为NULL,但为了kset的自动free,它还是需要一个ktype的,这就是为什么kset->kobj.ktype不为NULL。
最后,您提到的kset->kobj.parent=parent_kobj,也就是说该kset可能会有一个上层的parent(也是一个kset),这就是另外一个含义了,kset之间可以组成一个树形结构,这不是kset要描述的事情,而是kobject要描述的。

loren
2017-05-10 20:09

@wowo:“kset->kobj.parent=parent_kobj,能说明该kset可能会有一个上层的parent(也是一个kset)”
为什么这个上层的parent必须是一个kset呢?不能这个parent不属于任何一个kset吗?

wowo
2017-05-11 08:56

@loren:是的,你说的对,kernel没有规定这个parent kobj必须是什么,它只是为了让kset有个树形结构,因而可以是另一个kset,也有可能是一个普通的数据结构。

不过看kernel代码,大部分时候,都是一个kset。因为kset的上级是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->state_in_sysfs = 0;
    kobj->state_add_uevent_sent = 0;
    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‘\n",
         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.\n",
                 __func__, kobject_name(kobj));
        else
            WARN(1, "%s failed for %s (error: %d parent: %s)\n",
                 __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来实现需求。

下列的东西写入 bus 驱动中

回到`kobject_put()`,它通常被具体对象做一个简单包装,如bus_put(),它直接调用kset_put(),然后调用到kobject_put()。那对于这个bus_type对象而言,仅仅通过kobject_put(),如何来达到释放整个bus_type的目的呢?这里就需要kobject另一个成员`struct kobj_type * ktype`来完成。 

回到kobject_put()的release()操作。当引用计数为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`操作;

有了该操作,那么前面的`bus_put()`在执行`bus_type->p-> subsys.kobj->ktype->release()`时,就会执行上面注册的`bus_ktype.release = bus_releas`e函数,由于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 系统的电源管理数据

用户空间事件投递

这方面的内容可参考《http://www.wowotech.net/device_model/uevent.html》,该博文已经详细的说明了用户空间事件投递。

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

一切准备完毕,就会通过两种可能的方法向用户空间发送消息:1.netlink广播;2. uevent_helper程序。 

因此,上面具体对象的事件消息填充函数,应该由特定对象来填充;对于device对象来说,在初始化的时候,通过下面这行代码: `devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);`

来完成kset->uevent_ops的指定,今后所有设备注册时,调用device_register()-->device_initialize()后,都将导致: 

dev->kobj.kset = devices_kset;

因此通过device_register()注册的设备,在调用kobject_uevent()接口发送事件消息时,就自动会调用devices_kset的device_uevent_ops。该ops的uevent()方法定义如下: 

> static const struct kset_uevent_ops device_uevent_ops = { 
>
> ?     .filter =   dev_uevent_filter, 
>
> ?     .name =       dev_uevent_name, 
>
> ?     .uevent = dev_uevent, 
>
> };        || 
>
> ?         \/ 
>
> dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env) 
>
> ?     add_uevent_var(env, "xxxxxxx", ....) 
>
> ?     add_uevent_var(env, "xxxxxxx", ....) 
>
> ?     add_uevent_var(env, "xxxxxxx", ....) 
>
> ?     .... 
>
> ?     if (dev->bus && dev->bus->uevent) 
>
> ?          dev->bus->uevent(dev, env)    //通过总线的uevent()方法,发送设备状态改变的事件消息 
>
> ?     if (dev->class && dev->class->dev_uevent) 
>
> ?          dev->class->dev_uevent(dev, env) 
>
> ?     if (dev->type && dev->type->uevent) 
>
> ?          dev->type->uevent(dev, env) 

在该ops的uevent()方法中,会分别调用bus、class、device type的uevent方法来生成事件消息。 

总结

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

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

kobject_create_and_add解析

// lib/kobject.c
/**
 * kobject_create_and_add - 动态创建一个kobject结构并注册到sysfs
 *
 * @name: kobject的名称
 * @parent: kobject的parent kobject of this kobject, 如果有的话
 *
 * 该方法动态创建一个kobject结构并注册到sysfs。当你完成该结构
 * 之后. 调用kobject_put(),这样该结构在不再使用时将会动态的释放。
 *
 * 如果该kobject无法被创建,将会返回NULL。
 */
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
    struct kobject *kobj;
    int retval;

    kobj = kobject_create();
    if (!kobj)
        return NULL;

    retval = kobject_add(kobj, parent, "%s", name);
    if (retval) {
        printk(KERN_WARNING "%s: kobject_add error: %d\n",  __func__, retval);
        kobject_put(kobj);
        kobj = NULL;
    }
    return kobj;
}

kobject_create

/**
 * kobject_create - 动态创建一个kobject结构
 *
 * 动态创建一个kobject结构,并将其设置为一个带默认释放方法的
 * 动态的kobject。
 *
 * 如果无法创建kobject,将会返回NULL。从这里返回的kobject 结构释放
 * 必须调用kobject_put() 方法而不是kfree(),因为kobject_init()已经被调用
 * 过了。
 */
struct kobject *kobject_create(void)
{
    struct kobject *kobj;

    kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    if (!kobj)
        return NULL;

    kobject_init(kobj, &dynamic_kobj_ktype);
    return kobj;
}

kobject_init

/**
 * kobject_init - 初始化一个kobject结构
 * @kobj: 指向要初始化的kobject的指针
 * @ktype: 指向该kobject 的ktype的指针
 *
 * 该方法会正确的初始化一个kobject来保证它可以被传递给kobject_add()
 * 调用.
 *
 * 该功能被调用后,kobject必须通过调用kobject_put()来清理,而不是直接
 * 调用kfree,来保证所有的内存都可以被正确的清理。
 */
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
    char *err_str;

    if (!kobj) {
        err_str = "invalid kobject pointer!";
        goto error;
    }
    if (!ktype) {
        err_str = "must have a ktype to be initialized properly!\n";
        goto error;
    }
    if (kobj->state_initialized) {
        /* do not error out as sometimes we can recover */
        printk(KERN_ERR "kobject (%p): tried to init an initialized object, something is seriously wrong.\n", kobj);
        dump_stack();
    }

    kobject_init_internal(kobj);
    kobj->ktype = ktype;
    return;

    error:
    printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
    dump_stack();
}

kobject_init_internal

/* 设置初始化状态 */
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 - 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, ...)
{
    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.\n",
               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;
}


static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
                            const char *fmt, va_list vargs)
{
    int retval;

    retval = kobject_set_name_vargs(kobj, fmt, vargs);
    if (retval) {
        printk(KERN_ERR "kobject: can not set name properly!\n");
        return retval;
    }
    kobj->parent = parent;
    return kobject_add_internal(kobj);
}


/**
 * kobject_set_name_vargs - 设置一个kobject的名称
 * @kobj: 要设置名称的kobject
 * @fmt: 用来构建名称的字符串格式
 * @vargs: 构建字符串的参数
 */
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;
}

API与使用

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

例子:https://blog.csdn.net/yldfree/article/details/84393321

https://www.cnblogs.com/helloahui/p/3674933.html

## Kobject的使用

### 使用流程

kobject是高度抽象的实体,导致**在大多数情况下**,kobject基本上不会单独使用,而是嵌在具体对象中。

其使用流程如下:

1、定义一个`struct kset`类型的指针,并在初始化时为它分配空间,添加到内核中

2、根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有`Kobject`

3、定义一个适合自己的`ktype`,并实现其中回调函数

4、在需要使用到包含`Kobject`的数据结构时,动态分配该数据结构,并分配`Kobject`空间,添加到内核中

5、每一次引用数据结构时,调用`kobject_get`接口增加引用计数;引用结束时,调用`kobject_put`接口,减少引用计数

6、当引用计数减少为0时,`Kobject`模块调用`ktype`所提供的`release`接口,释放上层数据结构以及`Kobject`的内存空间

上面有提过,有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。

### kobject初始化

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

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

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

\```c
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改名,则调用kobject_rename()函数实现:

\```c
int kobject_rename(struct kobject *kobj, const char *new_name);
\```

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

> 有一个函数叫kobject_set_name(),但是它遗留了一些缺陷,目前正在被移除。如果你的代码需要调用这个函数,它是不正确的,需要被修正。

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

\```c
const char *kobject_name(const struct kobject * kobj);
\```

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

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

**创建简单的kobject**

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

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

\```c
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
\```

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

可以通过如下的函数简单创建kobject相关联的属性:

\```c
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
\```

或者

\```c
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
\```

这两种方法都包含了属性类型,和通过`kobject_create_and_add()`创建的kobject类型;属性类型可以直接使用kobj_attribute类型,所以不需要特别创建自定义属性。

### 分配和释放

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

上一篇:总监问我:Kafka 为什么要抛弃 ZooKeeper?


下一篇:10年+,阿里沉淀出怎样的搜索引擎?