你从未想过的关于kobjects,ksets和ktypes的一切
:作者:Greg Kroah-Hartman <gregkh@linuxfoundation.org>
:上次更新时间:2007年12月19日
基于Jon Corbet为2003年10月1日写的lwn.net的原创文章,位于http://lwn.net/Articles/51437/
理解驱动程序模型的难度部分 - 以及构建它的kobject抽象 - 是没有明显的起始位置。学习kobjects需要了解几种不同的类型,所有这些类
型都相互引用。 为了使事情变得更容易,我们将采用多遍方法,从模糊的术语开始,并在我们去的时候添加细节。 为此,这里有一些我们
将要使用的术语的快速定义。
- kobject是struct kobject类型的对象。 Kobjects有一个名字和一个引用计数。 kobject还具有父指针(允许将对象排列成层次结构),特定类型,以及通常在sysfs虚拟文件系统中的表示。
Kobjects本身通常不是很有趣; 相反,它们通常嵌入在包含代码真正感兴趣的东西的其他结构中。
任何结构都不应该嵌入多个kobject。 如果是这样,对象的引用计数肯定会搞砸并且不正确,并且您的代码将是错误的。 所以不要这样做。
- ktype是嵌入kobject的对象类型。 嵌入kobject的每个结构都需要相应的ktype。 ktype控制kobject在创建和销毁时会发生什么。
- kset是一组kobjects。 这些kobjects可以是相同的ktype或属于不同的ktypes。 kset是kobjects集合的基本容器类型。 Ksets包含自己的
kobjects,但是您可以安全地忽略该实现细节,因为kset核心代码会自动处理此kobject。
当您看到一个充满其他目录的sysfs目录时,通常每个目录对应于同一kset中的一个kobject。
我们将看看如何创建和操作所有这些类型。 将采用自下而上的方法,因此我们将回到kobjects。
嵌入kobjects
==================
内核代码很少创建独立的kobject,下面将解释一个主要的例外。 相反,kobjects用于控制对更大的特定于域的对象的访问。 为此,将发现
kobjects嵌入其他结构中。 如果您习惯于以面向对象的方式思考问题,那么可以将kobjects视为一个顶层的抽象类,从中派生出其他类。
kobject实现了一组功能,这些功能对自身并不特别有用,但在其他对象中很有用。 C语言不允许直接表达继承,因此必须使用其他技术 -
例如结构嵌入。
(顺便说一句,对于那些熟悉内核链表实现的人来说,这类似于“list_head”结构对自己很少有用,但总是被发现嵌入到更大的感兴趣对象中。)
因此,例如,drivers/uio/uio.c中的UIO代码具有定义与uio设备关联的内存区域的结构::
struct uio_map {
struct kobject kobj;
struct uio_mem *mem;
};
如果你有一个struct uio_map结构,找到它嵌入的kobject 只是使用kobj成员的问题。 但是,与kobjects一起使用的代码通常会遇到相反的
问题:给定一个struct kobject指针,指向包含结构的指针是什么? 你必须避免使用技巧(例如假设kobject位于结构的开头),而是使用在
<linux / kernel.h>中找到的container_of()宏::
container_of(pointer, type, member)
kobjects的初始化
==========================
当然,创建kobject的代码必须初始化该对象。 一些内部字段设置为(强制)调用kobject_init()::
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
ktype是正确创建kobject所必需的,因为每个kobject必须具有关联的kobj_type。 在调用kobject_init()之后,要使用sysfs注册kobject,
必须调用函数kobject_add()::
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
这将正确设置kobject的parent和kobject的name 。 如果要将kobject与特定kset相关联,则必须在调用kobject_add()之前分配kobj->kset。
如果kset与kobject相关联,那么kobject的parent可以在调用kobject_add()时设置为NULL,然后kobject的父级将是kset本身。
由于kobject的名称在添加到内核时已设置,因此不应直接操作kobject的名称。 如果必须更改kobject的名称,请调用kobject_rename()::
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添加到内核中,它是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之后,您需要向全世界宣布它已被创建。 这可以通过调用kobject_uevent()来完成::
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
当kobject首次添加到内核时,使用KOBJ_ADD操作。 只有在kobject的任何属性或子节点已正确初始化后才能执行此操作,因为当此调用发生
时,用户空间将立即开始查找它们。
当kobject从内核中删除时(下面将详细说明如何操作),KOBJ_REMOVE的uevent将由kobject内核自动创建,因此调用者不必担心手动执行此操作。
也就是说KOBJ_ADD是用户做的,KOBJ_REMOVE是内核自动做的。
引用计数
================
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()以释放该引用。
因为kobjects是动态的,所以它们不能静态地或在堆栈上声明,而是始终动态分配。 未来版本的内核将包含对静态创建的kobject的运行时
检查,并将警告开发人员这种不正确的用法。因此需要注意创建和释放,否则可能造成内存泄漏!
如果您想要使用kobject的所有目的是为您的结构提供引用计数器,请使用struct kref代替; 一个kobject将是杀鸡用牛刀。 有关如何使用
struct kref的更多信息,请参阅Linux内核源代码树中的Documentation/kref.txt文件。
创建“简单”的kobjects
==========================
有时,开发人员想要的只是在sysfs层次结构中创建一个简单目录的方法,而不必费力搞清楚ksets,show和store函数以及其他细节的整个复
杂性。 这是应该创建单个kobject的一个例外。 要创建这样的条目,请使用函数::
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
此函数将创建一个kobject并将其放在sysfs中指定的父kobject下面的位置。 要创建与此kobject关联的简单属性,请使用::
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
or::
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
这里使用的两种类型的属性,使用kobject_create_and_add()创建的kobject,都可以是kobj_attribute类型,因此不需要创建特殊的自定
义属性。有关简单kobject和属性的实现,请参阅示例模块samples/kobject/kobject-example.c。
ktypes和释放方法
==========================
讨论中仍然缺少的一件重要事情是当kobject的引用计数降为零时会发生什么。 创建kobject的代码通常不知道何时会发生; 如果确实如此,
那么首先使用kobject是没有意义的。 当引入sysfs时,甚至可预测的对象生命周期变得更加复杂,因为内核的其他部分可以在系统中注册的
任何kobject上获得引用。
最终结果是,在引用计数变为零之前,不能释放由kobject保护的结构。 引用计数不在创建kobject的代码的直接控制之下。 因此,只要对其
中一个kobjects的最后一次引用消失,代码就必须异步通知。
通过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维护者无情地嘲笑。
注意,kobject的名称在release函数中可用,但不能在此回调中更改。 否则kobject核心会出现内存泄漏,这会让人不高兴。
有趣的是,release()方法没有存储在kobject本身; 相反,它与ktype相关联。 那么让我们来介绍struct 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);
};
该结构用于描述特定类型的kobject(或者更确切地说,包含对象)。 每个kobject都需要有一个相关的kobj_type结构; 调用kobject_init()
或kobject_init_and_add()时,必须指定指向该结构的指针。
struct kobj_type中的release字段当然是指向此类kobject的release()方法的指针。 另外两个字段(sysfs_ops和default_attrs)控制
如何在sysfs中表示此类型的对象; 它们超出了本文档的范围。
default_attrs指针是一个默认的属性列表,将为使用此ktype注册的任何kobject自动创建这些默认的属性文件。
ksets
===============
kset只是想要彼此关联的kobjects的集合。 没有限制它们是相同的ktype,但如果不是,则要非常小心。
kset提供以下功能:
- 它用作包含一组对象的袋子。 内核可以使用kset来跟踪“所有块设备”或“所有PCI设备驱动程序”。
- kset也是sysfs中的子目录,其中可以显示与kset相关联的kobject。 每个kset包含一个kobject,可以设置为其他kobjects的父级;
sysfs层次结构的顶级目录以这种方式构造。
- Ksets可以支持kobjects的“热插拔”,并影响向用户空间报告uevent事件的方式。udev.rules是不是与它相关?
在面向对象的术语中,“kset”是顶级容器类; ksets包含自己的kobject,但该kobject由kset代码管理,不应由任何其他用户操纵。
kset将其子项保存在标准内核链表中。 Kobjects通过他们的kset字段指回他们包含的kset。 在几乎所有情况下,属于kset的kobjects在其父级中具有kset(或严格地说,其嵌入的kobject)。
由于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);
摧毁它。 这将从sysfs中删除kset并减少其引用计数。 当引用计数变为零时,kset将被释放。 由于对kset的其他引用可能仍然存在,因此可能在kset_unregister()返回后发生。
使用kset的一个例子可以在内核源码树中:samples/kobject/kset-example.c
如果kset希望控制与之关联的kobjects的uevent操作,它可以使用struct 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()函数允许kset防止将uevent发送到特定kobject的用户空间。 如果函数返回0,则不会发出uevent。
- name()函数用来覆盖uevent发送给用户空间的kset的默认名称。 默认情况下,名称将与kset本身相同,但此函数(如果存在)可以覆盖该名称。
- uevent()当uevent即将被发送到用户空间以允许将更多环境变量添加到uevent时,将调用uevent()函数。
有人可能会问,如果没有提供执行该功能的函数,kobject是如何添加到kset的。 答案是这个任务由kobject_add()处理。 当kobject传递给kobject_add()时,其kset成员应该指向kobject将属于的kset。
kobject_add()将处理其余的事情。
如果属于kset的kobject没有设置父kobject,它将被添加到kset的目录中。 并非所有kset成员都必须存在于kset目录中。 如果在添加kobject之前显式分配了父kobject,则kobject将注册到kset,但会添加
到父kobject下方。
Kobject删除
===============
kobject已成功注册到kobject核心后,必须在代码完成后清除它。 为此,请调用kobject_put()。 通过这样做,kobject核心将自动清理该kobject分配的所有内存。 如果已为该对象发送了KOBJ_ADD uevent,
则将发送相应的KOBJ_REMOVE uevent,并且将正确处理调用者的任何其他sysfs内务处理。也就是说发了此对象的KOBJ_ADD了才会发送KOBJ_REMOVE!!
如果你需要对kobject进行两阶段删除(比如当你需要销毁对象时不允许你进入睡眠状态),那么调用kobject_del()来取消注册来自sysfs的kobject。 这使得kobject“不可见”,但它没有被清理,并且对
象的引用计数仍然是相同的。 稍后调用kobject_put()来完成与kobject关联的内存的清理。
如果构造了循环引用,则可以使用kobject_del()来删除对父对象的引用。 在某些情况下,父对象引用子对象是有效的。 循环引用_must_将通过显式调用kobject_del()来中断,以便调用释放函数,并
且前一个圆中的对象相互释放。
可以参考的示例代码
=========================
有关正确使用ksets和kobjects的更完整示例,请参阅示例程序samples/kobject/{kobject-example.c,kset-example.c},如果选择CONFIG_SAMPLE_KOBJECT,它将构建为可加载模块。