kobject,ksets,ktype

原文:Documenttation\kobject.txt
在理解设备驱动模型上的部分困难-以及kobject建立在抽象上-是因为没有明显的开始地方。对于kobjects需要理解一些不同的类型,所有这些都互相引用。为了让事情变得简单,我们将利用多途径,从模糊的词语开始然后添加细节。最后,是一些快速的词语定义。

-kobject 是struct kobject类型的对象。kobjects有名字引用计数器。kobject同时还有parent指针(允许kobject组成层次结构),ktype,并且通常还有体现在sysfs虚拟文件系统

kobject通常不关心它们自己;相反的,它们通常内嵌到其他结构体内,结构体内其它内容才是真正需要关心的。

结构体不应嵌入多个kobject。如果这样做了,引用计数器会出错,且你的代码是有问题的。因此不能这样做。

-ktype 是内嵌kobject的对象的类型。每个内嵌kobject的结构体都需要一个ktype。ktype控制当前结构体创建或销毁时的行为

-kset 是一组kobject集合.这些kobject可以是相同ktype的或者不同ktype的.kset是一个基本的容器来归集kobject.kset有自己的kobject,但你可以忽略它的实现细节因为kset的核心代码自动处理这个kobject.

当你看到sysfs目录下有很多其它目录,通常这些目录每个都对应一个有同样kset的kobject.

我们来看一下如何创建和操作这些类型.我们采用自下而上法,然后返回到kobject.

内嵌kobject
创建孤立的kobject不多见,只有一个特例后面会提到.反而kobject常用于控制访问更大,特定领域的对象.为此,kobject是嵌入到其它结构体内的.如果用面向对象的方法来说明,kobject可以被看作是一个顶层的抽象类,其它类都继承于它.kobject实现了一系列的功能,这些功能对本身来说没什么用,但它们对其它结构体却很有用.c语言不允许继承,因此必须使用其他方法比如内嵌结构体.
(顺便提一下,熟悉内核链表的人都知道,有一种类似情况,list_head结构体很少单独用,但它们却常常嵌入到其它更大的结构体里.)

比如,UIO代码drivers/uio/uio.c有一个结构,它定义了uio设备相关的内存:

    struct uio_map {
	struct kobject kobj;
	struct uio_mem *mem;
    };

如果你有一个uio_map结构体,找到它内嵌的kobject是很重要的.处理kobject的代码会有相反的问题,然而:有一个kobject的指针如何获取包含它的结构体的指针呢?你必须避免这些招数(比如假设kobject放在在结构体的起始位置),可以使用container_of()宏,定义在<linux/kernel.h>:

container_of(pointer, type, member)

这里:

  • “pointer” 内嵌kobject的指针,
  • “type” 包含kobjiect结构体的类型
  • “member” pointer指向结构体的成员
    container_of()宏返回的是容器的指针,比如,指向kobject的指针"kp"可以转换成容器uio_map的指针:
struct uio_map *u_map = container_of(kp, struct uio_map, kobj);

为了便利,编程者通常定义一个简单的宏用来转换kobject指针到容器指针,drivers/uio/uio.c就是这么做的,如下:

    struct uio_map {
        struct kobject kobj;
        struct uio_mem *mem;
    };

    #define to_map(map) container_of(map, struct uio_map, kobj)

这里宏参数map就是一个指向kobject的指针.这个宏在后面调用:

 struct uio_map *map = to_map(kobj);

kobject初始化
创建kobject的代码必须初始化kobject.一些内部字段必须强制调用kobject_init()设置:

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

ktype对创建kobject是必需的,对每个kobject必需要有一个相关的kobj_type.kobject_init初始化后注册kobject到sysfs必需调用kobject_add()函数:

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

该函数设置kobject的parent和name.如果要设置kset,必须在调用kobject_add之前设置kobj->kset的值.如果kset于kobject关联,在调用kobject_add()的时候设置parent为NULL,这样该kobject的parent就被设置成kset自己了.

由于kobject名字是在注册时加入到内核的,kobject的名字不能直接操作修改.如果必须修改kobject的名字调用函数kobject_name():

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

kobject_rename未执行锁定或确定名字的有效性因此调用者必须自己做合法性检测.
还有个函数叫kobject_set_name()但它是遗留的不完整的已经被移除.如果你的代码需要调用这个函数,是不正确的需要修改.
正确访问kobject名字的函数是kobject_name():

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

有一个函数可以一次完成初始化和注册功能,叫做kobject_init_and_add():

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

参数跟单独使用的kobject_init()和kobject_add()是一样的.

uevents
一旦kobject完成了注册,你需要发布声明表示对象已经创建了.该工作可以调用kobject_uevent()函数来完成:

int kobject_uevent(struct kobject *kobj, enum kobject_action action);

当kobject首次被添加进内核使用KOBJ_ADD参数.在所有attributes或子kobject已经完成正确初始化后调用该函数,用户空间马上就开始搜寻它们.
当kobject从内核移除后(移除方法后面介绍),kobject内核的KOBJ_REMOVE的uevent会自动发生,因此调用者不需要手工处理.

引用次数
kobject一个关键的功能是作为被嵌入对象的引用计数器.只要对该对象的引用还存在,该对象就必须继续存在.底层操作kobject的函数是:

    struct kobject *kobject_get(struct kobject *kobj);
    void kobject_put(struct kobject *kobj);

一次成功的调用kobject_get()会增加kobject的引用次数并返回kobject的指针.

当一个引用释放时,调用kobject_put()将减少引用计数器,也许,释放该对象.注意kobject_init设置引用计数器值为1,因此设置kobject的代码需要调用一次kobject_put()最终释放这个引用.

因为kobject是动态的,它们不能被定义为静态(static)或在堆栈里,反而,应该是动态分配的.将来的内核版本会包含运行时(run-time)检测kobject被静态创建的错误且会发出警告.

对于只想用kobject来做引用计数器的情况,请用kref结构代替;用kobject多余了.如何使用kref结构请参考文档Document/kref.txt.

创建简单的kobjects
有时候开发者想创建一个简单的目录在sysfs里,且不需要复杂的kset,show和store功能,以及其他细节.这是一个例外这里单个kobject可以被创建.为了创建这么一个接口,使用以下函数:

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

该函数将创建一个kobject并添加到sysfs父kobject的目录下.创建kobject相关的简单的attribute,使用:

    int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
or
    int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);

这里使用的两种attribute,和kobject已经调用kobject_create_and_add()创建,也可以是kobj_attribute类型,因此特殊定制的attribute需要被创建.查看例子模块关于kobject和attribute的实现方法,samples/kobject/kobject-example.c

ktypes和释放方法
还有一家重要的事还没讨论到,那就是当引用计数器减到0.创建kobject的代码通常不知道这情况什么时候发生;如果这样,那就不重要了在使用kobject创建的地方.甚至可以预见的生命周期的都变得非常复杂引入sysfs以后,因为其他注册进系统的对象多会引用它.

结果就是包含kobject的对象不能释放,直到引用计数器归零.引用计数器不在创建的地方控制,因此当引用移除后必须通知到代码.

一旦你通过kobject_add()注册了kobject,你不能直接使用kfree()释放.唯一安全的做法是调用kobject_put().好的习惯是在kobject_init()后调用kobject_put()来避免错误.
通知是通过kobject的release()函数处理的.通常该函数是这样定义的:

    void my_object_release(struct kobject *kobj)
    {
    	    struct my_object *mine = container_of(kobj, struct my_object, kobj);

	    /* Perform any additional cleanup on this object, then... */
	    kfree(mine);
    }

有一点再强调也不过分:每个kobject必须有一个release函数,且kobject必须持续(以一贯的状态)直到该函数被调用.如果这些约束条件不满足,代码是有缺陷的.注意内核会提醒你如果你没写release函数.不要想提供个空函数来消除这些告警.你会被嘲笑的如果这样做.

注意.kobject的名字在release函数中可以获取,但不能修改.否则会到账内存泄漏.

有趣的是release函数不在kobject结构体,而是在ktype结构体.下面来介绍一下ktype结构体:

    struct kobj_type {
	    void (*release)(struct kobject *);
	    const struct sysfs_ops *sysfs_ops;
	    struct attribute	**default_attrs;
    };

该结构体用来描述kobject的类型(或者更准确的说是保护kobject的结构体).每个kobject都需要有一个相关的ktype.当你调用kobject_init()和kobject_init_and_add()时都必须指定一个ktype的指针.另两个字段 (sysfs_ops
and default_attrs) 控制该对象如何在sysfs系统中的显示;它们超出了本文档的范畴.

任何带ktype类型的kobject默认的attribute会被自动创建.

ksets
kset只不过是互相关联kobject的集合.但不要求是有相同的ktype,但如果不同则要非常小心.

A kset serves these functions:
kset有这些功能:

  • 它作为一个包含kobject的包.kset能被内核用来追踪"所有块设备"或"所有PCI驱动"

  • kset同时也是sysfs下的一个子目录,在这个目录里呈现和kset相关的kobject.每个kset自身有一个kobject用来作为其它kobject的parent;sysfs的顶层目录就是这么组织的.

  • kset支持kobject的热插拔并且影响uevent事件如何通知到用户空间

在面向对象术语里,"kset"是顶层容器类;kset有自己的kobject,但该kobject由kset的代码来管理并且是不能被其它用户操作的.

kset使用内核标准的链表来管理它的孩子.kobjects的kset指向他们的kset容器.大多数情况,kobject归属于一个kset,并把它作为自己的parent.

kset包含一个kobject,它应该被动态创建并且不能被声明为静态或者在堆栈里,要创建一个新的kset使用以下方法:

  struct kset *kset_create_and_add(const char *name,
				   struct kset_uevent_ops *u,
				   struct kobject *parent);

当你完成kset的任务后,调用:

void kset_unregister(struct kset *kset);

来销毁它
kset的例子请参考samples/kobject/kset-example.c
kset控制归属它的kobject的uevent使用kset_uevent_ops来处理:

struct kset_uevent_ops {
        int (*filter)(struct kset *kset, struct kobject *kobj);
        const char *(*name)(struct kset *kset, struct kobject *kobj);
        int (*uevent)(struct kset *kset, struct kobject *kobj,
                      struct kobj_uevent_env *env);
};

filter 函数设置uevent不发送到用户空间,如果函数返回0,则该uevent不会被发送到用户空间.

name函数调用来覆盖kset默认发送到用户空间的名字.默认的名字是kset的名字,但是该函数可以用来覆盖该名字

uevent函数在uevent发送到用户空间时被调用,可以增加更多环境变量到uevent.

有人可能会问如果没有提供执行该功能的函数,kobject是如何加入到kset的.答案就是该任务由kobject_add()函数来处理.当kobject调用kobject_add(),它的kset会指向kobject所属的kset.kobject_add()会处理余下的工作.
如果kobject没有设置parent,它会被添加到kset目录,不是所有的kset成员都需要在kset目录,如果设置了直接的parent,kobject会被注册到kset但却是在parent目录下.

Kobject 移除
kobject成功注册后,结束时必须编写代码清理.可以调用kobject_put()函数.这样做kobject核心代码会自动清理分配给kobject的内存.如果KOBJ_ADDuevent事件被发送给对象,一个相应的KOBJ_REMOVE uevent会被发送,然后sysfs会正确地处理.

如果你需要分两阶段删除kobject(你不允许在销毁kobject的时候执行sleep),调用kobject_del()会把kobject从sysfs注销.但这只是让kobject不科技,并没有清理掉,引用计数器也没有变化.稍后调用kobject_put()来完成清理与kobject相关的内存.

kobject_del()能被用于终止对父对象的引用,如果构建的是环形引用.在有些时候是合法的,父对象引用子对象.环形引用必须调用kobject_del()来终止,以便调用release函数,在环形引用的对象互相释放.

可以复制的例子代码

For a more complete example of using ksets and kobjects properly, see the
example programs samples/kobject/{kobject-example.c,kset-example.c},
which will be built as loadable modules if you select CONFIG_SAMPLE_KOBJECT.

猜你喜欢

转载自blog.csdn.net/qq_36412526/article/details/83790558
今日推荐