linux kernel kobject之禅

翻译自: https://lwn.net/Articles/51437/

目录

背景

嵌入kobjects

kobjects的初始化

Reference counts

连接到sysfs

ktypes 和 release方法

ksets

subsystem

Kobject initialization again

Looking forwar


背景

“kobject”结构首先出现在2.5.45开发内核中。它最初的意思是统一管理引用计数对象的内核代码的简单方法。然而,他后来遇到了一点“任务蠕变”;现在,它是将大部分设备模型及其sysfs接口结合在一起的粘合剂。对于一个驱动工程师,直接与kobjects合作是很少见的;这是因为它们通常隐藏在由高级代码创建的结构中。然而,Kobjects有一定的倾向于通过中间层泄漏,并使其存在为人所知。因此,熟悉它们是什么以及它们是如何工作的是一件好事。本文档将介绍kobject类型和相关主题,也会介绍kobject和Sysf之间的大多数交互(稍后将单独介绍)。

理解驱动程序模型——以及它所基于的kobject抽象——的部分困难在于没有明显的起点。处理kobjects需要理解几种不同的类型,所有这些类型都相互引用。为了让事情变得更简单,我们将采取多步骤的方法,从模糊的术语开始,并在进行时添加细节。为此,以下是一些我们将使用的术语的快速定义。

  •         kobject是struct kobject类型的对象。Kobjects有一个名字和一个引用计数。kobject还有一个父指针(允许将kobject排列成层次结构-继承关系)、一个特定的类型,或许还有一个在sysfs虚拟文件系统中的表示。

        Kobjects本身通常并不有趣;相反,它们通常嵌入到其他一些结构中,其中包含代码真正感兴趣的内容。

  • ktype是与kobject关联的类型。ktype控制当一个kobject不再被引用时会发生什么,以及该kobject在sysfs中的默认表示形式。
  • kset是一组kobjects,它们都嵌入在同一类型的结构中。kset是kobjects集合的基本容器类型。KSet包含它们自己的kobjects,不管它值多少钱。除此之外,这意味着一个kobject的父对象通常是包含它的kset,尽管事情通常不必如此。 

        当您看到一个sysfs目录中满是条目时,通常每个条目都对应于同一个kset中的一个kobject。

  • 子系统是KSet的集合,它们共同构成内核的主要子部分。子系统通常对应于sysfs中的顶级目录。

我们将了解如何创建和操作所有这些类型。我们将采用自下而上的方法,所以我们将回到kobjects。

嵌入kobjects

        内核代码很少(甚至未知)创建独立的kobject;相反,kobjects用于控制对更大的、特定于域的对象的访问。为此,kobjects将嵌入其他结构中。如果您习惯于用面向对象的术语来思考问题,那么kobjects可以被视为一个顶级抽象类,其他类都是从该类派生的。kobject实现了一组功能,这些功能本身并不是特别有用,但在其他对象中很好。C语言不允许直接表达继承,因此必须使用其他技术,例如结构嵌入。

因此,例如,struct cdev的2.6.0-test6版本(描述字符设备的结构)是:

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

如果您有一个struct cdev结构,那么查找其嵌入的kobject只需使用kobj指针即可。然而,与kobjects一起工作的代码通常会有相反的问题:给定一个struct kobject指针,指向包含该结构的指针是什么?应该避免使用技巧(例如假设kobject位于结构的开头),而是使用<linux/kernel.h>中的 container_of()宏:

container_of(pointer, type, member)

其中,pointer是指向嵌入式kobject的指针,type是包含结构的类型,member是指针指向的结构字段的名称。container_of()的返回值是指向给定类型的指针。因此,例如,指向嵌入在名为“kp”的结构cdev中的结构kobject的指针可以转换为指向包含该结构的指针:

struct cdev *device = container_of(kp, struct cdev, kobj);

程序员通常会定义一个简单的宏来“反向转换”指向包含类型的kobject指针。

kobjects的初始化

        当然,创建kobject的代码必须初始化该对象。一些内部字段是通过(强制)调用kobject_init()设置的。

void kobject_init(struct kobject *kobj);

kobject_init()将kobject的引用计数设置为1。然而调用kobject_init()是不够的。Kobject用户必须至少设置Kobject的名称;这是将在sysfs条目中使用的名称。如果你深入研究内核源代码,你会发现代码直接将字符串复制到kobject的name字段中,但是这种方法应该避免。相反,请使用:

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

他的函数采用printk风格的变量参数列表。信不信由你,这次行动实际上有可能失败;认真的代码应该检查返回值并做出相应的反应。

        创建者应直接或间接设置的其他kobject字段是其ktype、kset和父字段。我们很快就会讨论这些问题。

Reference counts

kobject的关键功能之一是充当嵌入它的对象的Reference counts。只要存在对该对象的引用,该对象(以及支持它的代码)就必须继续存在。用于操纵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 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()的返回值,并在出现问题时释放对模块的引用。

连接到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())。

ktypes 和 release方法

         讨论中仍然缺少的一件重要事情是,当kobject的引用计数为零时,它会发生什么。创建kobject的代码通常不知道什么时候会发生;如果确实如此,使用kobject就没有什么意义了。引入sysfs后,即使是可预测的对象生命周期也变得更加复杂;用户空间程序可以在任意时间段内保持对kobject的引用(通过保持其一个相关联的sysfs文件处于打开状态)。 最终结果是,受kobject保护的结构在其引用计数变为零之前无法释放。引用计数不受创建kobject的代码的直接控制。因此,每当对其中一个Kobject的最后一次引用消失时,必须异步通知代码。他的通知是通过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本身中;相反,它与ktype关联。让我们介绍一下struct kobj_类型:

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结构的扩展;Kset是相同对象的集合。但是,虽然struct kobj_type关注对象的类型,但struct kset关注聚合和集合。这两个概念是分开的,因此相同类型的对象可以出现在不同的集合中。

kset具有以下功能:

  • 它就像一个袋子,里面装着一组完具有相同行为的对象。内核可以使用kset来跟踪“所有块设备”或“所有PCI设备驱动程序”。
  • kset是将设备模型和sysfs固定在一起的目录级粘合剂。每个kset包含一个kobject,可以将其设置为其他kobject的父对象;通过这种方式,构建了设备模型层次结构。
  • Ksets可以支持kobjects的“热插拔”,并影响如何向用户空间报告热插拔事件。

在面向对象的术语中,“kset”是顶级容器类;KSET继承自己的kobject,也可以作为kobject处理。

kset将其子项保存在标准内核链表中。Kobjects通过其kset字段指向其包含的kset。在几乎所有情况下,包含的kobject在其父字段中也有一个指向kset(或者严格地说是其嵌入的kobject)的指针。因此,通常情况下,一个kset和它的kobjects看起来像下图中所示。

请记住:

        (1)图中包含的所有kobjects实际上都嵌入到其他类型中,

甚至可能嵌入到其他kset中;        

        (2)不要求kobjects的父对象是包含kset的。

 对于初始化和设置,kset有一个与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上类似的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, "The name");

Kset还有一个指向kobj_type结构的指针(在ktype字段中),该结构描述了它所包含的Kobject。此类型将应用于任何不包含指向其自身kobj_type结构的指针的kobject。==》 给所有子 object 提供一个通用的kobj_type。

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

有人可能会问,如果没有提供执行该功能的函数,那么kobject究竟是如何添加到kset的。答案是这个任务由kobject_add()处理。当一个kobject被传递给kobject_add()时,它的kset成员应该指向该kobject所属的kset。kobject_add()将处理其余的问题。目前,没有其他方法可以在不直接干扰链表指针的情况下将kobject添加到kset。

最后,kset包含一个subsystem指针(称为subsys)。所以现在必须谈谈subsystem。

subsystem

subsystem是内核作为一个整体的高级部分的表示。它实际上是一个简单的结构:

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

因此,子系统实际上只是一个kset的包装器。事实上并不是那么简单;单个子系统可以包含多个KSet。这个包含由struct kset中的subsys指针表示;因此,如果一个子系统中有多个KSET,就不可能直接从子系统结构中找到所有KSET。

每个kset必须属于一个子系统;子系统的rwsem信号用于序列化对kset内部链表的访问。

子系统通常用一个特殊的宏来声明:

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

该宏只创建一个结构子系统(其名称是附加了_subsys的宏的名称),内部kset用给定的类型和hotplug_ops初始化。

子系统具有通用的setup 和 teardown 函数:

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 and k_name - the name of the object. These fields should always be initialized with kobject_set_name().
  • refcount is the kobject's reference count; it is initialized by kobject_init()
  • parent is the kobject's parent in whatever hierarchy it belongs to. It can be set explicitly by the creator. If parent is NULL when kobject_add() is called, it will be set to the kobject of the containing kset.
  • kset is a pointer to the kset which will contain this kobject; it should be set prior to calling kobject_add().
  • ktype is the type of the kobject. If the kobject is contained within a kset, and that kset has a type set in its ktype field, then this field in the kobject will not be used. Otherwise it should be set to a suitable kobj_type structure.

通常,kobject的大部分初始化都由管理包含的kset的层处理。因此,回到我们以前的示例,char驱动程序可能会创建一个struct cdev,但它不必担心在嵌入的kobject中设置任何字段——除了名称。其他一切都由char设备层处理。

Looking forward

到目前为止,我们已经介绍了用于设置和操作kobjects的操作。核心概念相对简单:kobjects可用于(1)维护对象的引用计数,并在不再使用该对象时进行清理,以及(2)通过kset成员身份创建分层数据结构。

到目前为止,缺少的是kobjects如何在用户空间中表现自己。kobjects的sysfs接口使向用户空间导出信息(以及从用户空间接收信息)变得容易。sysf的符号链接特性允许跨不同的kobject层次结构创建指针。请继续关注这一切是如何运作的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值