qb_2008的专栏

哪怕是被默默注视的我也会有飞翔的勇气

Everything you never wanted to know about kobjects, ksets, and ktypes

    要理解kobject抽象及其之上的设备驱动模型并不简单,难点之一就在于,没有一个明显的起点。要处理好kobject,需要理解一些别的类型,而这些类型又是相互引用的。为了让事情简单,我们采用多遍的过程,从模糊的概念出发,逐渐增加细节。为此,这里先对一些相关概念进行定义:

—kobject 是一个类型为struct object 的对象。kobject有一个名称和一个引用计数,还有一个指向父kobject的指针(允许kobject分层排布),一个特定的类别,通常还有一个在sysfs虚拟文件系统中的显示。

    kobject本身并不是重点,但它经常嵌入在其它结构中,这些被嵌入的结构里,往往有我们感兴趣的内容。

    每个结构不能有超过一个kobject嵌入其中。如果超过了,对该结构的引用计数会混乱掉,代码会有bug。所以不要这样做。

—ktype是一类嵌入在kobject中的对象。每个使用kobject的结构都需要相应的ktype。ktype管理了在kobject创建和销毁时会发生什么。

—kset是一群kobject。这些kobject可以使用同样的ktype,也可以是属于不同 的ktype。kset是容纳多个kobject的基本容器类型。kset有自己的一个kobject,但你可以安全地忽略它,kset的代码会自动管理这个内部的kobject。

    但你看到一个sysfs目录,下面包含有子目录,通常每个子目录就对应一个处在同一kset下的kobject。

    我们下面看如何创建并管理所有这些类型。我们会使用自底向上的方法,所以我们回到kobject。

 

内嵌的kobject

    内核代码很少创建一个单独的kobject,但后面有一个例外情况。kobject主要用于控制更大的、应用指定的对象的访问。所以,kobject经常内嵌到其它对象结构中。如果你习惯用面向对象的方式来思考,kobject可以看成一个顶层的抽象的类,其它的类从这里继承。kobject中集成了一系列对自己没用的能力,但这些功能在继承的类里很有用。c语言不支持直观地使用继承,所以必须使用其它方式完成——例如结构内嵌。

    例如,UIO设备代码定义了一个表示内存范围的结构:

struct uio_mem
{
    struct kobject kobj;
    unsigned long addr;
    unsigned long size;
    int memtype;
    void __iomem *internal_addr;
};

如果你有了一个uio_mem类型的对象,要找到它里面的kobject很简单,只要使用相应的kobj结构成员就行了。但处理kobject的代码往往需要完成相反的过程:给你一个kobject的指针,如何找到指向uio_mem的指针?你需要避免使用一些把戏(例如假设kobject就是uio_mem的第一个结构成员),相反,你应该使用container_of宏,它定义在<linux/kernel.h>中:

container_of(pointer, type, member)


这里的pointer是指向内嵌kobject的指针,type是kobject所在地结构类型,member是结构类型中kobject成员的名字。container_of()的返回值就是kobject所在的结构类型。假设有一个内嵌在uio_mem中的kobject的指针"kp",可以很容易地把它转化成指向uio_mem结构的指针:

struct uio_mem *u_mem = container_of(kp, struct uio_mem, kobj);

程序员往往会定义一个更简单的宏来进行从kobject指针到结构指针的回溯。

 

初始化kobject

    创建kobject的代码同样要初始化kobject。可以调用kobject_init()完成kobject内部的初始化。

void kobject_init(struct kobject *kobj, struct kobj_type *ktype);

    要初始化kobject,需要相应的ktype,因为kobject必须有一个对应的ktype。调用完kobject_init()之后,要调用kobject_add()把kobject注册到sysfs中。

int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);

   kobject_add设定了kobject的父节点,kobject的名字。如果kobject需要和一个特定的kset关联,在调用kobject_add()之前要先对kobj->kset赋值。如果一个kobject与一个kset关联,那它的父节点可以设为NULL,这样kobject的父节点会在调用kobject_add()时自动设为kset内部的kobject。

   因为kobject的名字是在它调用kobject_add()时设定的,会在sysfs中显示,所以不要手动修改kobject名字,而是调用kobject_rename()。

int kobject_rename(struct kobject *kobj, const char *new_name);

    kobject_rename 不会加锁,不会检查name是否有效,所以调用者需要提供名称检查和串行化管理。

    有一个叫做kobject_set_name()的汗水,但已经过时了,准备移除。所以不要使用kobject_set_name。

    为了更好地获取kobject的名字,使用kobject_name()。

const char *kobject_name(const struct kobject *kobj);

    有一个帮助函数,既可以初始化kobject,有可以将其注册到sysfs中,它叫做kobject_init_and_add。

int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);

 

uevent

    一个kobject被注册到sysfs中后,需要向世界宣布它的创建。这需要调用kobject_uevent()。

int kobject_uevent(struct kobject *kobj, enum kobject_action action);

    在kobject加入内核时,先使用KOBJ_ADD action。这一调用需要等kobject的属性和子kobject初始化好之后才行,因为调用之后用户空间会立刻检查kobject的属性和子kobject。

    当kobject从内核中删除时,KOBJ_REMOVE action会被自动发出,所以调用者不需要手动发出KOBJ_REMOVE。

 

引用计数

    kobject的一个重要功能就是充当所嵌入结构的引用计数。只要对象的引用还存在,对象就要存在。管理一个kobject引用计数的低层函数如下:

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

    调用kobject_get()会增加kobject的引用计数,并返回kobject指针。

    当一个引用要被释放,需要调用kobject_put(),并可能要销毁kobject。注意kobject_init()设置计数值为1,所以创建kobject的代码需要调用kobject_put来释放创建时生成的引用。

    因为kobject是动态的,他们不应该被静态声明或者放在堆栈上,而应该动态创建。内核的更高版本会进行运行时检查,对静态创建的kobject予以警告。

    如果你只想用kobject为你的结构提供一个引用计数器,可以用kref替换,kobject太浪费了。

 

创建简单的kobject

    有时开发者只想在sysfs中创建一个简单的目录,而且不想考虑与kset、show函数、store函数,等一系列细节问题。这是就可以创建一个单独的kobject,用下面的函数创建。

struct kobject *kobject_create_and_add(char *name, struct kobject *parent);

    这个函数会创建一个kobject,并把它放在sysfs中parent的子目录中。为了创建这个kobject的相关属性,用以下函数。

int sysfs_create_file(struct kobject *kobj, struct attribute *attr);

int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);

    两种属性都用到了kobject_create_and_add中创建的kobject。
    具体使用简单kobject的例子可以参见samples/kobject/kobject-example.c。

 

ktype和release函数

    我们还没有讨论当kobject引用计数降为零时会发生什么。创建kobject的代码通常不知道引用计数何时会降为零,如果它知道,那使用kobject就没什么意义了。即使是可以预测的对象生命周期,也会在注册到sysfs文件系统后变得更复杂。因为系统的其它部分也可以获得注册到系统中的kobject的引用。

    我们的目的是在kobject的引用计数降为零之前,决不能释放kobject。这个引用计数并非由创建kobject的代码直接控制,所以要在kobject引用计数降为零时异步地通知创建它的代码。一旦你通过kobject_add()把一个kobject注册到系统中,就决不能直接用kfree()释放它。唯一安全的做法是使用kobject_put()。

    这种释放时对创建代码的异步通知是通过kobject的release函数完成的。通常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函数前一定要保持存在状态。否则代码就是有缺陷的。注意,如果代码没有提供一个release函数,内核会提醒你的。不要试图用空的release函数来消除警告;如果你那样做,你最终会被kobject的维护者无情地嘲弄。【作者为了提醒这点已经开始不择手段了】

    注意,在release函数中,kobject的名字仍然是有效的,只是绝不能被更改。不然在kobject中会出现内存泄露。
    有趣的是,release函数指针并不存放在kobject中,而是在ktype中。所以我们下面介绍ktype。

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

    ktype用来表示一类kobject的公共内容。每个kobject都要有一个对应的kobj_type结构,在kobject_init()或者kobject_init_and_add()调用时给出。

    kobj_type中的release函数指针,当然就是指向这类kobject使用的release函数。另外两个部分(sysfs_ops和default_attrs)管理这类kobject在sysfs中如何显示,对它们的讨论超出了本文档的范围。

    default_attrs指针是一个默认属性的链表,这些属性会在这类kobject创建时自动创建。

 

kset

    一个kset是一些kobject的集合,这些kobject通过kset关联在一起。一个kset中的kobject并不要求非得使用同样的ktype,但如果不是,必须加以注意。

    一个kset有如下功能: 

         1) 它作为一个容器,包含一组对象。它可以被内核用来跟踪所有的块设备或者所有的PCI设备驱动。

         2) 一个kset在sysfs中变现为一个分层的目录。每个kset都有一个内部kobject,作为其它kobject的父目录,其余各个kobject都是它的子目录。sysfs的顶层目录结构也是这样组成的。

        3) kset可以支持kobject的热插拔,并且影响uevent事件如何被发布到用户空间。

    从面向对象的角度来讲,kset是顶层类容器。kset也有自己内部的kobject,但这个内部kobject是由kset代码管理的,不允许其它用户直接访问。

    kset用一个标准的链表链接它的子kobject。而子kobject又反过来通过结构中的kset域指向kset。在绝大部分情况下,kobject都属于自己所在的kset,把自己的parent域设为所在kset的内部kobject。

    因为kset包括一个内部kobject,kset必须动态创建,不能静态声明。可以用如下函数创建一个新的kset。

struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *u, struct kobject *parent);

    当你不再使用一个kset,可以用kset_unregister()释放它。

void kset_unregister(struct kset *kset);

    在samples/kobject/kset-example.c中有使用kset的例子。

    如果一个kset想要管理其下kobject的uevent操作,可以使用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中某个特定的kobject向用户控件发送uevent。如果函数结果返回0,则不会发送。

    name函数是用来覆盖uevent发送到用户控件的kset名字。默认情况下,这个名字和kset的名字一样,但如果提供了name函数,这个名字会被覆盖。

    uevent函数在uevent将被发送到用户空间时调用,可以在uevent中添加更多的环境变量。

 

    大家或许会怀疑,一个kobject究竟是如何被加入一个kset中的,没有一个函数进行此项操作。其实这项任务是由kobject_add()完成。当一个kobject参数传给kobject_add()时,它的kset域应该指向所属的kset,其余的工作会由kobject_add()完成。

    如果属于一个kset的kobject没有设置parent域,它会被放在kset目录下。但如果一个kobject设置了parent kobject,虽然它仍属于这个kset,但却放在parent kobject的目录下。

 

  
删除kobject

    如果一个kobject成功地注册到内核中,在代码结束对其操作时必须清除该kobject。可以调用kobject_put()。通过调用kobject_put(),系统会自动释放与该kobject有关的所有内存。如果该kobject发送过一个KOBJ_ADD消息,结束时会对应地发一个KOBJ_REMOVE消息,其它的sysfs管理也会被相应地做好。

    如果你需要一个两段式的kobject删除(例如你不希望在删除kobject时进入睡眠),可以调用kobject_del()将kobject从sysfs中注销。这会使kobject不可见,但还没有被清除,对它的引用计数也不变。之后某个时间需要调用kobject_put()结束该kobject相关的内存清理工作。

    kobject_del()可以用来减少对parent object的引用,特别是在循环引用的时候。父对象引用子对象在某些情况下是合法的。为了打破环状的引用,必须显式地调用kobject_del(),这样才能将计数降为零,调用release函数,清除环中的kobject。

    sample/kobject/kset-example.c是一个使用kobject和kset的简单例子。



  


 

 

 

 




 

阅读更多
个人分类: linux内核学习
想对作者说点什么? 我来说一句

Everything You Know About CSS Is Wrong

2013年11月10日 17.7MB 下载

DjangoDoc.chm(英文)

2009年10月26日 1.63MB 下载

ICPackaging

2009年05月19日 533KB 下载

Data Science for Business

2014年01月22日 7.91MB 下载

Professional WebGL Programming

2014年09月15日 6.11MB 下载

没有更多推荐了,返回首页

不良信息举报

Everything you never wanted to know about kobjects, ksets, and ktypes

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭