LDD3学习笔记设备模型之底层实现(kobject,kset)

来源:互联网 发布:沈阳师范大学软件楼 编辑:程序博客网 时间:2024/05/24 08:34

写在前面的一些话:LDD3使用的内核版本是2.6.11,很多数据结构随着内核版本的改变也发生了变化。我是依据自己系统的内核版本来学习的。所以在笔记中的一些数据结构及一些函数发生变化时不要惊讶,它不是一个bug,呵呵。

尽管我们的出发点在底层,但我们有必要先来粗略了解一下高层的视图。

linux设备模型的基本组成结构:


类型所包含的内容对应内核数据结构对应/sys项设备(Devices)设备是此模型中最基本的类型,以设备本身的连接按层次组织struct device/sys/devices/*/*/.../设备驱动(Device Drivers)在一个系统中安装多个相同设备,只需要一份驱动程序的支持struct device_driver/sys/bus/pci/drivers/*/总线类型(Bus Types)在整个总线级别对此总线上连接的所有设备进行管理struct bus_type/sys/bus/*/设备类别(Device Classes)这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下struct class/sys/class/*/

说明:任何设备要与系统交互,必须通过总线来接连,设备本身要工作又必须要有设备驱动的支持,把设备分门另类,可以更好地组织,更迅速地处理设备。

为了把这些基本组成结构组织成统一的设备模型,内核在真正实现时是通过kobject和kset这两个底层的数据结构来完成。

先上一个图来说明这它们的关系

kobject是一种数据结构(include/linux/kobject.h):

view plaincopy to clipboardprint?
  1. 59struct kobject {  
  2. 60        const char              *name;  
  3. 61        struct list_head        entry;  
  4. 62        struct kobject          *parent;  
  5. 63        struct kset             *kset;  
  6. 64        struct kobj_type        *ktype;  
  7. 65        struct sysfs_dirent     *sd;  
  8. 66        struct kref             kref;  
  9. 67        unsigned int state_initialized:1;  
  10. 68        unsigned int state_in_sysfs:1;  
  11. 69        unsigned int state_add_uevent_sent:1;  
  12. 70        unsigned int state_remove_uevent_sent:1;  
  13. 71        unsigned int uevent_suppress:1;  
  14. 72};  

在理解这些数据结构成员的时候要与设备模型提供的任务联系起来理解,因为它们就是设备模型的底层实现,代表着设备模型实现任务。下面要介绍的kset也是一样的。

其中 struct kref 内含一个 atomic_t 类型用于引用计数,对对象生命周期进行控制。 parent 是指向父节点的指针,实现设备模型的分层结构 entry 用于父 kset 以链表头结构将 kobject 结构维护成双向链表;name是对象的名字。

嵌入的kobject


  一个kobject对自身关不感兴趣,它存在的意义在于把高级对象连接到设备模型上。高级对象包括:总线,设备,设备驱动等。要把它们抽象起来,组织成一个模型,把必须要有一个中间物,这个中间物就是kobject。为此,kobject总是被嵌入到这些高级对象中,很少有一个单独的kobject对象。理解这个对于理解设备模型很重要,具体的参考LDD3的说明。

  针对kobject的函数代表嵌入它的其他对象去完成任务。

kobject的初始化

  对kobject的初始化有一些步骤是必须的:

  1、将整个kobject设置为0。通常使用memset函数,而且通常是在分配包含kobject的结构时进行的。如果忘记对kobject的清零,以后使用它有可能会发生一些奇怪的错误,所以这一步是必须的。

  2、调用kobject_init()函数,以便设置结构内部的一些成员。随着内核版的更新,这个函数也发生了变化,但其所做的工作基本上没发生什么变化。请自己查看kobject_init()的实现。它所做的一件事情是设置引用计数为1。现在,这个函数的样子是:

view plaincopy to clipboardprint?
  1. extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);  

多了个ktype参数,关于kob_type后面再说。

3、设置kobject的名字,这是在sysfs入口中使用的名字。这个函数有内核文档说它已经被移除,但这个函数还是存在着。也许这步已经不再是初始化必须了。

view plaincopy to clipboardprint?
  1. extern int kobject_set_name(struct kobject *kobj, const char *name, ...)                         __attribute__((format(printf, 2, 3)));  

kobject的添加

  kobject初始化之后就可以注册到sysfs文件系统下了。

view plaincopy to clipboardprint?
  1. extern int __must_check kobject_add(struct kobject *kobj,  
  2.                                     struct kobject *parent,  
  3.                                     const char *fmt, ...);  

关于这个函数的进一步解释,讲到sysfs再进一步说明。

对引用计数的操作

  设备模型一个重要的作用就是对对象生命周期的跟踪,这通过kobject的引用计数来实现。只要对象的引用计数存在(不为0),对象(以及支持它的代码)就必须继续存在。底层控制引用计数的函数有:

view plaincopy to clipboardprint?
  1. struct kobject *kobject_get(struct kobject *kobj);增加引用计数  
  2. void kobject_put(struct kobject *kobj); 减少引用计数  

kobject_get的成功调用将增加引用计数,并且返回指向kobject的指针。必须检查返回值,因为该调用有可能会失败,而且是在kobject已经牌销毁的时候调用时失败,不检查返回值会产生竞态。当引用被释放时,调用kobject_put减少引用计数,并在可能的情况下(引用计数为0)释放该对象。注意,kobject_init设置了引用计数为1,当创建kobject时,如果不再需要初始化的引用必须调用kobject_put减少引用计数,否则对象不能释放,因为引用计数的存在,对象就必须继续存在。

release函数和kobject类型

  kobject要从内核中释放其引用计数必须为0,当然引用计数为0时不一定采取行动,但要释放必须是引用计数为0。那引用计数为0的时候,kobject将采取什么操作呢?深入到kobject_put函数,我们可以发现,其不单减少引用计数,还测试引用计数,检查其是否为0,如果为0则调用一个release函数。也就是说,当引用计数为0时会引发调用kobject的release函数。有一个通用的原型如下:

view plaincopy to clipboardprint?
  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方法,其实它是嵌入在kobj_type结构里。接下来我们就来理解这个struct kobj_ytpe结构:

view plaincopy to clipboardprint?
  1. struct kobj_type {  
  2.         void (*release)(struct kobject *kobj);  
  3.         struct sysfs_ops *sysfs_ops;  
  4.         struct attribute **default_attrs;  
  5. };  

每一个kobject都需要有一个相应的kobj_type结构。其中的release成员就是在kobject引用计数为0的时候需要调用的函数。其他两个成员分别是对象的属性,以及属性的实现方法。对它们的介绍我们再往后推一下。有一个宏可以方便地获取kobj_type结构:

view plaincopy to clipboardprint?
  1. struct kobj_type *get_ktype(struct kobject *kobj)  

kobject层次结构kset

  内核用kobject结构将各个对象连接起来组成一个分层的结构体系,让设备模型有层次感。有两种独立的机制用于连接:parent指针和kset。

  kobject结构的parent成员保存了另一个kobject结构的指针,这个结构表示了分层结构的上一层节点。对parent指针最重要的用途是在sysfs分层结构中定位对象。

kset

kset像是kobj_type结构的扩充。一个kset是嵌入相同类型结构的kobject的集合。kobj_type结构关心的是对象的类型,而kset结构关心的是对象的集合。

view plaincopy to clipboardprint?
  1. 154struct kset {  
  2.  155        struct list_head list;  
  3.  156        spinlock_t list_lock;  
  4.  157        struct kobject kobj;  
  5.  158        struct kset_uevent_ops *uevent_ops;  
  6.  159};  

每一个kset内部,包含了自己的kobject,实际上处理kset的函数基本上就是处理其kset成员。

kset总是在sysfs中出现,一旦设置了kset并把它们添加到系统中,将在sysfs中创建一个目录。kobject不必在sysfs中表示,但kset中的每一个kobject成员都将在sysfs中得到表述。

创建一个对象时,通常要把一个kobject添加到kset中去。实现这个过程有两个步骤:

1、先把kobject的kset成员指向目的kset

2、调用kobject_add函数。

我们还可以调用下面这个函数一步到位:

view plaincopy to clipboardprint?
  1. 88extern int __must_check kobject_init_and_add(struct kobject *kobj,  
  2.  89                                             struct kobj_type *ktype,  
  3.  90                                             struct kobject *parent,  
  4.  91                                             const char *fmt, ...);  
  5.  92  

但手动设置kobject的kset必不可少。

有添加就有删除,删除kset中的kobject用下面这个函数:

view plaincopy to clipboardprint?
  1. void kobject_del(struct kobject *kobj);  

kset上的操作

kset拥有与kobject相似的初始化和设置接口。这些函数是:

view plaincopy to clipboardprint?
  1. void kset_init(struct kset *kset);  
  2. int kset_add(struct kset *kset);  
  3. int kset_register(struct kset *kset);  
  4. void kset_unregister(struct kset *kset);  
  5. struct kset *kset_get(struct kset *kset);  
  6. void kset_put(struct kset *kset);  
  7. kobject_set_name(&my_set->kobj, "The name");  

这些函数只是对kset中的kobject结构调用类似前面kobject_函数。

总的来说,kset就是集合了嵌入相同结构类型的kobject的集合,操作它大部分就是操作它的kobject成员。

在新的内核里, kset 不再包含一个子系统指针struct subsystem * subsys, 而且subsystem已经被kset取代。

低层sysfs操作

  kobject和kset和sysfs关系甚密。在之前我们一直避而不谈sysfs是为了在这里统一地理解它。设备模型的一个重要的任务就是与用户空间通信,而这个任务的实现就是通过sysfs。关于sysfs的知识在LDD3里说得比较简单,如果学习LDD3关于sysfs部分理解比较困难,那还是有必要通过搜索引擎来学习一下sysfs。在这里我们只学习LDD3里提到的东西。

说到底sysfs是一种虚拟文件系统,内核通过它实现与用户空间的交互。

kobject 是在 sysfs 虚拟文件系统后的机制。对每个在 sysfs 中的目录, 在内核中都会有一个 kobject 与之对应。每个 kobject 都输出一个或多个属性, 它在 kobject 的 sysfs 目录中以文件的形式出现, 其中的内容由内核产生。 <linux/sysfs.h>  包含 sysfs 的工作代码。
在 sysfs 中创建kobject的入口是kobject_add的工作的一部分,只要调用 kobject_add 就会在sysfs 中显示,还有些知识值得记住:
1)kobjects 的 sysfs 入口始终为目录, kobject_add 的调用将在sysfs 中创建一个目录,这个目录包含一个或多个属性(文件);
(2)分配给 kobject 的名字( 用 kobject_set_name ) 是 sysfs 中的目录名,出现在 sysfs 层次的相同部分的 kobjects 必须有唯一的名字. 分配给 kobjects 的名字也应当是合法的文件名字: 它们不能包含非法字符(如:斜线)且不推荐使用空白。
(3)sysfs 入口位置对应 kobject 的 parent 指针。若 parent 是 NULL ,则它被设置为嵌入到新 kobject 的 kset 中的 kobject;若 parent 和 kset 都是 NULL, 则sysfs 入口目录在顶层,通常不推荐。

默认属性

  当创建kobject的时候,都会给每个kobject一系列默认属性。这些属性保存在kobj_type结构中。之前我们也提到过,其他的两个与属性有关的成员我们没有讲,在这里我们重点理解一下。

view plaincopy to clipboardprint?
  1. 107struct kobj_type {  
  2. 108        void (*release)(struct kobject *kobj);  
  3. 109        struct sysfs_ops *sysfs_ops;  
  4. 110        struct attribute **default_attrs;  
  5. 111};  

default_attrs成员保存了属性列表,每一个该类型的kobject也一起拥有这些属性。sysfs_ops提供了实现这些属性的方法。最值得注意的是default_attrs链表中的最后一个元素必须用NULL填充,因为判断属性结构数组的结尾是NULL,如果没有用NULL填充,那么就会发生数组越界,造成无法预知的错误。

非默认属性

  默认属性是在kobject被创建时就在sysfs下以文件的形式创建了,我们可以根据需要对kobject内的属性进行添加和删除。添加新的属性,首先要填写一个attribute结构,然后把它传递给下面的函数:

view plaincopy to clipboardprint?
  1. int sysfs_create_file(struct kobject *kobj, struct attribute *attr);  

新添加的属性调用同一show()和store()函数,必须采取必要的步骤让这些函数知道如何实现新添加进来的属性。

调用下面的函数删除属性:

view plaincopy to clipboardprint?
  1. int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);  

二进制属性

   LDD3没有源代码也没举具体的例子说明,我也带过不学。

符号链接

  没看。

热插拔事件的产生

  一个热插拔事件是从内核空间发送到用户空间的通知,它表明系统配置出现了变化。

热插拔操作

  对热插拔事件的实际控制,是由保存在kset中的kset_uevent_ops *uevent_ops结构中的函数完成的。LDD3用的内核版本为kset_hotplug_ops,在新的内核版本里改成了kset_uevent_ops。

view plaincopy to clipboardprint?
  1. 120struct kset_uevent_ops {  
  2.  121        int (*filter)(struct kset *kset, struct kobject *kobj);  
  3.  122        const char *(*name)(struct kset *kset, struct kobject *kobj);  
  4.  123        int (*uevent)(struct kset *kset, struct kobject *kobj,  
  5.  124                      struct kobj_uevent_env *env);  
  6.  125};  

若在 kobject 中不包含指定的 kset , 内核将通过 parent 指针在分层结构中进行搜索,直到发现一个包含有kset的 kobject ; 接着使用这个 kset 的热插拔操作。

kset_uevent_ops 结构中的三个方法作用如下:
(1) filter 函数让 kset 代码决定是否将事件传递给用户空间。如果 filter 返回 0,将不产生事件。LDD3以块设备子系统为例,毕竟函数发生了变化,我不知道现在那个例子的源代码是否改变,所以这里不引用那个例子了。

(2) 当调用用户空间的热插拔程序时,相关子系统的名字将作为唯一的参数传递给它。name 函数负责返回合适的字符串传递给用户空间的热插拔程序。

(3)热插拔脚本想得到的任何其他参数都通过环境变量传递。uevent 函数的作用是在调用热插拔脚本之前将参数添加到环境变量中。函数原型:

view plaincopy to clipboardprint?
  1. int (*uevent)(struct kset *kset, struct kobject *kobj,  
  2.                       struct kobj_uevent_env *env);  

热插拔事件讲得比较粗糙,其实我自己也搞得不是很清楚,所以不敢多说多写。

内核文档里有一个kobject.txt说明文件是学习kobject和kset不错的资料,并且内核源代码还提供了单独实现kobject和kset的源代码。它们分别在内核源代码根目录下的sample目录里。为了弄清kobject和kset,这些源代码很值得研究。

由于本人自己的掌握理解有限,加上本来这设备模型这章讲述的东西比较抽象复杂,学习起来相对困难,如果