翻译原文链接: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