Linux设备模型 (2)
上一篇文章《Linux设备模型 (1)》主要介绍了Linux设备模型在用户空间的接口sysfs,用户通过这个接口可以一览内核设备的全貌。本文将从Linux内核的角度来看一看这个设备模型是如何构建的。
在Linux内核里,kobject是组成Linux设备模型的基础,一个kobject对应sysfs里的一个目录。从面向对象的角度来说,kobject可以看作是所有设备对象的基类,因为C语言并没有面向对象的语法,所以一般是把kobject内嵌到其他结构体里来实现类似的作用,这里的其他结构体可以看作是kobject的派生类。Kobject为Linux设备模型提供了很多有用的功能,比如引用计数,接口抽象,父子关系等等。引用计数本质上就是利用kref实现的,至于kref的细节可以参考我之前的文章《Linux内核里的“智能指针”》。
另外,Linux设备模型还有一个重要的数据结构kset。Kset本身也是一个kobject,所以它在sysfs里同样表现为一个目录,但它和kobject的不同之处在于kset可以看作是一个容器,如果你把它类比为C++里的容器类如list也无不可。Kset之所以能作为容器来使用,其内部正是内嵌了一个双向链表结构struct list_head。对于list_head的细节可以参考《玩转C链表》一文。
下面这幅图可以用来表示kobject和kset在内核里关系。

在接下来的篇幅里我们会逐步看到这个关系图在内核里是如何建立的。本文的示例代码可以从这里下载,下文中的两个实作都在这个示例代码里。
Kobject
Kobject在Linux内核里的定义如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct sysfs_dirent *sd; struct kref kref; unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1;}; |
在《Linux设备模型 (1)》里面我们介绍到内核里的设备之间是以树状形式组织的,在这种组织架构里比较靠上层的节点可以看作是下层节点的父节点,反映到sysfs里就是上级目录和下级目录之间的关系,在内核里,正是kobject帮助我们实现这种父子关系。在kobject的定义里,name表示的是kobject在sysfs中的名字;指针parent用来指向kobject的父对象;Kref大家应该比较熟悉了,kobject通过它来实现引用计数;Kset指针用来指向这个kobject所属的kset,下文会再详细描述kset的用法;对于ktype,如果只是望文生义的话,应该是用来描述kobject的类型信息。Ktype的定义如下:
|
1
2
3
4
5
|
struct kobj_type { void (*release)(struct kobject *kobj); const struct sysfs_ops *sysfs_ops; struct attribute **default_attrs;}; |
函数指针release是给kref使用的,当引用计数为0这个指针指向的函数会被调用来释放内存。sysfs_ops和attribute是做什么用的呢?前文里提到,一个kobject对应sysfs里的一个目录,而目录下的文件就是由sysfs_ops和attribute来实现的,其中,attribute定义了kobject的属性,在sysfs里对应一个文件,sysfs_ops用来定义读写这个文件的方法。Ktype里的attribute是默认的属性,另外也可以使用更加灵活的手段,本文的重点还是放在default attribute。
下面看一个实作。在这个实作里,我们定义一个内嵌kobject的结构。
|
1
2
3
4
|
struct my_kobj { int val; struct kobject kobj;}; |
最终我们的目的是在内核里构建这样的架构。

对应sysfs里的目录关系是:
mykobj1/
|-- mykobj2
| |-- name
| `-- val
|-- name
`-- val
这是module_init代码。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
static int __init mykobj_init(void){ printk(KERN_INFO "mykobj_init\n"); obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL); if (!obj1) { return -ENOMEM; } obj1->val = 1; obj2 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL); if (!obj2) { kfree(obj1); return -ENOMEM; } obj2->val = 2; my_type.release = obj_release; my_type.default_attrs = my_attrs; my_type.sysfs_ops = &my_sysfsops; kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1"); kobject_init_and_add(&obj2->kobj, &my_type, &obj1->kobj, "mykobj2"); return 0;} |
这段代码可以分作三个部分。第一部分是分配obj1和obj2并赋值;第二部分是初始化kobj_type变量my_type;第三部分是调用kobject_init_and_add函数来初始化kobject并把它加入到设备模型的体系架构(也就是上文中提到的内核中的那棵树)中。kobject_init_and_add是简化的写法,这个函数也可以分两步完成:kobject_init和kobject_add。
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);void kobject_init(struct kobject *kobj, struct kobj_type *ktype);int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...); |
kobject_init用来初始化kobject结构,kobject_add用来把kobj加入到设备模型之中。在实作中,我们先对obj1进行初始化和添加的动作,调用参数里,parent被赋为NULL,表示obj1没有父对象,反映到sysfs里,my_kobj1的目录会出现在/sys下,obj2的父对象设定为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1下面。
前面提到,kobject也提供了引用计数的功能,虽然本质上是利用kref,但也提供了另外的接口供用户使用。
|
1
2
|
struct kobject *kobject_get(struct kobject *kobj);void kobject_put(struct kobject *kobj); |
kobject_init_and_add和kobject_init这两个函数被调用后,kobj的引用计数会初始化为1,所以在module_exit时要记得用kobject_put来释放引用计数。
我们再回到实作中,看看如何使用ktype。代码里,my_attrs是这样定义的:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
struct attribute name_attr = { .name = "name", .mode = 0444,};struct attribute val_attr = { .name = "val", .mode = 0666,};struct attribute *my_attrs[] = { &name_attr, &val_attr, NULL,}; |
结构体struct attribute里的name变量用来指定文件名,mode变量用来指定文件的访问权限。这里需要着重指出的是,数组my_attrs的最后一项一定要赋为NULL,否则会造成内核oops。
sysfs_ops的代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
ssize_t my_show(struct kobject *kobj, struct attribute *attr, char *buffer){ struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj); ssize_t count = 0; if (strcmp(attr->name, "name") == 0) { count = sprintf(buffer, "%s\n", kobject_name(kobj)); } else if (strcmp(attr->name, "val") == 0) { count = sprintf(buffer, "%d\n", obj->val); } return count;}ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size){ struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj); if (strcmp(attr->name, "val") == 0) { sscanf(buffer, "%d", &obj->val); } return size;}struct sysfs_ops my_sysfsops = { .show = my_show, .store = my_store,}; |
读文件会调用my_show,写文件会调用my_store。
最后是module_exit:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
static void __exit mykobj_exit(void){ printk(KERN_INFO "mykobj_exit\n"); kobject_del(&obj2->kobj); kobject_put(&obj2->kobj); kobject_del(&obj1->kobj); kobject_put(&obj1->kobj); return;} |
kobject_del的作用是把kobject从设备模型的那棵树里摘掉,同时sysfs里相应的目录也会删除。这里需要指出的是,释放的顺序应该是先子对象,后父对象。因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,所以kobject_del需要调用kobject_put来减少父对象的引用计数。在本例中,如果先通过kobject_put来释放obj1,那kobject_del(&obj2->kobj)就会出现内存错误。
在这个实作中,我们建立了两个对象obj1和obj2,obj1是obj2的父对象,如果推广开来,obj1可以有更多的子对象。在Linux内核中,这种架构方式其实并无太大的实际价值,有限的用处之一是在sysfs里创建子目录(Linux内核里有这种用法,这种情况下,直接调用内核提供的kobject_create来实现,不需要自定义数据结构并内嵌kobject),而且,创建子目录也是有其他的办法的。我们知道,Linux设备模型最初的目的是为了方便电源管理,这就需要从上到下的遍历,在这种架构里,通过obj1并无法访问其所有的子对象。这个实作最大的意义在于可以让我们比较清晰的理解kobject如何使用。通常情况下,kobject只需要在叶节点里使用,上层的节点要使用kset。
Kset
Kset的定义如下:
|
1
2
3
4
5
6
|
struct kset { struct list_head list; spinlock_t list_lock; struct kobject kobj; const struct kset_uevent_ops *uevent_ops;}; |
Kset结构里的kobj表明它也是一个kobject,list变量用来组织它所有的子对象。
我们直接看一个实作。在这个实作里,我们将构建如下的架构。

对应sysfs里的目录关系是:
my_kset/
|-- mykobj1
| |-- name
| `-- val
`-- mykobj2
|-- name
`-- val
这个实作和前一个差别很小,下面只简略的引用一些代码。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
static int __init mykset_init(void){ printk(KERN_INFO "mykset_init\n"); my_kset = kset_create_and_add("my_kset", NULL, NULL); if (!my_kset) { return -ENOMEM; } // Allocate obj1 and obj2 // ... obj1->kobj.kset = my_kset; obj2->kobj.kset = my_kset; // Init my_type // ... kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1"); kobject_init_and_add(&obj2->kobj, &my_type, NULL, "mykobj2"); return 0;}static void __exit mykset_exit(void){ printk(KERN_INFO "mykset_exit\n"); // Release obj1 and obj2 // ... kset_unregister(my_kset); return;} |
在module_init里,我们首先调用kset_create_and_add创建my_kset,接下来把my_kset赋给obj1和obj2,最后调用kobject_init_and_add来添加obj1和obj2。这里需要注意的是,kobject_init_and_add参数里的parent都是NULL,在这种情况下,obj1和obj2的父对象由kobject结构里的kset指针决定,在这个实作里就是my_kset。在module_exit里,我们还需要额外调用kset_unregister来释放之前创建的my_kset。
注:看过LDD3的读者应该对Linux Device Model一章中的subsystem还有印象,我在这里注明一下,从2.6.23开始Linux内核就抛弃了subsystem,subsystem其实只是kset的一个马甲,所以抛弃它对Linux设备模型并没什么影响。
作者:wwang
出处:http://www.cnblogs.com/wwang
本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
--------懒人评论(请勿重复点击)--------
Linux设备模型 (2)的更多相关文章
- linux设备模型_转
建议原博文查看,效果更佳. 转自:http://www.cnblogs.com/wwang/category/269350.html Linux设备模型 (1) 随着计算机的周边外设越来越丰富,设备管 ...
- Linux设备模型(9)_device resource management ---devm申请空间【转】
转自:http://www.wowotech.net/linux_kenrel/device_resource_management.html . 前言 蜗蜗建议,每一个Linux驱动工程师,都能瞄一 ...
- Linux设备模型(总线、设备、驱动程序和类)
Linux设备驱动程序学习(13) -Linux设备模型(总线.设备.驱动程序和类)[转] 文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 提示:在学习这部分内容是一 ...
- Linux设备模型 学习总结
看LDD3中设备模型一章,觉得思维有些混乱.这里从整体的角度来理理思路.本文从四个方面来总结一些内容: 1.底层数据结构:kobject,kset.2.linux设备模型层次关系:bus_type,d ...
- Linux设备模型——设备驱动模型和sysfs文件系统解读
本文将对Linux系统中的sysfs进行简单的分析,要分析sysfs就必须分析内核的driver-model(驱动模型),两者是紧密联系的.在分析过程中,本文将以platform总线和spi主控制器的 ...
- Linux 设备模型浅析之 uevent 篇(2)
Linux 设备模型浅析之 uevent 篇 本文属本人原创,欢迎转载,转载请注明出处.由于个人的见识和能力有限,不可能面 面俱到,也可能存在谬误,敬请网友指出,本人的邮箱是 yzq.seen@gma ...
- linux设备模型:扩展篇
Linux设备模型组件:总线 一.定义:总线是不同IC器件之间相互通讯的通道;在计算机中,一个总线就是处理器与一个或多个不同外设之间的通讯通道;为了设备模型的目的,所有的设备都通过总线相互连接,甚至 ...
- Linux设备模型:基础篇
linux提供了新的设备模型:总线(bus).设备(device).驱动(driver).其中总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连:设备是对于一个设备的详细信息描述,驱动 ...
- Linux 设备模型之 (kobject、kset 和 Subsystem)(二)
问题描写叙述:前文我们知道了/sys是包括内核和驱动的实施信息的,用户能够通过 /sys 这个接口.用户通过这个接口能够一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是怎样构建的 ...
- Linux设备模型(总结)
转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...
随机推荐
- [luoguP1437] [HNOI2004]敲砖块(DP)
传送门 可以得到一个性质,如果打掉第i列的第j个,那么第i列的1~j-1个也会打掉. 如果第i列打j个,那么第i+1列至少打j-1个. #include <cstdio> #include ...
- hdu 4422
#include<stdio.h> #include<string.h> #define inf 0x7fffffff int main() { int i,j,k, ...
- hdu1059(背包dp二进制优化)
Dividing Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Subm ...
- jQuery的对象访问函数(get,index,size,each)
1.get() 元素集合 取得所有匹配的 DOM 元素集合. 这是取得所有匹配元素的一种向后兼容的方式(不同于jQuery对象,而实际上是元素数组). 如果你想要直接操作 DOM 对象而不是 jQue ...
- ThinkPHP5 的入门学习
与Tp3.2相比,有一下的不同: (1)目录名称的改变: tp3.2的目录命名首字母皆为大写,例如:Application.Public.Controller.Model.View.ThinkPHP. ...
- java基础语法——方法,static关键字
一:方法: 1.什么是方法: 通俗地讲,方法就是行为.它是完成特定功能的代码块能执行一个功能.它包含于类和对象中. 2.为什么要有方法: *提高代码的复用性. *提高效率 *利于程序维护 3.命名规则 ...
- Node.js+Web TWAIN,实现Web文档扫描和图像上传
目录(?)[+] 通过Dynamic Web TWAIN SDK和Node.js的组合,只需要几行代码就可以实现在浏览器中控制扫描仪,获取图像后上传到远程服务器. 原文:Document Imag ...
- 使用MySQL Workbench进行数据库设计——MySQL Workbench用法总结
转载请注明出处:http://blog.csdn.net/dongdong9223/article/details/48318877 本文出自[我是干勾鱼的博客] 1 简单介绍 MySQL Workb ...
- [LeetCode][Java] Subsets
题目: Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset ...
- ACM之数论数字根
先来看一道杭电的数字根问题 此题的大大意是输入一个数.假设它不是一位的数字的话,那么我们就将它的每一位都相加,相加后假设还是两位或者很多其它的话那么我们继续取出它的每一位数字进行相加.知道等到单个数字 ...