您的位置:首页 > 移动开发 > Objective-C

Everything you never wanted to know about kobjects, ksets, and ktypes

2012-07-10 17:52 429 查看
要理解kobject抽象及其之上的设备驱动模型并不简单,难点之一就在于,没有一个明显的起点。要处理好kobject,需要理解一些别的类型,而这些类型又是相互引用的。为了让事情简单,我们采用多遍的过程,从模糊的概念出发,逐渐增加细节。为此,这里先对一些相关概念进行定义:

—kobject 是一个类型为struct object 的对象。kobject有一个名称和一个引用计数,还有一个指向父kobject的指针(允许kobject分层排布),一个特定的类别,通常还有一个在sysfs虚拟文件系统中的显示。

kobject本身并不是重点,但它经常嵌入在其它结构中,这些被嵌入的结构里,往往有我们感兴趣的内容。

每个结构不能有超过一个kobject嵌入其中。如果超过了,对该结构的引用计数会混乱掉,代码会有bug。所以不要这样做。

—ktype是一类嵌入在kobject中的对象。每个使用kobject的结构都需要相应的ktype。ktype管理了在kobject创建和销毁时会发生什么。

—kset是一群kobject。这些kobject可以使用同样的ktype,也可以是属于不同 的ktype。kset是容纳多个kobject的基本容器类型。kset有自己的一个kobject,但你可以安全地忽略它,kset的代码会自动管理这个内部的kobject。

但你看到一个sysfs目录,下面包含有子目录,通常每个子目录就对应一个处在同一kset下的kobject。

我们下面看如何创建并管理所有这些类型。我们会使用自底向上的方法,所以我们回到kobject。

内嵌的kobject

内核代码很少创建一个单独的kobject,但后面有一个例外情况。kobject主要用于控制更大的、应用指定的对象的访问。所以,kobject经常内嵌到其它对象结构中。如果你习惯用面向对象的方式来思考,kobject可以看成一个顶层的抽象的类,其它的类从这里继承。kobject中集成了一系列对自己没用的能力,但这些功能在继承的类里很有用。c语言不支持直观地使用继承,所以必须使用其它方式完成——例如结构内嵌。

例如,UIO设备代码定义了一个表示内存范围的结构:

[cpp]
view plaincopyprint?

struct uio_mem
{
struct kobject kobj;
unsigned long addr;
unsigned long size;
int memtype;
void __iomem *internal_addr;
};

如果你有了一个uio_mem类型的对象,要找到它里面的kobject很简单,只要使用相应的kobj结构成员就行了。但处理kobject的代码往往需要完成相反的过程:给你一个kobject的指针,如何找到指向uio_mem的指针?你需要避免使用一些把戏(例如假设kobject就是uio_mem的第一个结构成员),相反,你应该使用container_of宏,它定义在<linux/kernel.h>中:

[cpp]
view plaincopyprint?

container_of(pointer, type, member)

这里的pointer是指向内嵌kobject的指针,type是kobject所在地结构类型,member是结构类型中kobject成员的名字。container_of()的返回值就是kobject所在的结构类型。假设有一个内嵌在uio_mem中的kobject的指针"kp",可以很容易地把它转化成指向uio_mem结构的指针:

[cpp]
view plaincopyprint?

struct uio_mem *u_mem = container_of(kp, struct uio_mem, kobj);

程序员往往会定义一个更简单的宏来进行从kobject指针到结构指针的回溯。

初始化kobject

创建kobject的代码同样要初始化kobject。可以调用kobject_init()完成kobject内部的初始化。

[cpp]
view plaincopyprint?

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

要初始化kobject,需要相应的ktype,因为kobject必须有一个对应的ktype。调用完kobject_init()之后,要调用kobject_add()把kobject注册到sysfs中。

[cpp]
view plaincopyprint?

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

kobject_add设定了kobject的父节点,kobject的名字。如果kobject需要和一个特定的kset关联,在调用kobject_add()之前要先对kobj->kset赋值。如果一个kobject与一个kset关联,那它的父节点可以设为NULL,这样kobject的父节点会在调用kobject_add()时自动设为kset内部的kobject。

因为kobject的名字是在它调用kobject_add()时设定的,会在sysfs中显示,所以不要手动修改kobject名字,而是调用kobject_rename()。

[cpp]
view plaincopyprint?

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

kobject_rename 不会加锁,不会检查name是否有效,所以调用者需要提供名称检查和串行化管理。

有一个叫做kobject_set_name()的汗水,但已经过时了,准备移除。所以不要使用kobject_set_name。

为了更好地获取kobject的名字,使用kobject_name()。

[cpp]
view plaincopyprint?

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

有一个帮助函数,既可以初始化kobject,有可以将其注册到sysfs中,它叫做kobject_init_and_add。

[cpp]
view plaincopyprint?

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

uevent

一个kobject被注册到sysfs中后,需要向世界宣布它的创建。这需要调用kobject_uevent()。

[cpp]
view plaincopyprint?

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

在kobject加入内核时,先使用KOBJ_ADD action。这一调用需要等kobject的属性和子kobject初始化好之后才行,因为调用之后用户空间会立刻检查kobject的属性和子kobject。
当kobject从内核中删除时,KOBJ_REMOVE action会被自动发出,所以调用者不需要手动发出KOBJ_REMOVE。

引用计数

kobject的一个重要功能就是充当所嵌入结构的引用计数。只要对象的引用还存在,对象就要存在。管理一个kobject引用计数的低层函数如下:

[cpp]
view plaincopyprint?

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

调用kobject_get()会增加kobject的引用计数,并返回kobject指针。
当一个引用要被释放,需要调用kobject_put(),并可能要销毁kobject。注意kobject_init()设置计数值为1,所以创建kobject的代码需要调用kobject_put来释放创建时生成的引用。
因为kobject是动态的,他们不应该被静态声明或者放在堆栈上,而应该动态创建。内核的更高版本会进行运行时检查,对静态创建的kobject予以警告。
如果你只想用kobject为你的结构提供一个引用计数器,可以用kref替换,kobject太浪费了。

创建简单的kobject

有时开发者只想在sysfs中创建一个简单的目录,而且不想考虑与kset、show函数、store函数,等一系列细节问题。这是就可以创建一个单独的kobject,用下面的函数创建。

[cpp]
view plaincopyprint?

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

这个函数会创建一个kobject,并把它放在sysfs中parent的子目录中。为了创建这个kobject的相关属性,用以下函数。

[cpp]
view plaincopyprint?

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

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

两种属性都用到了kobject_create_and_add中创建的kobject。

具体使用简单kobject的例子可以参见samples/kobject/kobject-example.c。

ktype和release函数

我们还没有讨论当kobject引用计数降为零时会发生什么。创建kobject的代码通常不知道引用计数何时会降为零,如果它知道,那使用kobject就没什么意义了。即使是可以预测的对象生命周期,也会在注册到sysfs文件系统后变得更复杂。因为系统的其它部分也可以获得注册到系统中的kobject的引用。
我们的目的是在kobject的引用计数降为零之前,决不能释放kobject。这个引用计数并非由创建kobject的代码直接控制,所以要在kobject引用计数降为零时异步地通知创建它的代码。一旦你通过kobject_add()把一个kobject注册到系统中,就决不能直接用kfree()释放它。唯一安全的做法是使用kobject_put()。
这种释放时对创建代码的异步通知是通过kobject的release函数完成的。通常release函数有如下的形式。

[cpp]
view plaincopyprint?

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函数前一定要保持存在状态。否则代码就是有缺陷的。注意,如果代码没有提供一个release函数,内核会提醒你的。不要试图用空的release函数来消除警告;如果你那样做,你最终会被kobject的维护者无情地嘲弄。【作者为了提醒这点已经开始不择手段了】
注意,在release函数中,kobject的名字仍然是有效的,只是绝不能被更改。不然在kobject中会出现内存泄露。

有趣的是,release函数指针并不存放在kobject中,而是在ktype中。所以我们下面介绍ktype。

[cpp]
view plaincopyprint?

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

ktype用来表示一类kobject的公共内容。每个kobject都要有一个对应的kobj_type结构,在kobject_init()或者kobject_init_and_add()调用时给出。
kobj_type中的release函数指针,当然就是指向这类kobject使用的release函数。另外两个部分(sysfs_ops和default_attrs)管理这类kobject在sysfs中如何显示,对它们的讨论超出了本文档的范围。
default_attrs指针是一个默认属性的链表,这些属性会在这类kobject创建时自动创建。

kset

一个kset是一些kobject的集合,这些kobject通过kset关联在一起。一个kset中的kobject并不要求非得使用同样的ktype,但如果不是,必须加以注意。
一个kset有如下功能:
1) 它作为一个容器,包含一组对象。它可以被内核用来跟踪所有的块设备或者所有的PCI设备驱动。
2) 一个kset在sysfs中变现为一个分层的目录。每个kset都有一个内部kobject,作为其它kobject的父目录,其余各个kobject都是它的子目录。sysfs的顶层目录结构也是这样组成的。
3) kset可以支持kobject的热插拔,并且影响uevent事件如何被发布到用户空间。
从面向对象的角度来讲,kset是顶层类容器。kset也有自己内部的kobject,但这个内部kobject是由kset代码管理的,不允许其它用户直接访问。
kset用一个标准的链表链接它的子kobject。而子kobject又反过来通过结构中的kset域指向kset。在绝大部分情况下,kobject都属于自己所在的kset,把自己的parent域设为所在kset的内部kobject。
因为kset包括一个内部kobject,kset必须动态创建,不能静态声明。可以用如下函数创建一个新的kset。

[cpp]
view plaincopyprint?

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

当你不再使用一个kset,可以用kset_unregister()释放它。

[cpp]
view plaincopyprint?

void kset_unregister(struct kset *kset);

在samples/kobject/kset-example.c中有使用kset的例子。
如果一个kset想要管理其下kobject的uevent操作,可以使用kset_uevent_ops结构。

[cpp]
view plaincopyprint?

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函数用来阻止kset中某个特定的kobject向用户控件发送uevent。如果函数结果返回0,则不会发送。
name函数是用来覆盖uevent发送到用户控件的kset名字。默认情况下,这个名字和kset的名字一样,但如果提供了name函数,这个名字会被覆盖。
uevent函数在uevent将被发送到用户空间时调用,可以在uevent中添加更多的环境变量。

大家或许会怀疑,一个kobject究竟是如何被加入一个kset中的,没有一个函数进行此项操作。其实这项任务是由kobject_add()完成。当一个kobject参数传给kobject_add()时,它的kset域应该指向所属的kset,其余的工作会由kobject_add()完成。
如果属于一个kset的kobject没有设置parent域,它会被放在kset目录下。但如果一个kobject设置了parent kobject,虽然它仍属于这个kset,但却放在parent kobject的目录下。



删除kobject

如果一个kobject成功地注册到内核中,在代码结束对其操作时必须清除该kobject。可以调用kobject_put()。通过调用kobject_put(),系统会自动释放与该kobject有关的所有内存。如果该kobject发送过一个KOBJ_ADD消息,结束时会对应地发一个KOBJ_REMOVE消息,其它的sysfs管理也会被相应地做好。
如果你需要一个两段式的kobject删除(例如你不希望在删除kobject时进入睡眠),可以调用kobject_del()将kobject从sysfs中注销。这会使kobject不可见,但还没有被清除,对它的引用计数也不变。之后某个时间需要调用kobject_put()结束该kobject相关的内存清理工作。
kobject_del()可以用来减少对parent object的引用,特别是在循环引用的时候。父对象引用子对象在某些情况下是合法的。为了打破环状的引用,必须显式地调用kobject_del(),这样才能将计数降为零,调用release函数,清除环中的kobject。
sample/kobject/kset-example.c是一个使用kobject和kset的简单例子。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: