1. Tekkaman:​​http://blog.chinaunix.net/space.php?uid=20543672&do=blog&id=94312​
  2. 《Linux那些事儿 之 我是Sysfs》:
  3. ​://hi.baidu.com/csdeny/blog/item/5a4f14f342d30dca0a46e038.html​
  4. 还有国嵌的实验代码,对我理解kobject帮助很大,一并表示感谢


设备模型这一块相对来说比较复杂,不管是LDD3还是宋宝华的书,讲的都很抽象,我以前也看了好几遍,都是云里雾里,没有理出个头绪(是偶智商低么?

)。ldd3认为设备模型这一块属于高级教材,对于多数程序来说是不必要的,但我实际工作中发现,虽然设备模型内核已经处理的很好,几乎不需要改动,但正确理解其工作机理,对认知Linux驱动,几乎可以说是个质的提升。so,还是静下心来,吧这个部分好好梳理一下,采用的方法是自下而上,先弄清楚最基本的概念,(以前方法相反,效果很杯催...)。

Linux 2.6内核的一个重要特色是提供了统一的内核设备模型。随着技术的不断进步,系统的拓扑结构越来越复杂,对智能电源管理、热插拔以及plug and play的支持要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,2.6内核开发了全新的设备模型。


1.sysfs


"sysfs is a ram-based filesystem initially based on ramfs. It provides a means


to export kernel data structures, their attributes, and the linkages between them to


userspace.” 


--- documentation/filesystems/sysfs.txt


Sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于把连接到系统上的设备和总线组织成分级的文件结构,向用户模式程序提供详细的内核数据结构信息。


  1. # ls /sys
  2. class firmware vendor
  3. bus devices kernel module
  4. #


block: 包含所有的快设备,系统中的每个块设备在该目录下对于一个子目录,每个子目录包含一些属性文件,loop块设备使用文件来模拟的。


devices: 包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构。


bus: 在内核中注册的每条总线在该目录下对应一个子目录,每个总线又包含两个子目录devices,drivers,devices目录包含整个系统中属于该总线类型的设备,drivers目录包含所有属于该总线的驱动


drivers: 包含内核中已经注册的所有设备驱动


class: 系统中的设备类型,按功能划分。


kernel:内核中的配置参数


module:系统中的所有模块信息


sysfs这样被挂载的。


mount -t sysfs sysfs /sys


sysfs是一个特殊文件系统,并没有一个实际存放文件的介质。断电就消失了。简而言之,sysfs的信息来源是kobject层次结构,读一个sysfs文件,就是动态的从kobject结构提取信息,生成文件。


所以,首先要研究下sysfs文件系统的信息来源 -- kobject层次结构。kobject层次结构就是linux的设备模型。




2.kobject


kobject实现了基本的面向对象管理机制,是构成Linux2.6设备模型的核心结构,它与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录。kobject可以理解为类似JAVA,C++中基类,常被嵌入到其他类型(容器)中,比如bus,devices,drivers都是典型的容器,他们继承kobject基本功能,并扩展自身其他功能,这些容器通过通过kobject链接起来,形成树状结构。C语言中没有继承特性,通过结构体包含来实现类继承。


①kobject定义在 。


  1. struct kobject {
  2. const char * k_name;/*kobject 的名字数组(sysfs 入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间 */
  3. [KOBJ_NAME_LEN];/*kobject 的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/
  4. ;/*kobject 的引用计数*/
  5. ;/*kobject 之间的双向链表,与所属的kset形成环形链表*/
  6. * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/
  7. * kset;/*指向所属的kset*/
  8. * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/
  9. * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/
  10. ;/*等待队列头*/
  11. };


kobject 是组成设备模型的基本结构,初始它只被作为一个简单的引用计数, 但随时间的推移,其任务越来越多。现在kobject 所处理的任务和支持代码包括:


对象的引用计数 :跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。


sysfs 表述:在 sysfs 中出现的每个对象都对应一个 kobject, 它和内核交互来创建它的可见表述。


数据结构关联:整体来看, 设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。kobject 实现了该结构并将其聚合在一起。


热插拔事件处理


一个kobject对自身并不感兴趣,它存在的意义在于把高级对象连接到设备模型上。因此内核代码很少(甚至不知道)创建一个单独的 kobject;而kobject 被用来控制对大型域(domain)相关对象的访问,所以kobject 被嵌入到其他结构中。kobject 可被看作一个最顶层的基类,其他类都它的派生产物。 kobject 实现了一系列方法,对自身并没有特殊作用,而对其他对象却非常有效。


 对于给定的kobject指针,可使用container_of宏得到包含它的结构体的指针。

②kobject方法

  1. void kobject_init(struct kobject *kobj, struct kobj_type *ktype)/*初始化kobject结构*/

  2. int kobject_add(struct kobject *kobj, struct kobject *parent,const char *fmt, ...)//将kobject对象注册到Linux系统

  3. int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,struct kobject *parent, const char *fmt, ...)
  4. /*初始化kobject,并将其注册到Linux系统,手头实验平台是2.6.10的代码,注册用kobject_register(),基本过程是一样的*/

  5. int kobject_set_name(struct kobject *kobj, const char *fmt, ...)//设置kobject目录名字

  6. (struct kobject *kobj)/*从Linux系统中删除kobj对象*/

  7. *kobject_get(struct kobject *kobj)
  8. //将kobject对象的引用计数加1,同时返回该对象指针

  9. (struct kobject *kobj)
  10. //将kobject对象的引用计数减1,如果引用计数为0,则调用release方法释放该kobject对象。


③kobj_type

kobject的ktype成员是一个指向 kobj_type结构的指针,该结构记录了kobject的一些属性

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

release:用于释放kobject占用的资源,当kobject的应用计数为0时被调用。



  1. struct attribute {
  2. const char *name;
  3. struct module *owner;
  4. mode_t mode;
  5. };

struct attribute:对应于kobject下的一个文件,name就是文件名。

  1. struct sysfs_ops {
  2. (*show)(struct kobject *, struct attribute *,char *buf);
  3. (*store)(struct kobject *,struct attribute *,const char *buf, size_t);
  4. };

show: 当用户读属性文件时,被调用,该函数将属性值写入buf返回给用户态。

store:当用户写属性文件时,被调用,用户存储用户传入的属性值。

加载一个kobject过程:

① 分配kobject内存空间,并清零(必须清零,否则出错,我板子上出现段错误)

② 设置kobject目录名字,ktype等成员

③ 初始化,加载kobject

测试代码: ​​ kobject.rar ​​ 

实验现象:

​​

kobject可以简单通俗的理解为一个对象,功能是用来在sysfs文件系统中管理一个目录,以及目录下文件属性。Linux内核模型中的几个典型模块bus,device,driver都是通过继承kobject对象(结构体包含),来组织sysfs文件系统的层级目录树状图。kobject是设备模型中最核心的数据结构,真正理解这个对象机制,才能掌握好后面的总线模型。

3.kset

kset是具有相同类型的object的集合,在sysfs中体现为一个目录,

  1. struct kset {
  2. * ktype; /*指向该kset对象类型的指针*/
  3. ;/*用于连接该kset中所有kobject以形成环形链表的链表头*/
  4. ;/*用于避免竞态的自旋锁*/
  5. ; /*嵌入的kobject*/
  6. * uevent_ops;
  7. /*处理热插拔时间的集合*/
  8. };

​​

kset方法,与kobject类似

  1. void kset_init(struct kset *kset);
  2. int kset_add(struct kset *kset);
  3. int kset_register(struct kset *kset);
  4. (struct kset *kset);

  5. /*管理 ksets 的引用计数:*/
  6. *kset_get(struct kset *kset);
  7. (struct kset *kset);

  8. /* kset 也有一个名字,存储于嵌入的 kobject,因此设置它的名字用:*/
  9. (&my_set->kobj, "The name");

热插拔事件

一个热插拔事件是一个从内核空间发送到用户空间的通知, 表明系统配置已经改变. 无论 kobject 被创建或删除,都会产生这种事件。热插拔事件会导致对 /sbin/hotplug 的调用, 它通过加载驱动程序, 创建设备节点, 挂载分区或其他正确动作响应事件。


热插拔事件的实际控制是通过一套存储于 kset_uevent_ops (《LDD3》中介绍的struct kset_hotplug_ops * hotplug_ops;在2.6.22.2中已经被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, char **envp,
            int num_envp, char *buffer, int buffer_size);
};



可以在 kset 结构的uevent_ops 成员中找到指向kset_uevent_ops结构的指针。

若在 kobject 中不包含指定的 kset , 内核将通过 parent 指针在分层结构中进行搜索,直到发现一个包含有kset的 kobject ; 接着使用这个 kset 的热插拔操作。当kset管理的kobject和kset发生变化时,这三个方法被调用。


三个方法功能如下:
(1) filter 函数让 kset 代码决定是否将事件传递给用户空间。如果 filter 返回 0,将不产生事件。以磁盘的 filter 函数为例,它只允许kobject产生磁盘和分区的事件,源码如下:


static int block_hotplug_filter(struct kset *kset, struct kobject *kobj)
{
 struct kobj_type *ktype = get_ktype(kobj);
    return ((ktype == &ktype_block) || (ktype == &ktype_part));
}

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

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

int (*uevent)(struct kset *kset, struct kobject *kobj, /*产生事件的目标对象*/
 char **envp,/*一个保存其他环境变量定义(通常为 NAME=value 的格式)的数组*/
 int num_envp, /*环境变量数组中包含的变量个数(数组大小)*/
 char *buffer, int buffer_size/*环境变量被编码后放入的缓冲区的指针和字节数(大小)*/
/*若需要添加任何环境变量到 envp, 必须在最后的添加项后加一个 NULL 入口,使内核知道数组的结尾*/
        );
/*返回值正常应当是 0,若返回非零值将终止热插拔事件的产生*/

热插拔事件的产生通常是由在总线驱动程序层的逻辑所控制。

一个简单的测试代码:

1. #include <linux/device.h>
2. <linux/module.h>
3. <linux/kernel.h>
4. <linux/init.h>
5. <linux/string.h>
6. <linux/sysfs.h>
7. <linux/stat.h>
8. <linux/kobject.h>
9.
10. ("David Xie");
11. ("Dual BSD/GPL");
12.
13. * kset_p;
14. * kset_c;
15.
16. int kset_filter(struct kset *kset, struct kobject *kobj)
17. {
18. ("Filter: kobj %s.\n",kobj->name);
19. ;
20. }
21.
22. const char *kset_name(struct kset *kset, struct kobject *kobj)
23. {
24. [20];
25. ("Name: kobj %s.\n",kobj->name);
26. (buf,"%s","kset_name");
27. ;
28. }
29.
30. int kset_uevent(struct kset *kset, struct kobject *kobj, char **envp,
31. int num_envp, char *buffer, int buffer_size)
32. {
33. //int i = 0;
34. ("uevent: kobj %s.\n",kobj->name);
35. if 0
36. while( i < env->envp_idx){
37. ("%s.\n",env->envp[i]);
38. ++;
39. }
40. #endif
41. ;
42. }
43.
44. =
45. {
46. .filter = kset_filter,
47. .name = kset_name,
48. .uevent = kset_uevent,
49. };
50.
51. int kset_test_init()
52. {
53. ("kset test init.\n");
54. = kzalloc(sizeof(struct kset), GFP_KERNEL);
55. = kzalloc(sizeof(struct kset), GFP_KERNEL);
56.
57. (&kset_p->kobj,"kset_p");
58. ->uevent_ops = &uevent_ops;
59. (kset_p);
60.
61. (&kset_c->kobj,"kset_c");
62. ->kobj.kset = kset_p;
63. (kset_c);
64. ;
65. }
66.
67. int kset_test_exit()
68. {
69. ("kset test exit.\n");
70. (&kset_p);
71. (&kset_c);
72.
73. (kset_p);
74. (kset_c);
75. ;
76. }
77.
78. (kset_test_init);
79. (kset_test_exit);

4.子系统


子系统是对整个内核中一些高级部分的表述。子系统通常(但不一定)出现在 sysfs分层结构中的顶层,内核子系统包括 block_subsys(/sys/block 块设备)、 devices_subsys(/sys/devices 核心设备层)以及内核已知的用于各种总线的特定子系统。


每个 kset 必须属于一个子系统,子系统成员帮助内核在分层结构中定位 kset 。由于子系统在新版内核中已经被去掉用kset代替了。,就不详细研究了。