统一设备模型:kobj、kset分析

作者:callme_friend 发布于:2018-1-9 18:37 分类:统一设备模型

 

kobj/kset作为统一设备模型的基础,到底提供了哪些功能,在具体应用过程中,如devicebus甚至platform_device等是如何使用kobj/kset的,这是本文的主要阐述内容。

作为阅读wowo相关文章后的笔记,本文纰漏之处,欢迎各位大侠拍砖。

  

1 kobj实现

1.1 kobject

struct kobject {

         const char                  *name;

         struct list_head        entry;

         struct kobject           *parent;

         struct kset                  *kset;

         struct kobj_type       *ktype;

         struct kernfs_node  *sd;

         struct kref                  kref;

         unsigned int   state_initialized:1;

         unsigned int   state_in_sysfs:1;

         unsigned int   state_add_uevent_sent:1;

         unsigned int   state_remove_uevent_sent:1;

         unsigned int uevent_suppress:1;

};

name:对应sysfs的目录名。

entry:用于将kobj挂在kset->list中。

parent:指向kobj的父结构,形成层次结构,在sysfs中表现为父子目录的关系。

kset:表征该kobj所属的ksetkset可以作为parent候补:当注册时,传入的parent为空时,可以让kset来担当。

ktype:该kobj对应的kobj_type。每个kobj或其嵌入的结构对象应该都对应一个kobj_type

sd:对应sysfs对象。在3.14以后的内核中,sysfs基于kernfs来实现。

kref:引用计数对象,支撑kobj的引用计数功能。

state_initialized:1-------------------记录初始化与否。调用kobject_init()后,会置位。

state_in_sysfs:1---------------------记录kobj是否注册到sysfs,在kobject_add_internal()中置位。

state_add_uevent_sent:1--------当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。

state_remove_uevent_sent:1---当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。

uevent_suppress:1-----------------如果该字段为1,则表示忽略所有上报的uevent事件。

1.2 kobj_type

struct kobj_type {

         void (*release)(struct kobject *kobj);

         const struct sysfs_ops *sysfs_ops;

         struct attribute **default_attrs;

         const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);

         const void *(*namespace)(struct kobject *kobj);

};

release:处理对象终结的回调函数。该接口应该由具体对象负责填充。

sysfs_ops:该类型kobjsysfs操作接口。

default_attrs:该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。

child_ns_type/namespace:文件系统命名空间相关,不分析。

2 kset实现

struct kset {

         struct list_head list;

         spinlock_t list_lock;

         struct kobject kobj;

         const struct kset_uevent_ops *uevent_ops;

};

head_list:与kobj->entry对应,用来组织本kset管理的kobj

kobjkset内部包含一个kobj对象。

uevent_opskset用于发送消息的操作函数集。需要指出的是,kset能够发送它所包含的各种子kobj、孙kobj的消息,即kobj或其父辈、爷爷辈,都可以发送消息;优先父辈,然后是爷爷辈,以此类推。 

3 kobj/kset功能特性

3.1对象生命周期管理

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

struct kobject *kobject_get(struct kobject *kobj)

void kobject_put(struct kobject *kobj)

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

而对于kobject_put(),它的实现复杂。其不仅要使用kref_put()接口来对引用计数进行减1操作,还要对生命终结的对象执行release()操作。然而kobject是高度抽象的实体,导致kobject不会单独使用,而是嵌在具体对象中。反过来也可以这样理解:凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求

回到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_release函数,由于bus_release()函数由具体的bus子系统提供,它必定知道如何释放包括kobj在内的bus_type对象。 

3.2sysfs文件系统的层次组织

kobject的另一个功能就是完成sysfs文件系统的组织功能。该功能依靠kobjparentksetsd等成员来完成。sysfs文件系统能够以友好的界面,将kobj所刻画的对象层次、所属关系、属性值,展现在用户空间。 


3.3用户空间事件投递

这方面的内容可参考《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_ksetdevice_uevent_ops。该opsuevent()方法定义如下:

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)

在该opsuevent()方法中,会分别调用busclassdevice typeuevent方法来生成事件消息。 

3.4kobjkset关系总结

kobjkset并不是完全的父子关系kset算是kobj的“接盘侠”,当kobj没有所属的parent时,才让kset来接盘当parent;如果连kset也没有,那该kobj属于顶层对象,其sysfs目录将位于/sys/下。正因为kobjkset并不是完全的父子关系,因此在注册kobj时,将同时对parent及其所属的kset增加引用计数。若parentkset为同一对象,则会对kset增加两次引用计数。

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来向用户空间发送消息;若无关联ksetkobj向上组成的树中,任何成员都无所属的kset),则kobj无法发送用户消息。 

4 ksetkobj的注册总结

4.1 API

kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)

说明:(1kobj->parent = parent;(2kobject_add_internal(kobj)。其中,kobject_add_internal(kobj)会根据kobj.parentkobj.kset来综合决定父目录。详见下面总结。

kset_register(struct kset *k)

说明:直接调用kobject_add_internal()进行注册,然后用kobject_uevent(&k->kobj, KOBJ_ADD)发送KOBJ_ADD事件。

kobject_uevent(struct kobject *kobj, enum kobject_action action)

说明:向用户空间发送事件消息。

4.2总结

关于kobject_add()的总结:

在进行kobj注册时,调用kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)

输入信息kobj对象、kobj-parentkobj-name

kobj,无parent、无kset时,将在sysfs的根目录(/sys/)下创建目录;

kobj,无parent、有kset时,kobject_add()会设置kobj->parentkset->kobj;因此会在该kset下创建目录;该kobj会加入kset.list;同时,会对kobj->kset->kobj增加两次引用:1.增加对kobj->parent的引用;2.增加对kobj->kset的引用。

kobj,有parent、无kset时,kobject_add()会在该parent下创建目录;同时,会对parent增加引用。

kobj,有parent、有kset时,优先在parent下创建目录;该kobj会加入kset.list;同时,会分别对parentkset增加引用计数。

1kobj/kset的引用计数示例

下图展示了一个顶层kobj/kset,通过不断的添加子节点,导致的引用计数变化情况。

1

首先在(I)中建立了顶层对象A,其kref初始化为1;在第(II)步中,建立了A的子对象B,此时A对象的kref引用计数加1变为2;在第(III)步中,继续在A下建立子对象C,此时A对象的kref引用计数加1变为3;在第(IV)步中,建立B对象的子对象D,此时,只会对D的父对象进行引用计数加1;而对更上层的父对象,则不进行引用加1操作。

2platform_bus_type的注册示例

内核启动时,会在初始化阶段进行platform_bus_type总线的注册:bus_register(&platform_bus_type)。在该函数里,会设置platform_bus_type->p->subsys.kobj.kset = bus_ksetplatform_bus_type->p->subsys.kobj.ktype = &bus_ktype,因此按照前面的总结,由于platform_bus_typekset、无parent,导致该bus注册进系统后,其kset引用计数将被增加两次,同时,在bus_kset对应的目录(/sys/bus/)下建立platform_bus_type对应的子目录(/sys/bus/platform/)。同理,当该总线的引用计数为0时,将导致该对象生命的终结,会触发release()操作,显然该操作将导致bus_kset的引用计数减少两次。

5 对外接口的总结

2  示意图如下图所示。

4

  6 小结

l  kobj对应一个目录;

l  kobj实现对象的生命周期管理(计数为0即清除);

l  kset包含一个kobj,相当于也是一个目录;

l  kset是一个容器,可以包容不同类型的kobj(甚至父子关系的两个kobj都可以属于一个kset);

l  注册kobj(如kobj_add()函数)时,优先以kobj->parent作为自身的parent目录;

其次,以kset作为parent目录;若都没有,则是sysfs的顶层目录;

同时,若设置了kset,还会将kobj加入kset.list。举例:

1、无parent、无kset,则将在sysfs的根目录(即/sys/)下创建目录;

2、无parent、有kset,则将在kset下创建目录;并将kobj加入kset.list

3、有parent、无kset,则将在parent下创建目录;

4、有parent、有kset,则将在parent下创建目录,并将kobj加入kset.list

l  注册kset时,如果它的kobj->parent为空,则设置它所属的ksetkset.kobj->kset.kobj)为parent,即优先使用kobj所属的parent;然后再使用kset作为parent。(如platform_bus_type的注册,如某些input设备的注册)

l  注册device时,dev->kobj.kset = devices_kset;然而kobj->parent的选取有优先次序:

3

标签: kset kobj

评论:

renyule
2018-04-18 17:06
似乎明白了点,总结的很棒
callme_friend
2018-04-20 11:05
@renyule:谢谢。一起交流

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog