14.1. Kobjects, Ksets 和 Subsystems

最后更新于:2022-04-01 02:59:40

## 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 层次")中所见. 记住: **图 14.2. 一个简单的 kset 层次** ![一个简单的 kset 层次](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-02_55e6d9e86f20d.png) - 图表中的所有的被包含的 kobjects 实际上被嵌入在一些其他类型中, 甚至可能其他的 ksets. - 一个 kobject 的 parent 不要求是包含 kset( 尽管任何其他的组织可能是奇怪的和稀少的). #### 14.1.2.2. ksets 之上的操作 对于初始化和设置, ksets 有一个接口非常类似于 kobjects. 下列函数存在: ~~~ void kset_init(struct kset *kset); int kset_add(struct kset *kset); int kset_register(struct kset *kset); void kset_unregister(struct kset *kset); ~~~ 对大部分, 这些函数只是在 kset 的嵌入对象上调用类似的 kobject_ 函数. 为管理 ksets 的引用计数, 情况大概相同: ~~~ struct kset *kset_get(struct kset *kset); void kset_put(struct kset *kset); ~~~ 一个 kset 还有一个名子, 存储于嵌入的 kobject. 因此, 如果你有一个 kset 称为 my_set, 你将设置它的名子用: ~~~ kobject_set_name(&my_set->kobj, "The name"); ~~~ ksets 还有一个指针( 在 ktye 成员 )指向 kobject_type 结构来描述它包含的 kobject. 这个类型优先于在 kobject 自身中的 ktype 成员. 结果, 在典型的应用中, 在 struct kobject 中的 ktype 成员被留为 NULL, 因为 kset 中的相同成员是实际使用的那个. 最后, 一个 kset 包含一个子系统指针(称为 subsys). 因此是时候讨论子系统了. #### 14.1.2.3. 子系统 一个子系统是作为一个整体对内核一个高级部分的代表. 子系统常常(但是不是一直)出现在 sysfs 层次的顶级. 一些内核中的例子子系统包括 block_subsys(/sys/block, 给块设备), devices_subsys(/sys/devices, 核心设备层次), 以及一个特殊子系统给每个内核已知的总线类型. 一个驱动作者几乎从不需要创建一个新子系统; 如果你想这样做, 再仔细想想. 你可能需要什么, 最后, 是增加一个新类别, 如同在"类别"一节中描述的. 一个子系统由一个简单结构代表: ~~~ struct subsystem { struct kset kset; struct rw_semaphore rwsem; }; ~~~ 一个子系统, 因此, 其实只是一个对 kset 的包装, 有一个旗标丢在里面. 每个 kset 必须属于一个子系统. 子系统成员关系帮助建立 kset 的位置在层次中, 但是, 更重要的, 子系统的 rwsem 旗标用来串行化对 kset 的内部链表的存取. 这个成员关系由在 struct kset 中的 subsys 指针所表示. 因此, 可以从 kset 的结构找到每个 kset 的包含子系统, 但是却无法直接从子系统结构发现多个包含在子系统中的 kset. 子系统常常用一个特殊的宏声明: ~~~ decl_subsys(name, struct kobj_type *type, struct kset_hotplug_ops *hotplug_ops); ~~~ 这个宏创建一个 struct subsystem 使用一个给这个宏的名子并后缀以 _subsys 而形成的名子. 这个宏还初始化内部的 kset 使用给定的 type 和 hotplug_ops. ( 我们在本章后面讨论热插拔操作). 子系统有通常的建立和拆卸函数: ~~~ void subsystem_init(struct subsystem *subsys); int subsystem_register(struct subsystem *subsys); void subsystem_unregister(struct subsystem *subsys); struct subsystem *subsys_get(struct subsystem *subsys) void subsys_put(struct subsystem *subsys); ~~~ 大部分这些操作只是作用在子系统的 kset上.
';