翻译原文链接:http://blog.chinaunix.net/uid-20522771-id-3447116.html
内核文档: Documentation/kobject.txt
Everything you never wanted to know about kobjects, ksets, and ktypes
你永远不会想知道的关于 kobject,kset 和 ktype 的一切
Greg Kroah-Hartman
Based on an original article by Jon Corbet for lwn.net written October 1,
2003 and located at http://lwn.net/Articles/51437/
Last updated December 19, 2007
理解那些建立在kobject抽象之上的驱动模型的困难之一就是没有一个明确的入口。
使用kobject需要了解几种不同的类型,而这些类型又会相互引用。为了让这一切变
得简单些,我们将采取多通道方法,以模糊的术语开始,然后逐渐添加细节。为此,
现在给出一些今后会使用到的一些术语的简单定义。
kobject是一个struct kobject类型的对象。kobject包含一个名字和一个
引用计数。同时一个kobject还包含一个父指针(允许对象被安排成层次结构)、
一个特定的类型,通常情况下还有一个在sysfs虚拟文件系统里的表现。
通常我们并不关注kobject本身,而应该关注那些嵌入了kobject的那些结构体。
任何结构体都不允许包含一个以上的kobject。如果这么做,那么引用计数将肯
定会出错,你的代码也将bug百出。所以千万别这么做。ktype是嵌入了kobject的对象的类型。每个嵌入了kobject的对象都需要一个
相应的ktype。ktype用来控制当kobject创建和销毁时所发生的操作。kset是kobject的一组集合。这些kobject可以是同样的ktype,也可以分别
属于不同的ktype。kset是kobject集合的基本容器类型。kset也包含它们自
己的kobject,但是你可以放心的忽略这些kobjects,因为kset的核心代码会
自动处理这些kobject。当你看到一个
sysfs目录里全都是其它目录时,通常每一个目录都对应着一个在
同一个kset里的kobject。
我们来看看如何创建和操作所有的这些类型。因为采用自下而上的方法,所以我们
首先回到kobject。
kobject结构定义如下:
内嵌 kobject
就内核代码而言,基本上不会创建一个单独的kobject,但也有例外,这以后再说。
其实,kobject通常被用来控制一个更大的特定域对象。因此你将发现kobject都被
嵌入到了其他的结构体当中。如果从面向对象的观点出发,那么kobject可以被看做
是被其他类继承的、顶层的、抽象的类。一个kobject实现了一组对于它们自己不是
很有用,但对那些包含了这个kobject的对象很有用的功能。另外 C 语言不支持直接
使用继承,所以必须依靠其他的技术来实现,比如结构体嵌套。
(顺便说一句,对于那些熟悉内核链表实现的同志来说,这和“list_head”结构体很
类似。它们都是本身没什么用,其真正的价值是在嵌套进一个更大的结构体中才得以
体现。)
举一个例子,drivers/uio/uio.c 里的 UIO 代码包含一个定义了内存区域的 uio 设备。
如果你有一个struct uio_map结构体,使用它的成员kobj就能找到嵌套的kobject。
但是操作kobject的代码往往会引出一个相反的问题:如果给定一个struct kobject
指针,那么包含这个指针的结构体又是什么呢?别投机取巧(比如假设这个kobject
是该结构体的第一个字段),你应该使用container_of()宏函数:
其中:
- “
pointer“ 是指向被嵌入的kobject的指针。 - “
type“ 是包含kobject的结构体类型。 - “
member“ 是结构体中”pointer“所指向的字段名。container_of()的返回值就是一个相应结构体的指针。例如,一个指向嵌套在uio_map里的struct kobject的指针“kp”,可以这样获得包含它的结构体的指针:1struct uio_map *u_map = container_of(kp, struct uio_map, kobj);
为方便起见,程序员通常会定义一个简单的宏,用于“反指”包含这个kobject的容器
类型指针。正因为这样,在以前的文件 drivers/uio/uio.c 中你可以看到:
宏参数“map”是一个指向struct kobject的指针。这个宏函数将随后被调用:
kobject 的初始化
创建一个kobject的代码首先必须初始化这个对象。调用kobject_init()来设置一些
内部字段(强制性的):
因为每一个kobject都有一个关联的kobj_type,所以正确创建一个kobject时ktype是必须的。调用kobject_init()之后,必须是用kobject_add()在sysfs上
注册kobject。
这将为kobject设置parent和name。如果这个kobject将关联于一个指定的kset,那么kobj->kset必须在kobject_add()之前被赋值。如果一个ket关联
于一个kobject,那么在调用kobject_add()时parent可以是NULL,这时kobject
的parent将会是这个kset本身。
当一个kobject的name已经被设置并添加至kernel之后,就不允许直接操作这个kobject的name了。如果你必须修改这个name,请使用kobject_rename():
kobject_rename不会执行任何锁定操作,也不会验证name的有效性,所以调用者
必须提供自己的完整性检查和序列化。
这里有一个函数kobject_set_name(),但这个函数是遗留问题,而且在将来会被删
除。如果你的代码需要调用这个函数,那么这将是不正确的并且必须被修正。
要正确访问kobject的name,使用这个函数kobject_name():
这里有一个辅助函数将同时初始化kobject并将其添加至 kernel,kobject_init_and_add():
其中的参数与kobject_init()和kobject_add()里描述的一致。
Uevents
在一个kobject注册到核心(core)之后,你需要通过kobject_uevent()向系统宣布它被创建了。
当kobject首次被添加进 kernel 时,使用KOBJ_ADD动作。这个调用必须在kobject所有
的attributes或children都被正确初始化之后,因为当这个调用发生时,用户空间将会
立即开始寻找它们。
当kobject从 kernel 中移除时,KOBJ_REMOVE的uevent将会被kobject核心(core)
自动创建,所以调用者不必担心手动完成这些。
引用计数
一个kobject的主要功能之一就是在它被嵌入的对象中作为一个引用计数器。只要存
在对该对象的引用,对象(和支持它的代码)就必须继续存在。操作一个kobject的
引用计数的底层函数是:
对kobject_get()的成功调用将递增kobject的引用计数并且返回指向该kobject的指针。
当一个引用被释放时,调用kobject_put()将递减引用计数,并且可能的话,释放对象。
请注意,kobject_init()将引用计数设置为 1,所以在建立kobject的代码里需要执行kobject_put()用于最终释放该引用。
因为kobject是动态的,所以它们不能被声明为静态的或者在栈区分配空间,它们
应该始终被动态分配。未来的 kernel 版本将会包含一个对kobject是否静态创建的
运行时检查,并且会警告开发人员这种不正确的使用。
如果你使用kobject的理由仅仅是使用引用计数的话,那么请使用struct kref替代kobject。更多关于struct kref的信息请参考 Linux 内核文档 Documentation/kref.txt
创建“简单”的 kobject
有时开发人员希望有一种创建一个在 sysfs 层次中简单目录的方式,而并不想搞乱本来
就错综复杂的ksets,show 和store函数,或一些其他的细节。这是一个创建单独的kobjects的一个例外。要创建这样的条目,使用这个函数:
此函数将创建一个kobject,并将其放置在指定的父kobject在 sysfs 中的目录下。
要创建简单的与这个kobject关联的属性,使用函数:
或者
这两种使用kobject_create_and_add()创建的kobject的属性类型都可以是kobj_attribute,所以没有创建自定义属性的需要。
查看示例模块 samples/kobject/kobject-example.c,一个简单的kobject
及其属性的实现。
ktypes 和 release 方法
到目前为止我们遗漏了一个重要的事情,那就是当一个kobject的引用计数达到 0 时将
会发生什么。通常,创建kobject的代码并不知道这种情况何时发生。如果它们知道何
时发生,那么把kobject放在结构体的首位可能会有那么一点点帮助。即使是一个可预
测的对象生命周期也将变得复杂,特别是在 sysfs 作为 kernel 的一部分被引入时,它
可以获取到任意在系统中注册的kobject的引用。
结论就是一个被kobject保护的结构体不能在这个kobject引用计数到 0 之前被释放。
而这个引用计数又不被创建这个kobject的代码所直接控制,所以必须在这个kobject
的最后一个引用消失时异步通知这些代码。
一旦你通过kobject_add() 注册你的kobject之后,永远也不要使用kfree()去释
放直接它。唯一安全的途径是使用kobject_put()。总是在kobject_init()之后使
用kobject_put()是避免错误蔓延的很好的做法。
这个通知是通过kobject的release()函数完成的。该函数通常具有这样的形式:
很重要的一点怎么强调也不过分:每个kobject必须有一个release()方法,而且
在这个方法被调用之前kobject必须继续存在(保持一致的状态)。如果不符合这
些限制,那么代码是有缺陷的。需要注意的是,如果你忘记提供一个release()方法,
kernel 会警告你。不要试图提供一个“空”的release函数来摆脱这个警告,如果你
这样做你会受到kobject维护者们无情的嘲笑。
注意,尽管在release函数中kobject的name是可用的,但是千万不要在这个回调函
数中修改它。否则将会在kobject核心(core)中发生令人不愉快的内存泄露问题。
有趣的是release()方法并没有保存在kobject之中,而是关联在它的ktype
成员中。让我们来介绍struct kobj_type:
这个结构体是用来描述一个特定类型的kobject(或者更确切的说,包含它的对象)。
每个kobject都需要一个关联的kobj_type结构体,当你调用kobject_init()或kobject_init_and_add()时必须指定一个指向kobj_type结构体的指针。
当然struct kobj_type中的release字段就是一个指向同类型对象release()方法的
函数指针。另外两个字段(sysfs_ops和default_attrs)是用来控制这些类型的对象在
sysfs 里是如何表现的,这已经超出了本文的讨论范围。
default_attrs成员指针是一个在任何属于这个ktype的kobject注册时自动创
建的默认属性列表。
ksets
kset仅仅是一个需要相互关联的kobject集合。在这里没有任何规定它们必须是同
样的ktype,但如果它们不是一样的ktype,则一定要小心处理。
一个kset提供以下功能:
- 它就像一个装有一堆对象袋子。
kset可以被 kernel 用来跟踪像“所有的块设备”
或者“所有的 PCI 设备驱动”这样的东西。 - 一个
kset也是一个 sysfs 里的子目录,该目录里能够看见这些相关的kobject。
每个kset都包含一个kobject,这个kobject可以用来设置成其他kobjects
的parent。sysfs 层次结构中的顶层目录就是通过这样的方法构建的。 kset还可以支持kobject的“热插拔”,并会影响uevent事件如何报告给用户空间。
以面向对象的观点来看,“kset” 是一个顶层容器类。kset包含有它们自己的kobject,
这个kobject是在kset代码管理之下的,而且不允许其他任何用户对其操作。
一个kset使用标准的 kernel 链表来保存它的children。kobjects通过它们的kset
字段回指向包含它们的kset。在几乎所有的情况下,属于某kset的kobject的parent都指向这个kset(严格来说是嵌套进这个kset的kobject)。
正是因为一个kset包含了一个kobject,就应该始终动态创建这个kset,千万
不要将其声明为静态的或在栈区分配空间。创建一个kset使用:
当你使用完一个kset时,调用这个函数:
来销毁它。
内核树中的 samples/kobject/kset-example.c 文件是一个
有关于如何使用kset的示例。
如果一个kset希望控制那些与它相关联的kobject的uevent操作,可以使用struct kset_uevent_ops处理。
filter函数允许kset阻止一个特定的kobject的uevent是否发送到用户空间。如果
这个函数返回 0,则uevent将不会被发出。
name函数用来重写那些将被uevent发送到用户空间的kset的默认名称。默认情况下,
这个名称应该和kset本身的名称相同,但如果提供这个函数,则可以改写这个名称。
uevent函数会向即将被发送到用户空间的uevent添加更多的环境变量。
有人可能会问,当一个kobject加入到一个kset 但没有提供完成这些功能的函数时,情况会怎样?
答案就是这些任务将由kobject_add()来完成,当一个kobject传递给kobject_add()函数,它的kset成员必将指向它将被
加入到的kset,然后kobject_add()会帮你干完剩下的活。
如果一个属于某个kset的kobject没有设置它的parent kobject,那么它将被
添加到kset的目录中去。但并不是kset的所有成员都一定存在于kset目录下。
如果在kobject被添加前就指明了它的parent kobject, 那么该kobject将被
注册到这个kset下,然后添加到它的parent kobject下。
kobject 的移除
当一个kobject成功的注册到kobject核心(core)之后,这个kobject必须在代码完成对它的
使用时销毁。要做到这一点,请调用kobject_put()。通过这个调用,kobject 核心(core)会
自动清理所有通过这个kobject分配的内存空间。如果曾经为了这个kobject发送过一个KOBJ_ADD uevent,那么一个相应的KOBJ_REMOVE uevent 将会被发送,并且任何
其他的 sysfs 维护者都将为这个调用作出相应的处理。
如果你需要分两步来删除一个kobject的话(也就是说在你需要销毁一个对象时不允
许 sleep),那么请使用kobject_del()从 sysfs 注销这个kobject。这将使得该kobject “不可见”,但是它并没有被清理,并且它的引用计数也没变。在稍后的时候
调用kobject_put()去完成与这个kobject相关的内存空间的清理。
如果建立了循环引用,kobject_del()可以用来删除指向parent对象的引用。
一些情况下,某个parnet对象引用了它的child是合法的,循环引用必须
显式的通过调用kobject_del() 来打破,这样做之后将会调用一个release函数,
先前在循环引用中的对象才会彼此释放。
示例代码
想要获得更完整的关于正确使用kset和kobject的例子,请参考示例程序
samples/kobject/{kobject-example.c,kset-example.c}。可以通过选择编译条件CONFIG_SAMPLE_KOBJECT来把这些示例编译成可装载模块。
samples/kobject/kobject-example.c
samples/kobject/kset-example.c