分享

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

 lycbje 2013-12-31

    要理解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设备代码定义了一个表示内存范围的结构:

  1. struct uio_mem  
  2. {  
  3.     struct kobject kobj;  
  4.     unsigned long addr;  
  5.     unsigned long size;  
  6.     int memtype;  
  7.     void __iomem *internal_addr;  
  8. };  

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

  1. container_of(pointer, type, member)  


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

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

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

 

初始化kobject

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

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

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

  1. 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()。

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

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

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

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

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

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

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

 

uevent

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

  1. 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引用计数的低层函数如下:

  1. struct kobject *kobject_get(struct kobject *kobj);  
  2. 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,用下面的函数创建。

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

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

  1. int sysfs_create_file(struct kobject *kobj, struct attribute *attr);  
  2.   
  3. 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函数有如下的形式。

  1. void my_object_release(struct kobject *kobj)  
  2. {  
  3.     struct my_object *mine = container_of(kobj, struct my_object, kobj);  
  4.     /*perform any additional cleanup on this object, then... */  
  5.     kfree(mine);  
  6. }  

    有一件事一定要强调:每个kobject一定要有一个release函数,并且这个kobject在调用release函数前一定要保持存在状态。否则代码就是有缺陷的。注意,如果代码没有提供一个release函数,内核会提醒你的。不要试图用空的release函数来消除警告;如果你那样做,你最终会被kobject的维护者无情地嘲弄。【作者为了提醒这点已经开始不择手段了】

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

  1. struct kobj_type {  
  2.     void (*release)(struct kobject *);  
  3.     struct sysfs_ops *sysfs_ops;  
  4.     struct attribute **default_attrs;  
  5. };     

    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。

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

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

  1. void kset_unregister(struct kset *kset);  

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

    如果一个kset想要管理其下kobject的uevent操作,可以使用kset_uevent_ops结构。

  1. struct kset_uevent_ops {  
  2.     int (*filter)(struct kset *kset, struct kobject *kobj);  
  3.     const char *(*name)(struct kset *kset, struct kobject *kobj);  
  4.     int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);  
  5. };  

    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的简单例子。



  


 

 

 

 




  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多