kobject之禅翻译

简介

  kobject 出现在2.5.45的开发版内核当中,最早想要简单的用来实现对象的引用计数,但是后来遭遇了“mission creep”,成了sysfs和具体设备的胶水。对于驱动开发者而言,与kobjects直接交互的情况实际上很少,它们在高层次代码中已经被抽象起来了,不过在中间层,还是有直接使用的kobject倾向的,所以kobject还是被大家知道了,了解他是什么、怎么工作还是有必要的。本文将覆盖到kobject的类型定义和相关的主题,但不会提到太多kobjects与sysfs的关系。

  为了更好的理解kobject,这里将采取多道次(multi-pass)的方法,从一些模糊的词汇开始,然后再逐渐添加细节。以下介绍几个重要的定义:

  • kobject 是一个结构体。kobject有一个名字name和引用的数目reference count,有一个父指针parent pointer(如此可以建立一个kobjet的层次结构),有一个表示具体类型变量,或许还有一个sysfs的虚拟文件系统的表示。
    kobject 本身其实并没有趣,有趣的是嵌入kobject的其他代码。
  • ktype 是与kobject相关联的类型,ktype将在kobject没有引用之后对其进行处理,以及在sysfs系统的默认默认表示
  • kset 是保存所有具有相同类型的kobject的集合,它是kobject的基础集合。kset同样可以保存kset,这表示一个kobject的parent经常会是一个包含它的kset,即使一般不会这样。
    sysfs中各种各样的entries(目录),这些目录一般将在一个kset之下。
  • subsystem是一个kset的集合,它将构成内核的主要子模块。子系统一般对应着sysfs顶层的目录。也就是sysfs下每一个目录都是一个subsystem。

接下来将去查看如何创建和操作这些类型。这里将采取一个自底向上的方式进行。所以现在回到kobject。

嵌入在其他结构中的kobjects(Embedding kobjects)

  在内核代码中几乎不存在独立存在的kobject创建需求,而是通过存在的kobjects去控制一个更大的、特定范围的object。所以,kobject经常被发现嵌入在其它的结构里。如果你熟悉面向对象思考的风格,那么kobjects可以被视为在最顶层,作为抽象类被其他类继承。kobject自己实现的功能并没有多少作用,而是在被其他其他对象使用时才有价值。C语言当然不支持继承,所以其他技术被用来实现功能,其实就是通过结构嵌入(组合)来完成。

例如,在2.6.0-test6版本中的struct dev,以下这个数据结构表示一个字符设备:

struct cdev{
	struct kobject kobj;
	struct module *owner;
	struct file_operations *ops;
	struct list_head list;
}

在已经有一个cdev结构的对象下,找到它内部嵌入的内容其实就是通过kobj指针完成。利用kobjects在代码中将经常产生一些负面作用:比如有了一个kobject的指针,怎么找到包含这个指针的结构?同时也需要避免一个误区(假设kobject是结构的开始),可以通过使用一个container_of这个宏,可以<linux/kernel.h>中发现:

container_of(pointer, type, member)

该宏中pointer指嵌入在其中的kobject指针,type是指包含这个kobject的类型,memberkobject指针所指的数据结构中具体域的类型名字(如果指向的是kobject,那这里就是数据结构中kobject类型的名字kobj)。最后,该宏返回的结果就是指向type的指针。
例如,有一个指针指向kobject嵌入在cdev中,叫做kp,这里可以通过container_of获得包含kp的数据结构。

container_of(kp, struct cdev, kobj)

编程过程中将时常使用到这个简单的宏完成向前转型(back-casting)。

kobject的初始化

创建kobject必须对其进行初始化,需要调用kobject_init()方法,当然方法内部有委托调用该方法。

void kobject_init(struct kobject *kobj);

除了别的方法之外,kobject_init()方法将kobject的初始引用设置为1。然而调用完kobject_init()方法其实还是不够的,Kobject 的使用者还得为kobject至少设置一个名字,这个名字将会在sysfs当中被使用。如果大家在去深挖内核代码会发现kobject将会有一个名字直接复制过来到name域。作为name域修改方式:

int kobject_set_name(struct kobject *kobj, const char *format, ...);

这个函数接受printk风格的变量列表。此外Believe it or not,这个操作有可能会失败,仔细的代码需要检查返回。
其他kobject中的属性要么是直接要么间接的被赋值,通过其创造者比如ktypekset,和其parent

引用计数Reference counts

kobject的一个核心功能就是作为其嵌入的对象的引用计数,只要引用存在,对象就一直存在。操纵引用计数底层的函数为:

struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);

kobect_get()成功执行后,将kobect的引用计数加1并且返回kobject指针。然而如果kobject所在的进程被摧毁了,这个操作会失败并返回空,所以该函数返回值必须检查。

当一个引用被释放后,kobject_put()会被调用并将引用计数减1,如果为0了会释放object。注意kobject_init()会将引用计数设为1,所以初始化这个kobject的代码最后需要kobejct_put()释放kobject。

注意在很多情况下,kobject的引用计数可能不会避免的了竞争(也就是多线程不安全),kobject的存在可能正常。例如,当需要一个持续存在的内核模块,这个模块创建了kobject。不会卸载这个模块,同时kobject又在到处传递。这就是为什么cdev结构包含了module的指针,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),所以cdev_get使用了try_module_get去尝试增加模块的使用计数,如果成功,kobject_get就回去增加kobject的引用计数,如果失败,则检查kobj的值并释放模块的引用。

与sysfs绑定

初始化的kobject将毫无问题地执行引用计数,但不会出现在sysfs中。 要创建sysfs条目,内核代码必须将对象传递给kobject_add():

     int kobject_add(struct kobject * kobj);

此操作也可能会失败,需要检查。

     void kobject_del(struct kobject * kobj);

顾名思义,将从sysfs中删除kobject。

有一个kobject_register函数,它实际上只是对kobject_init和kobject_add的调用的组合。 同样,kobject_unregister将调用kobject_del,然后调用kobject_put释放使用kobject_register或kobject_init创建的kobject。

ktypes和release方法

讨论中仍然遗漏的重要一件事是,当kobject的引用计数达到零时会发生什么。创建kobject的代码通常不知道何时会发生。如果这样做的话,首先使用kobject毫无意义。当引入sysfs时,甚至可预测的对象生命周期也变得更加复杂。用户空间程序可以在任意时间段内保持对kobject的引用(通过使其关联的sysfs文件之一保持打开状态)。
最终结果是,受kobject保护的结构在其引用计数变为零之前无法释放。引用计数不受创建kobject的代码的直接控制。因此,只要最后一个对其kobject的引用消失,该代码就必须异步通知。

该通知是通过kobject的release()方法完成的。通常,这种方法的形式如下:

void my_object_releasestruct(kobject * kobj)
{
	struct my_object * mine = container_of(kobj,struct my_object,kobj);

	/ *对这个对象执行任何其他清理,然后... * /
	kfree(mine);
}

有一点非常重要:每个kobject必须具有release()方法,并且kobject必须持久(处于一致状态),直到调用该方法为止。如果不满足这些约束,则代码有缺陷。

有趣的是,release方法没有存储在kobject本身中;相反,它与ktype关联。因此,让我们介绍struct kobj_type:

struct kobj_type {
	void (*release)(struct kobject *);
	struct sysfs_ops	*sysfs_ops;
	struct attribute	**default_attrs;
};

此结构用于描述特定类型的kobject(或更正确地说,是包含对象)。每个kobject需要具有关联的kobj_type结构;可以在初始化时将指向该结构的指针放置在kobject的ktype字段中,或者(更有可能)可以由kobject的包含kset定义它。

当然,struct kobj_type中的release字段是指向此类kobject的release()方法的指针。其他两个字段(sysfs_ops和default_attrs)控制在sysfs中如何表示这种类型的对象。它们超出了本文档的范围。

ksets

在许多方面,kset看起来像是kobj_type结构的扩展;但是,虽然struct kobj_type本身与对象的类型有关,但struct kset与集合有关。这两个概念已经分开,因此相同类型的对象可以出现在不同的集合中。
kset具有以下功能:

  • 它用作容纳一组相同(类似)kobject的袋子。内核可以使用kset来存储“所有块设备”或“所有PCI设备驱动程序”。
  • kset是将设备模型(和sysfs)保持在一起的目录级粘合。每个kset都包含一个kobject,可以将其设置为其他kobject的父对象。以这种方式构造了设备模型层次结构。
  • Ksets可以支持kobject的“热插拔”,并影响将热插拔事件报告给用户空间的方式。

用面向对象的术语来说,“ kset”是顶级容器类。 kset继承自己的kobject,也可以视为kobject。

kset将其孩子(kobject)保留在标准内核链接列表中。 Kobject通过其kset字段指向包含它的kset。在几乎所有情况下,所包含的kobject在其parent字段中还具有指向kset(或严格来说是其嵌入的kobject)的指针。因此,通常,一个kset及其kobject看起来类似于您在下图中所看到的。

kobject And kset structure
为消除歧义,上图要注意的两点有:
(1)所有嵌入的kobject可能嵌入了其它类型,不仅是kset;
(2)并不是kobject的父对象一定为kset。

对于初始化和设置,kset具有与kobject非常相似的接口。存在以下功能:

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_型函数的近似函数。

为了管理kset的引用计数,情况大致相同:

struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);

kset也具有一个名称,该名称存储在嵌入的kobject中。因此,如果您有一个名为my_set的kset,则可以使用以下命令设置其名称:

kobject_set_name(my_set-> kobj,“名称”);

Kset还具有指向kobj_type结构的指针(在ktype字段中),该结构描述了其中包含的kobject。此类型将应用于不包含指向其自身kobj_type结构的指针的任何kobject。

kset的另一个属性是一组热插拔操作。只要有kobject进入或离开kset,就会调用这些操作。他们能够确定是否为此更改生成了用户空间热插拔事件,并可以影响该事件的显示方式。热插拔操作超出了本文档的范围;稍后将与sysfs讨论它们。

假设没有提供执行该功能的功能,则可能会问如何将kobject准确地添加到kset。答案是此任务由kobject_add处理。将kobject传递给kobject_add时,其kset成员应指向该kobject所属的kset。 kobject_add将处理其余部分。当前没有其他方法可以将kobject添加到kset中而不直接弄乱列表指针。

最后,一个kset包含一个子系统指针(称为subsys)。因此,现在该是讨论子系统的时候了。

子系统

子系统代表整个内核的高层部分。它实际上是一个简单的结构体:

struct subsystem {
	struct kset		kset;
	struct rw_semaphore	rwsem;
};

因此,子系统实际上只是围绕kset的包装。实际上,并不是那么简单。一个子系统可以包含多个kset。这是由struct kset中的subsys指针表示。因此,如果子系统中有多个kset,则不可能直接从subsystem中找到所有的kset。

每个kset必须属于一个子系统。子系统的rwsem信号量用于控制对kset内部链接列表的连续访问。

子系统通常使用特殊的宏声明:

decl_subsys(char *name, struct kobj_type *type, 
			struct kset_hotplug_ops *hotplug_ops);

这个宏只是创建一个结构子系统(name加上_subsys就会是subsys的名字,type是内部kset初始化的需要,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的操作。

Kobject initialization again

现在我们已经涵盖了所有这些内容,我们可以详细讨论如何为kobject在内核中的存在做好准备。这是所有必须以某种方式初始化的struct kobject字段:

  • name和k_name-对象的名称。这些字段应始终使用kobject_set_name()初始化。
  • refcount是kobject的引用计数;它由kobject_init()初始化
  • parent是kobject所属层次结构中的父对象。可以由创建者显式设置。如果在调用kobject_add()时parent为NULL,它将被设置为包含kset的kobject。
  • kset是指向将包含此kobject的kset的指针;应该在调用kobject_add()之前设置它。
  • ktype是kobject的类型。如果kobject包含在kset中,并且该kset在其ktype字段中设置了类型,则不会使用kobject中的该字段。否则,应将其设置为合适的kobj_type结构。

通常,kobject的大部分初始化是由管理包含的kset的层处理的。因此,回到我们的旧示例,字符驱动程序可以创建一个结构cdev,但不必担心设置嵌入式kobject中的任何字段(名称除外)。其他所有内容都由字符设备层处理。

Looking forward

到目前为止,我们已经介绍了用于设置和操作kobject的操作。 核心概念相对简单:kobjects可用于(1)维护对象的引用计数并在不再使用该对象时清除,以及(2)通过kset成员资格创建分层数据结构。
到目前为止,缺少的是kobject如何在用户空间中表示自己。 到kobjects的sysfs接口可以很容易地将信息导出到用户空间(以及从用户空间接收信息)。 sysfs的符号链接功能允许跨不同的kobject层次结构创建指针。 请继续关注所有工作原理的描述。

原文: The zen of kobject

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章