14.1. Kobjects, Ksets 和 Subsystems
14.1. Kobjects, Ksets 和 Subsystems
Kobject 是基础的结构, 它保持设备模型在一起. 初始地它被作为一个简单的引用计数, 但是它的责任已随时间增长, 并且因此有了它自己的战场. struct kobject 所处理的任务和它的支持代码现在包括:
对象的引用计数
常常, 当一个内核对象被创建, 没有方法知道它会存在多长时间. 一种跟踪这种对象生命周期的方法是通过引用计数. 当没有内核代码持有对给定对象的引用, 那个对象已经完成了它的有用寿命并且可以被删除.
sysfs 表示
在 sysfs 中出现的每个对象在它的下面都有一个 kobject, 它和内核交互来创建它的可见表示.
数据结构粘和
设备模型是, 整体来看, 一个极端复杂的由多级组成的数据结构, 各级之间有许多连接. kobject 实现这个结构并且保持它在一起.
热插拔事件处理
kobject 子系统处理事件的产生, 事件通知用户空间关于系统中硬件的来去.
你可能从前面的列表总结出 kobject 是一个复杂的结构. 这可能是对的. 通过一次看一部分, 但是, 是有可能理解这个结构和它如何工作的.
14.1.1. Kobject 基础
一个 kobject 有类型 struct kobject; 它在 <linux/kobject.h> 中定义. 这个文件还包含许多其他和 kobject 相关的结构的声明, 一个操作它们的函数的长列表.
14.1.1.1. 嵌入的 kobjects
在我们进入细节前, 值得花些时间理解如何使用 kobjects. 如果你回看被 kobjects 处理的函数列表, 你会看到它们都是代表其他对象进行的服务. 一个 kobject, 换句话说, 对其自己很少感兴趣; 它存在仅仅为了结合一个高级对象到设备模型.
因此, 对于内核代码它很少(甚至不知道)创建一个孤立的 kobject; 相反, kobject 被用来控制存取更大的, 特定域的对象. 为此, kobject 被嵌入到其他结构中. 如果你习惯以面向对象的术语考虑事情, kobject 可被看作一个顶级的, 抽象类, 其他的类自它而来. 一个 kobject 实现一系列功能, 这些功能对自己不是特别有用而对其他对象是好的. C 语言不允许直接表达继承, 因此其他的技术 -- 例如将一个结构嵌入另一个 -- 必须使用.
作为一个例子, 让我们回看 struct cdev, 我们在第 3 章遇到过它. 那个结构, 如同在 2.6.10 内核中发现的, 看来如此:
struct cdev {
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
我们可以看出, cdev 结构有一个 kobject 嵌在里面. 如果你有一个这样的结构, 会发现它的嵌入的 kobject 只是使用 kobj 成员. 使用 kobjects 的代码有相反的问题, 但是: 如果一个 struct kobject 指针, 什么是指向包含结构的指针? 你应当避免窍门(例如假定 kobject 是在结构的开始), 并且, 相反, 使用 container_of 宏 (在第 3 章的"open 方法"一节中介绍的). 因此转换一个指向嵌在一个结构 cdev 中的一个 struct kobject 的指针 kp 的方法是:
struct cdev *device = container_of(kp, struct cdev, kobj);
程序员常常定义一个简单的宏来"后向转换" kobject 指针到包含类型.
14.1.1.2. kobject 初始化
本书已经展示了许多数据类型, 带有简单的在编译或者运行时初始化机制. 一个 kobject 的初始化有些复杂, 特别当使用它的所有函数时. 不管一个 kobject 如何使用, 但是, 必须进行几个步骤.
这些步骤的第一个是仅仅设置整个 kobject 为 0, 常常使用一个对 memset 的调用. 常常这个初始化作为清零这个 kobjiect 嵌入的结构的一部分. 清零一个 kobject 失败导致非常奇怪的崩溃, 进一步会掉线; 这不是你想跳过的一步.
下一步是设立一些内部成员, 使用对 kobject_init() 的调用:
void kobject_init(struct kobject *kobj);
在其他事情中, kobject_init 设置 kobject 的引用计数为 1. 调用 kobject_init 不够, 但是. kobject 用户必须, 至少, 设置 kobject 的名子. 这是用在 sysfs 入口的名子. 如果你深入内核代码, 你可以发现直接拷贝一个字符串到 kobject 的名子成员的代码, 但是应当避免这个方法. 相反, 使用:
int kobject_set_name(struct kobject *kobj, const char *format, ...);
这个函数采用一个 printk 风格的变量参数列表. 不管你信或不信, 对这种操作实际上可能失败( 他可能试图分配内存 ); 负责任的代码应当检查返回值并且有针对性的相应.
其他的由创建者应当设置的 kobject 成员, 直接或间接, 是 ktype, kset, 和 parent. 我们在本章稍后到这些.
14.1.1.3. 引用计数的操作
一个 kobject 的其中一个关键函数是作为一个引用计数器, 给一个它被嵌入的对象. 只要对这个对象的引用存在, 这个对象( 和支持它的代码) 必须继续存在. 来操作一个 kobject 的引用计数的低级函数是:
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
一个对 kobject_get 的成功调用递增 kobject 的 引用计数并且返回一个指向 kobject 的指针. 如果, 但是, 这个 kobject 已经在被销毁的过程中, 这个操作失败, 并且 kobject_get 返回 NULL. 这个返回值必须总是被测试, 否则可能导致无法结束的令人不愉快的竞争情况.
当一个引用被释放, 对 kobject_put 的调用递减引用计数, 并且可能地, 释放这个对象. 记住 kobject _init 设置这个引用计数为 1; 因此当你创建一个 kobject, 你应当确保对应地采取 kobject_put 调用, 当这个初始化引用不再需要.
注意, 在许多情况下, 在 kobject 自身中的引用计数可能不足以阻止竞争情况. 一个 kobject 的存在( 以及它的包含结构 ) 可能非常, 例如, 需要创建这个 kobject 的模块的继续存在. 在这个 kobject 仍然在被传送时不能卸载那个模块. 这是为什么我们上面看到的 cdev 结构包含一个 struct module 指针. struct cdev 的引用计数实现如下:
struct kobject *cdev_get(struct cdev *p)
{
struct module *owner = p->owner;
struct kobject *kobj;
if (owner && !try_module_get(owner))
return NULL;
kobj = kobject_get(&p->kobj);
if (!kobj)
module_put(owner);
return kobj;
}
创建一个对 cdev 结构的引用还需要创建一个对拥有它的模块的引用. 因此, cdev_get 使用 try_module_get 来试图递增这个模块的使用计数. 如果这个操作成功, kobject_get 被同样用来递增 kobject 的引用计数. 那个操作可能失败, 当然, 因此这个代码检查自 kobject_get 的返回值并且释放它的对模块的引用如果事情没有解决.
14.1.1.4. 释放函数和 kobject 类型
讨论中仍然缺失的一个重要事情是当一个 kobject 的引用计数到 0 时会发生什么. 创建 kobject 的代码通常不知道什么时候要发生这个情况; 如果它知道, 在第一位使用一个引用计数就没有意义了. 即便当引入 sysfs 时可预测的对象生命周期变得更加复杂; 用户空间程序可保持一个对 kobject 的引用( 通过保持一个它的关联的 sysfs 文件打开 )一段任意的时间.
最后的结果是一个被 kobject 保护的结构无法在任何一个单个的, 可预测的驱动生命周期中的点被释放, 但是可以在必须准备在 kobject 的引用计数到 0 的任何时刻运行的代码中. 引用计数不在创建 kobject 的代码的直接控制之下. 因此这个代码必须被异步通知, 无论何时对它的 kobject 的最后引用消失.
这个通知由 kobject 的一个释放函数来完成. 常常地, 这个方法有一个形式如下:
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 必须有一个释放函数, 并且这个 kobject 必须持续( 以一致的状态 ) 直到这个方法被调用. 如果这些限制不满足, 代码就有缺陷. 当这个对象还在使用时被释放会有风险, 或者在最后引用被返回后无法释放对象.
有趣的是, 释放方法没有存储在 kobject 自身里面; 相反, 它被关联到包含 kobject 的结构类型中. 这个类型被跟踪, 用一个 struct kobj_type 结构类型, 常常简单地称为一个 "ktype". 这个结构看来如下:
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
在 struct kobj_type 中的 release 成员是, 当然, 一个指向这个 kobject 类型的 release 方法的指针. 我们将回到其他 2 个成员( sysfs_ops 和 default_attrs )在本章后面.
每一个 kobject 需要有一个关联的 kobj_type 结构. 易混淆地, 指向这个结构的指针能在 2 个不同的地方找到. kobject 结构自身包含一个成员(称为 ktype)包含这个指针. 但是, 如果这个 kobject 是一个 kset 的成员, kobj_type 指针由 kset 提供. ( 我们将在下一节查看 ksets. ) 其间, 这个宏定义:
struct kobj_type *get_ktype(struct kobject *kobj); finds the kobj_type pointer for a given kobject.
14.1.2. kobject 层次, kset, 和子系统
kobject 结构常常用来连接对象到一个层级的结构中, 匹配正被建模的子系统的结构. 有 2 个分开的机制对于这个连接: parent 指针和 ksets.
在结构 kobject 中的 parent 成员是一个指向其他对象的指针 -- 代表在层次中之上的下一级. 如果, 例如, 一个 kobject 表示一个 USB 设备, 它的 parent 指针可能指示这个设备被插入的 hub.
parent 指针的主要用途是在 sysfs 层次中定位对象. 我们将看到这个如何工作, 在"低级 sysfs 操作"一节中.
14.1.2.1. Ksets 对象
很多情况, 一个 kset 看来象一个 kobj_type 结构的扩展; 一个 kset 是一个嵌入到相同类型结构的 kobject 的集合. 但是, 虽然 struct kobj_type 关注的是一个对象的类型, struct kset 被聚合和集合所关注. 这 2 个概念已被分开以至于一致类型的对象可以出现在不同的集合中.
因此, 一个 kset 的主要功能是容纳; 它可被当作顶层的给 kobjects 的容器类. 实际上, 每个 kset 在内部容纳它自己的 kobject, 并且它可以, 在许多情况下, 如同一个 kobject 相同的方式被对待. 值得注意的是 ksets 一直在 sysfs 中出现; 一旦一个 kset 已被建立并且加入到系统, 会有一个 sysfs 目录给它. kobjects 没有必要在 sysfs 中出现, 但是每个是 kset 成员的 kobject 都出现在那里.
增加一个 kobject 到一个 kset 常常在一个对象创建时完成; 它是一个 2 步的过程. kobject 的 kset 成员必须 ???; 接着kobject 应当被传递到:
int kobject_add(struct kobject *kobj);
如常, 程序员应当小心这个函数可能失败(在这个情况下它返回一个负错误码)并且相应地反应. 有一个内核提供的方便函数:
extern int kobject_register(struct kobject *kobj);
这个函数仅仅是一个 kobject_init 和 kobject_add 的结合.
当一个 kobject 被传递给 kobject_add, 它的引用计数被递增. kset 中容纳的, 毕竟, 是一个对这个对象的引用. 某种意义上, kobject 可能要必须从 kset 中移出来清除这个引用; 完成这个使用:
void kobject_del(struct kobject *kobj);
还有一个 kobject_unregister 函数, 是 kobject_del 和 kobject_put 的结合.
一个 kset 保持它的子女在一个标准的内核链表中. 在大部分情况下, 被包含的 kobjects 也有指向这个 kset 的指针( 或者, 严格地, 它的嵌入 kobject)在它们的 parent 的成员. 因此, 典型地, 一个 kset 和它的 kobjects 看来有些象你在图 一个简单的 kset 层次中所见. 记住:
图 14.2. 一个简单的 kset 层次
更多建议: