文章系原创,如需转载请注明:转载自”Blog of UnicornX” (http://unicornx.github.io/)
最新更新于:2016-01-09
注: 有关设备模型和sysfs是一个很大的话题,我的很多理解还不是很成熟,匆忙写在这里大部分也就是个笔记罢了。
主要参考文章:
ldd3
lkd3
ulk3
Linux设备模型 (2) - 该文章有实际的例子讲解kobject的父子层次关系和kset之间的区别。
linux device model简述 - 内容一般,但有一张我曾经想补充的图。
内核代码v2.6.32.2
其他参考文章:
总论
在 Linux 2.5 内核的开发过程中,人们设计了一套新的设备模型,目的是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。这个模型是在分析了PCI和USB的总线驱动过程中得到的,这两个总线类型能代表当前系统中的大多数设备类型,它们都有完善的热挺拔机制和电源管理的支持,也都有级连机制的支持,以桥接的PCI/USB总线控制器的方式可以支持更多的PCI/USB设备。为了给所有设备添加统一的电源管理的支持,而不是让每个设备中去独立实现电源管理的支持,人们考虑的是如何尽可能地重用代码;而且在有层次模型的PCI/USB总线中,必须以合理形式展示出这个层次关系,这也是电源管理等所要求的必须有层次结构。
如在一个典型的PC系统中,中央处理器(CPU)能直接控制的是PCI总线设备,而USB总线设备是以一个PCI设备(PCI-USB桥)的形式接入在PCI总线设备上,外部USB设备再接入在USB总线设备上;当计算机执行挂起(suspend)操作时, Linux内核应该以“外部USB设备->USB总线设备->PCI总线设备”的顺序通知每一个设备将电源挂起;执行恢复(resume)时则以相反的顺序通知;反之如果不按此顺序则将有设备得不到正确的电源状态变迁的通知,将无法正常工作。
更加具体的设备模型所提供的功能,可以参考ldd3第十四章开篇的论述。
sysfs是在这个Linux统一设备模型的开发过程中的一项副产品(见Greg写作的LinuxJournal文章“The Driver Model Core, Part I)。为了将这种具备层次结构的内核的设备模型以用户程序可见的方式表达出来,人们很自然想到了利用文件系统的目录树结构(这是以 UNIX 方式思考问题的基础,一切都是文件!)。sysfs是一种虚拟的基于内存的文件系统,它的作用与proc有些类似,但除了与proc 相同的具有查看和设定内核参数功能之外,还有为Linux统一设备模型作为管理之用。相比于proc文件系统,使用 sysfs 导出内核数据的方式更为统一,并且组织的方式更好,它的设计从proc中吸取了很多教训。
ps: sysfs v.s. proc
sysfs 与 proc 相比有很多优点,最重要的莫过于设计上的清晰。一个 proc 虚拟文件可能有内部格式,如 /proc/scsi/scsi ,它是可读可写的,(其文件权限被错误地标记为了0444,这是内核的一个BUG),并且读写格式不一样,代表不同的操作,应用程序中读到了这个文件的内容一般还需要进行字符串解析,而在写入时需要先用字符串格式化按指定的格式写入字符串进行操作;相比而言, sysfs 的设计原则是一个属性文件只做一件事情, sysfs 属性文件一般只有一个值,直接读取或写入。整个 /proc/scsi 目录在2.6内核中已被标记为过时(LEGACY),它的功能已经被相应的 /sys 属性文件所完全取代。新设计的内核机制应该尽量使用 sysfs 机制,而将 proc 保留给纯净的“进程文件系统”。
Linux统一设备模型
Linux的设备模型作为一个内核的子系统,其设计由很多结构类型构成,而且这些类型之间具有明显的层次关系。
从大的层次来说,可以从下到上分为两层:
kobject和kset
按照ldd3的定义,它们属于Linux设备模型的低层部分,是组成设备模型的基础结构。具体的功能,可以参考ldd3的描述如下:管理对象的引用计数
sys表述,为建立sysfs的树状结构提供基本元素,具体说来,就是sysfs中的每个目录都对应着一个kobject对象,但反过来说则不成立,因为不是所有的kobject对象都需要在sysfs中有所展现(一个kset对象本质上也是一个kobject对象,后面我们会看到从OOP的角度来看,kset可以看成是kobject的子类);对每个kobject可以设置导出一个或者多个属性,这些属性在kobject所对应的sysfs的目录中则表现为文件。
数据结构关联,Linux 统一设备模型使用了两种基本数据结构,即以树状结构在纵向表达分层概念,以及用链表在横向上组织位于同一层上的所有对象。这些都在kobject和kset的设计中有所体现。
热插拔事件处理,观察内核代码kobject中的uevent相关内容,都是和这个功能有关,主要是当系统中硬件被热插拔时,可以产生事件通知用户空间。
总线,设备和驱动程序
按照ldd3的定义,它们属于Linux设备模型的高级部分。在这一层其实还可以再分。
最基本的就是形如bus_type,device和device_driver这三个类。这三个类从OOP的思想看来,看以看成是从kobject的子类,但它们仍然还是次基类,具体的总线,设备和驱动需要继续从这三个类进行派生。
它们的对应关系见下表”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/*/
Linux设备模型的低层部分 - kobject和kset
kobject
kobject是在Linux设备模型中最基本的对象,它的功能是提供引用计数和维持父子(parent)结构、平级(sibling)目录关系。按照OOP的概念来看可以认为是设备模型中其他类的基类,上面的device, device_driver等类型都是在kobject的基础功能之上实现的子类;具体定义参考include/linux/kobject.h
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;
};
其中:
name:分配给kobject的名字就是我们在sysfs中看到的对应的目录名。即处于sysfs分层结构同一层的kobject的名字必须唯一,而且名字必须是合法的文件名,不能包括/,而且强烈建议不可以包含空格。
entry:链表挂载点,用于将同级的对象串联在一起,配合kset使用。
parent:保存了另一个kobject的指针,这里不要理解成OOP的父类的概念,其主要用途是用于表达模型的分层特性。也就是说parent所指向的kobject对象位于本kobject对象的上一层。譬如一个kobject对象表示了一个USB设备,他的parent可能指向了该USB设备所连接的USB集线器对象,即表达了拓扑上USB设备是插在USB集线器上的概念。参考Linux设备模型 (2)所述:“内核里的设备之间是以树状形式组织的,在这种组织架构里比较靠上层的节点可以看作是下层节点的父节点,反映到sysfs里就是上级目录和下级目录之间的关系,在内核里,正是kobject帮助我们实现这种父子关系。”
kset:指向该kobject所归属的kset对象。具体用途见下文kset的介绍。
kref:struct kref内含一个atomic_t类型用于引用计数。
ktype:Ktype的定义如下:
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。
sd:和sysfs展现有关,看到的是一个dirent,所以一个kobject对应的是一个目录。
uevent相关项:和热插拔有关
kset
kset可以看成是kobject的顶层容器。从OOP的概念来看,表达的是多个kobject的聚集或者说是集合的概念。主要是为了sysfs的需要所设计,一旦定义了一个kset对象并将其添加到系统中,在sysfs中相应地创建一个目录(ldd3中提到top-level的概念,但我目前还不能确定,就是kset是否只对应sysfs的顶层目录)。至少参考lkd3的说法没有提到这个意思。
我们会发现一旦某个kobject对象被添加到一个kset集合中,则在sysfs里,在对应的kset的目录下就一定会出现一个和该kobject对应的子目录。但这并不意味着所有的kobject对象都会在sysfs中有对应的目录项来显示,譬如有些就没有被添加到kset中。
在这里提一下kobject结构中的parent和kset成员的含义。参考linux device model简述,中有这么一句话我觉得有点意思:
内核用kobject结构将各个对象连接起来组成一个分层的结构体系,有两种独立的机制用于连接:parent指针和kset。
在kobject结构中的parent成员指向另一个kobject结构,这个结构表示了分层结构中的上一层节点。父kobject和kobject在sysfs中表现为目录与子目录的关系。
kset是同类kobject的集合。
同时参考ldd3第14章中有关低层sysfs操作中的描述:
sysfs入口在目录中的位置对应于kobject的parent指针。
所以一个kobject的对象的parent可以是一个普通kobject,或者是一个kset,其实kset也不过就是一个特殊的kset,但重要的是kset为何了一个链表,遍历该链表上的kobject会方便。
所以我目前的理解是,到底怎么在设备模型的低层搭建kobject之间的层次关系,可以用kset,也可以不用kset。何时用kset,可以参考include/linux/kobject.h中的注释“these kobjects all want to be grouped together and operated on in the same manner.”:
/**
* struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
*
* A kset defines a group of kobjects. They can be individually
* different "types" but overall these kobjects all want to be grouped
* together and operated on in the same manner. ksets are used to
* define the attribute callbacks and other common events that happen to
* a kobject.
*
* @list: the list of all kobjects for this kset
* @list_lock: a lock for iterating over the kobjects
* @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
* @uevent_ops: the set of uevent operations for this kset. These are
* called whenever a kobject has something happen to it so that the kset
* can add new environment variables, or filter out the uevents if so
* desired.
*/
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};list:用于将同属于一个kset中的kobject维护成双向链表;
kobj:内嵌kobject对象表达的是OOP中继承的概念,说明我们可以把kset看成是kobject的子类。也就是说从设备模型的角度来说,一个kset对象也是一个kobject,我们可以对其应用所有kobject的方法。
uevent_ops:热插拔相关。
kset和kobject之间的关系,还是参考ldd3的经典分层结构图吧。注意理解一下ldd3上有这么一句话在图中所有被包含的kobject,实际上是被嵌入到其他类型的对象中,甚至可能是其他的kset对象。
所以更准确的图应该如下所示,本来想自己画的,既然网上已经有了,就借用一下吧。
总结
涉及到的结构体类型:
类
用途
方法
kobject
kset
kobj_type
Linux设备模型的高层部分 - 总线,设备和驱动
(待补充)
sysfs文件系统
/sys文件系统下的目录结构
/sys下的目录结构是经过精心设计的:在/sys/devices下是所有设备的真实对象,包括如视频卡和以太网卡等真实的设备,也包括 ACPI 等不那么显而易见的真实设备、还有 tty, bonding 等纯粹虚拟的设备;在其它目录如 class, bus 等中则在分类的目录中含有大量对 devices 中真实对象引用的符号链接文件; 清单 1 中在 /sys 根目录下顶层目录的意义如下:
表 1. /sys 下的目录结构:
/sys下的子目录
所包含的内容
/sys/devices
这是内核对系统中所有设备的分层次表达模型,也是 /sys文件系统管理设备的最重要的目录结构
/sys/dev
这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核2.6.26首次引入
/sys/bus
这是内核设备按总线类型分层放置的目录结构,devices中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成Linux统一设备模型的一部分
/sys/class
这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在/sys/class/input之下,而不论它们是以何种总线连接到系统。它也是构成Linux统一设备模型的一部分
/sys/block
这里是系统中当前所有的块设备所在,按照功能来说放置在/sys/class之下会更合适,但只是由于历史遗留因素而一直存在于/sys/block, 但从2.6.22开始就已标记为过时,只有在打开了CONFIG_SYSFS_DEPRECATED配置下编译才会有这个目录的存在,并且在2.6.26内核中已正式移到/sys/class/block, 旧的接口/sys/block为了向后兼容保留存在,但其中的内容已经变为指向它们在/sys/devices/中真实设备的符号链接文件
/sys/firmware
这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API,在LDD3一书中有关于内核支持固件加载机制的更详细的介绍
/sys/fs
这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持sysfs接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在sysctl(/proc/sys/fs)接口中
/sys/kernel
这里是内核所有可调整参数的位置,目前只有uevent_helper,kexec_loaded,mm和新式的slab分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于sysctl(/proc/sys/kernel)接口中
/sys/module
这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在/sys/module中;
- 编译为外部模块(ko文件)在加载后会出现对应的/sys/module//, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;
- 编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的/sys/module/, 这些模块的可用参数会出现在/sys/modules//parameters/中,如/sys/module/printk/parameters/time这个可读写参数控制着内联模块printk在打印内核消息时是否加上时间前缀;所有内联模块的参数也可以由”.=“的形式写在内核启动参数上,如启动内核时加上参数 “printk.time=1” 与 向 “/sys/module/printk/parameters/time” 写入1的效果相同;
- 没有非0属性参数的内联模块不会出现于此。
/sys/power
这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。
/sys/slab (对应 2.6.23 内核,在 2.6.24 以后移至 /sys/kernel/slab)
从2.6.23 开始可以选择 SLAB 内存分配器的实现,并且新的 SLUB(Unqueued Slab Allocator)被设置为缺省值;如果编译了此选项,在 /sys 下就会出现 /sys/slab ,里面有每一个 kmem_cache 结构体的可调整参数。对应于旧的 SLAB 内存分配器下的 /proc/slabinfo 动态调整接口,新式的 /sys/kernel/slab/接口中的各项信息和可调整项显得更为清晰。
sys文件系统实现
sysfs 是一种基于 ramfs 实现的内存文件系统,与其它同样以 ramfs
实现的内存文件系统(configfs,debugfs,tmpfs,…)类似, sysfs 也是直接以 VFS 中的 struct inode 和 struct dentry 等 VFS 层次的结构体直接实现文件系统中的各种对象;同时在每个文件系统的私有数据 (如 dentry->d_fsdata 等位置) 上,使用了称为 struct sysfs_dirent 的结构用于表示 /sys 中的每一个目录项。
struct sysfs_dirent {
atomic_t s_count;
atomic_t s_active;
struct sysfs_dirent *s_parent;
struct sysfs_dirent *s_sibling;
const char *s_name;
union {
struct sysfs_elem_dir s_dir;
struct sysfs_elem_symlink s_symlink;
struct sysfs_elem_attr s_attr;
struct sysfs_elem_bin_attr s_bin_attr;
};
unsigned int s_flags;
ino_t s_ino;
umode_t s_mode;
struct iattr *s_iattr;
};
在上面的 kobject 对象中可以看到有向sysfs_dirent的指针,因此在sysfs中是用同一种struct sysfs_dirent来统一设备模型中的kset/kobject/attr/attr_group。
具体在数据结构成员上,sysfs_dirent上有一个union共用体包含四种不同的结构,分别是目录、符号链接文件、属性文件、二进制属性文件;其中目录类型可以对应kobject,在相应的s_dir 中也有对 kobject 的指针,因此在内核数据结构,kobject与sysfs_dirent是互相引用的.
对sysfs中的每一个目录,对应一个kobject,每个kobject都输出一个或者多个属性,属性在目录中表现为文件。
kobject在sysfs的入口是一个目录,因此对kobject_add的调用将在sysfs中创建一个目录。通常在这个目录中包含一个或者多个文件,对应kobject的属性。
分配给kobject的名字(kobject_set_name)是sysfs中对应kobject的目录名。名字必须是合法的文件名,不可以包含/,并强烈建议不要使用空格。
在使用kobject_add添加一个新的kobject的时候,如果kobject的parent成员是NULL而其kset成员不是NULL,则该kobject被加入到kset中并且其对应的目录也被添加到kset所对应的目录下成为其子目录(kset也是一个kobject,所以在sysfs中其也表现为一个目录的形式)。如果该kobject的parent成员和kset成员都是NULL,则将其对应的目录设置为sysfs的顶层目录,但通常不建议这么做,应为sysfs的顶层目录是有特殊含义的,一般由系统创建。
属性:
默认属性:缺省属性由kobject的ktype来描述,其类型为kobj_type,其中有一个default_attrs的成员。
非默认属性: struct attrubute
二进制属性:struct bin_attribute
符号链接,在sysfs中用符号链接表达不同子树中对象之间的关系,譬如/sys/bus下驱动程序和/sys/devices下所有设备之间的关系通过符号链接来表达。注意是符号链接,不是硬链接。
常用工具:
在Linux里也可以找到一些图形化的工具来查询设备信息。比如GNOME下基于HAL的Device Manager:
这些图形化的工具提供了更加直观的方式来访问设备,但是它们的提供的信息还不够全面,而且没有向内核设备写数据的功能。
如果具体到某一类型的设备,Linux下还有一些专用的工具可以使用。比如面向PCI设备的pciutils,面向USB设备的usbutils,以及面向SCSI设备的lsscsi等。对于Linux开发者来说,有时使用这些专用的工具更加方便。
我们如果要写程序来访问sysfs,可以像读写普通文件一样来操作/sys目录下的文件,或者,也可以使用libsysfs。不过需要注意的是,Linux内核社区并不推荐用libsysfs,因为这个API的更新不够快,赶不上内核的变化。libsysfs已经逐渐背离最初创建它的目标,这个lib带来的问题似乎比它解决的还要多。当然,如果只是要访问设备,一般很少会直接操作sysfs,它太细节太底层了,大部分情况下可以使用更加方便的DeviceKit或者libudev。