相信自己,快乐每一天
分类: 嵌入式
2014-02-19 22:45:42
原文地址:Linux设备模型与sysfs文件系统 作者:oceanhehy
Linux设备模型与sysfs文件系统
1 前言
学习设备模型是因为我学习usb驱动而引起的。在看《linux设备驱动》的第13章usb驱动程序,发现有点进行不下去,必须先看明白设备模型才行。于是跳到第14章linux设备模型。第14章,我看了两周,断断续续的看,总也理解不透彻。期间在网上看到一篇《linux设备模型深探》博客文章,有所启发。而后翻看了《linux内核设计与实现》第17章,才更加明白。今天一看《深入理解linux内核》第13章讲解设备模型部分,感觉理解更加透彻。
说的linux的设备模型,就不得不说sysfs文件系统,这是一个处于内存中虚拟文件系统,为设备模型提供类似文件系统的视图,也就是说sysfs只是设备模型的一种表象。但是要注意区分设备模型和sysfs两个概念。
根据经验,学习设备模型与sysfs的顺序应该是:
《linux内核设计与实现》第17章;
《深入理解linux内核》第13章;
《linux设备驱动》的第14章。
1 kobject kset subsystem
1.1 kobject
设备模型的核心数据是kobject数据结构。《linux内核设计与实现》中如下表述:kobject类似于C#或Java这些面向对象语言中的object对象类,提供了诸如引用计数、名称和父指针等字段,可以创建对象的层次结构。因此,设备模型中的任一对象都包含一个kobject对象,或者说kobject总是被嵌入到一个叫做“容器”的更大对象中去。容器的典型例子就是某种设备的抽象。例如字符设备驱动中会涉及到cdev数据结构,cdev用于描述一个字符设备,而cdev中就定义了(嵌入了)一个kobject对象。当然再后面会看到容器还可以是总线、驱动等等。任何逻辑概念都可以作为容器,包括后面介绍的kset数据结构。
下面介绍一下kobject的数据结构内容(摘自linux2.6.21内核源码,下同):
struct kobject { const char * k_name; char name[KOBJ_NAME_LEN]; struct kref kref; struct list_head entry; struct kobject * parent; struct kset * kset; struct kobj_type * ktype; struct dentry * dentry; wait_queue_head_t poll; }; |
kref是容器的引用计数器;entry是为了kobject的链表操作(不明白的参考ldd3的第11章的链表部分);parent就很明了了,它指向一个父kobject对象,用于构造设备模型中的层次关系;kset用于构造更复杂的层次关系,后面会介绍;dentry指向了与kobject对于的sysfs文件系统中的dentry数据结构,也正是利用dentry才得以形成sysfs虚拟文件系统。关于dentry需要学习linux的VFS;poll目前没找到什么解释。
最后重点介绍ktype数据结构。从kboject结构可以看出,它利用parent字段可以构成父子层次关系;但是设备模型除了描述容器的层次关系,还要描述容器的特性,而ktype数据结构就是为此定义的。ktype数据结构如下。
struct kobj_type { void (*release)(struct kobject *); struct sysfs_ops * sysfs_ops; struct attribute ** default_attrs; }; |
release函数指针指向在kobject引用计数减至零时要被调用的析构函数。该函数负责释放所有的kobject使用的内存和其他相关清理工作。析构函数的概念是从《linux内核设计与实现》中看来的,因为我C++和C#都还学的不错,所有对这个概念很敏感,觉得总结的非常好。
default_attribute和sysfs_ops是配合使用的。default_attribute指向一个包含attribute结构数组。attribute结构如下。
struct attribute { const char * name; struct module * owner; mode_t mode; }; |
name是属性的名字;owner指向模块指针;mode是属性的保护位,设置读写权限等。
attribute可以说明容器具有哪些属性,而sysfs_ops中的函数指针所指向的函数就是来操作(读/写)这些的属性的。sysfs_ops数据结构定义如下。
struct sysfs_ops { ssize_t (*show)(struct kobject *, struct attribute *,char *); ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); }; |
从数据结构中可以看到,定义了show和stroe分别来读写容器的属性。
综上所述,kobject作为基类对象,总是被嵌入一个叫做“容器”的更大的对象中去;它使用kref完成引用计数;利用parent可以实现父子层次关系;利用ktype描述容器的属性。但是,kset用来做什么呢?因为parent所实现的父子层次关系是有缺陷的,所以需要kset。
1.2 kset
1.2.1 kset与kobject
在讨论kset之前,先来看看kobject的parent字段所构成的父子层次关系为什么有缺陷。注意parent实现的父子层次关系是单向的,因此一个kobject仅仅知道其父kobject,却不知道其子kobject。并且,一个kobject有且只有一个父kobject,但是可能会有多个子kobject。
如果让我来解决这个问题,我就在kobject中定义一个kobjcet集合,该集合用双向链表来把所有的子kobject链接起来。这样,每一个容器中嵌入一个kobject,每一个kobject通过kobject集合可以访问所有的子kobject;通过parent可以访问父kobject。我最新设计的VT100菜单库就是使用这种结构的。我的每个菜单项都包含一个kobject,包括顶层菜单项同样是包含一个kobject;kobject中的kobject集合中就是子菜单项,所有子菜单项中的kobject的父kobject都指向父菜单中的kobject;直至最后一级子菜单中,其kobjcet集合为空。
带着上述思路,我原本理解出来的kobject和kset构成的层次结构是这样的。
注意,kobject和kset中都有list_head,用于形成双向链表。
但是,在读三本参考文献时,我发现我的上述理解是错误的。linux的设备模型不是我想的这样子的。首先看一下kset的数据结构定义。
struct kset { struct subsystem * subsys; struct kobj_type * ktype; struct list_head list; spinlock_t list_lock; struct kobject kobj; struct kset_uevent_ops * uevent_ops; }; |
kset中包含list_head,说明其可以用于构成双向链表;它还嵌入了一个kobject,表明了kset其实是一个嵌入了kobject对象的容器。理解这一点很关键,回想kobject的表述,kobject总是被嵌入到一个更大的称为“容器”的对象中去,当时举了cdev的例子,而现在kset也是一个例子。但是kset比较特别,它内部不但嵌入了一个kobject,还包含了kobject的集合。如何实现这个kobject集合呢,是使用kset中的list_head和其所包含的kobject中的list_head形成双向链表。
kset和kobject的关系,在ldd3的第14章有一个经典的图。
图中,灰色部分表示kset数据结构,作为一个容器,其内部嵌入了一个koject对象。而底端三个kobject是嵌入到其他容器中的,这些kobject的parent就是kset中的kobject,从黑色虚线能看出来;而灰色虚线则表示底端kobject中的kset指针指向灰色的kset。黑色实线表明kset中的list_head以及底端kobject中的kobject的list_head构成了双向链表。
上图虽然经典,但是只能描述kset和kobjects间的单层关系。在我看这幅图时,我总是会有这样的疑问:底端的kobjects的容器会不会也是一个kset呢?会不会出现多个kset层次呢?从编程角度来说,是完全可以这样的;但是答案是否定的,因为linux不是这么用的。在设备模型树中,任一路径中,有且只能有一个“单一kset”。这个结论是我自己得出来的,后面会解释我为什么得出这个结论。
综上所述,kset和kobject的关系就很明了了。kset是嵌入了kobject的一个特殊容器,它利用list_head实现了子kobject的集合。
1.2.2 kset与ktype
kset做为一个kobject的容器,本来是要使用kobject中的ktype的;但是kset也定义了一个ktype。ldd3给出了明确的解释:kset中的ktype也用来描述其包含的kobject,优于kobject中的ktype使用。在典型应用中,kobject中的ktype设为null。
1.2.3 kset与subsystem
kset中包含一个subsystem指针;在linux中,每一个kset都必须属于一个子系统。
1.3 subsystem
subsystem的数据结构很简单。
struct subsystem { struct kset kset; struct rw_semaphore rwsem; }; |
subsystem结构中明显嵌入了一个kset结构;因此可以将subsystem看做是kset的容器。但是因为kset中嵌入了一个kobject,所以可以说subsystem中也是一个kobject的容器。
subsystem的作用是归纳kset集合。在subsystem的底层,会出现如下两种情况:第一,其底层是多个subsystem;第二种,其底层就是kset。无论是那种情况,以kset的能力都是能有效组织起来的。
linux中的设备模型的顶层是从subsystem子系统开始的。在sys目录底下能看到,其底下包括block_subsys(/sys/block)、devices_subsys(/sys/devices)等子系统。但是子系统subsytem不一定出现在顶层,也有可能出现在子系统中。在上面已经提到过,kset必须属于一个子系统;在像内核中添加一个kset结构时,如果没有指定子系统,那么这个kset将会出现在顶层。因此,在上面我得出过结论:在设备模型树中,任一路径中“单一kset”只会出现一次。请参考《深入理解linux内核》中的一幅图(图13-3),就能基本能理解设备模型了。
1.4 关于设备模型的若干结论
kobject总是被嵌入到一个称为“容器”的更大的对象中;单独存在的kobject是没意义的;
kset是一个嵌入了kobject的特殊容器,它内部包含一个子kobjects的集合;kset的上层必须是subsystem,kset的下层必然是kobject;在设备模型树中,任一路径中“单一kset”只会出现一次。
subsystem是kset的集合,它的底层可以是kset也可以是subsystem;
2 sysfs文件系统
sysfs的诀窍是把kobject对象与目录项(dentry)紧密联系起来,这点事通过kobject对象中的dentry实现的。对于sysfs中的每个目录,都会有一个kobject与其对应。
在sysfs中我们看到的是目录树,它是对象模型树导出而形成的文件系统。
sysfs下目前有7个子系统目录。
block |
每个目录对应着系统中的一个块设备,独立于所连接的总线 |
devices |
所有设备拓扑结构视图,依照连接它们的总线进行组织 |
bus |
系统中用于连接设备的总线 |
drivers |
在内核中注册的设备驱动程序 |
class |
以高层逻辑组织起来的系统设备视图,如声卡、显卡、网卡等;同一类可能包含由不同总线连接的设备。 |
power |
处理一些硬件设备电源的文件 |
firmware |
包含硬件设备的固件,诸如ACPI/EDD/EFI等低层子系统的特殊树 |
对于上述子系统,ldd3重点讲解了bus、devices、drivers三个子系统。上面说了这么多都是虚的,看看这三个子系统在sysfs中具体实现才能够更加明确设备模型的结构。
在linux设备模型中,用bus_type结构表示总线。在sysfs中可以看到,bus_type定义的总线子系统并不在sys顶层,而是位于sys/bus子系统中,如pci、usb等总线子系统。bus_type中包含了一个subsystem,两个kset。这两个kset的父kset都是该subsystem中的kset。看/sys/bus/pci下能找到devices和drives这两个kset。这两个kset就是单一kset。
写在最后:下一步学习《总线、设备与驱动程序》的具体细节;然后就是热插拔的细节。
参考文献
《linux内核设计与实现》第17章;
《深入理解linux内核》第13章;
《linux设备驱动》的第14章。