sysfs文件系统是内核2.6的一个特性,通过in-memory文件系统的方式把内核对象(object)信息暴露给用户程序。参考:https://www.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols-2005/mochel.pdf
note:本文是对sysfs的一些简单介绍,有很多不足之处----对于代码的结构都介绍的不够详细,相关代码在内核fs/sysfs目录下
介绍
sysfs是一种代表内核对象的机制,内核对象的属性和内核对象相互之间的关系。从sysfs的目的来看,是把原本在procfs中的,关于设备的部分独立出来,以“设备层次结构”的形式呈现。它提供了两个组件,一个内核编程接口来把这些item通过sysfs暴露出来;另外一个组件是用户接口,来view和操作这些item(对应着相应的内核对象)。从内核的角度来看,sysfs是作为一个基础架构的核心组件,所以接口很简单,要执行的任务也很简单。
sysfs是内核和用户空间的一个通道。用户空间的程序可以通过很多方式来利用这个信息。比如说现有的利用就有io调度器(决定操作系统把io请求传递给存储volume)的参数和udev程序。因为sysfs是最简单也是最抽象的接口,它和每个子系统的交互其实很多。尤其是对于kobject和设备驱动,他们都是2.6的内核的新的特性,他们都和sysfs相互纠缠着使用。这方面的知识我还要自己去脑补,比如下面这些。
历史
sysfs源自ramfs(大概是linux内核2.4版的时候出现的),ramfs的诞生或许只是为了验证当时刚刚出现的VFS文件系统。这为后来写memory-based的文件系统打下了基础。sysfs原来叫做ddfs(device driver fs),本来是用于作为新诞生的设备模型(device model)的调试工具(debug tool)的。而在这之前,调试的代码是使用procfs的,通过输出一个设备tree来进行相关的调试。不过大概是因为设备模型的出现,加上Linus
Torvarlds不停的督促,于是就有了这个文件系统。
在2.5.1的时候,它把名字改为了driverfs,又隔了一年,因为这个设备模型和driverfs证明了对其他的子系统也有作用,kobject这个数据结构被用来提供一种中心化的控制机制,最后从driverfs改名字为sysfs,来表明它对所有的子系统是没有偏见的。
相关内核知识
note:很多都可以参考《linux设备驱动程序》的第十四章设备模型http://oss.org.cn/kernel-book/ldd3/ch14.html。
Kobject
对每个在sysfs中发现的目录,有一个kobject潜伏在内核某处,每个kobject输出一个或者多个属性,它出现在kobject的sysfs目录,作为包含内核产生的信息的文件。sysfs文件系统所读写的信息是存放在kobject中的;
那么怎么理解kobject所在的位置呢?Kobject是基础的结构,它保持设备模型在一起,初始的它作为一个简单的引用计数,但是他的责任随着时间增长,并且有了它自己的战场;一个kobject,很少对它自己感兴趣,它存在仅仅是为了结合一个高级对象到设备模型。Kobject被嵌入到其他结构(比如bus和device,相当于cpp的容器)中,kobject结构的作用相当于C++中的基类。因为C语言没有直接表达继承的功能,因此将一个结构嵌入到另一个结构的事情必须使用(如device,bus和drivers都是典型的容器。这些容器就是通过kobject连接起来了,组成一个树状结构)。
但是c语言这种结构嵌入的设计方式,常常会出现一些问题,比如在这里,对于一个struct kobject指针,什么是包含这个结构的指针呢?这或许可以使用内核中常用的宏container_of。
设备驱动和设备模型
机器上有各种各样的设备,设备需要设备驱动来使得应用程序可以使用它。在2.5内核开发循环中,是为内核创建一个统一的设备模型。这需要一个通用的描述系统结构的抽象。linux设备模型是一个复杂的数据结构,设备模型代码负责这些方面,而不强加于驱动作者之上。和设备模型的直接交互通常由其他各种内核子系统处理。通过sysfs和用户空间交互也是一个设备模型的功能。
Kset和subsystem
虽然说kobject可以类比是对象的基类,那么kset则像是kobject的基类,顶层容器类。当我们读或者写一个属性的时候,sysfs会访问一个特殊的结构,通过kobject来访问的ksets结构。这个结构包括一些基本的读写特殊类型的kobject属性的操作。这些函数把kobject和它的属性翻译成更高级别的对象,然后传递给show和store函数。一个kset包含一个子系统指针(称为subsys),那么什么是子系统呢?
一个子系统是作为一个整体对内核一个高级部分的代表. 子系统常常(但是不是一直)出现在 sysfs 层次的顶级. 一些内核中的例子子系统包括block_subsys(/sys/block, 给块设备), devices_subsys(/sys/devices, 核心设备层次),一个子系统由一个简单结构代表:
struct subsystem { struct kset kset; struct rw_semaphore rwsem; };
一个子系统, 因此, 其实只是一个对 kset 的包装, 有一个旗标丢在里面.
设备驱动 and 字符设备驱动
计算机本科的操作系统课程应该要求写过设备驱动程序。用mknode命令创建设备文件。然后通过内核提供的接口来注册字符设备,主要是完成几个参数的提供(主设备号,名字和文件操作的数据结构的填充)。随着内核功能的增加,这个结构的定义也越来越复杂,不过我们写驱动的时候也只要写一些最基本的。
加载sysfs:mount和fstab
加载mount -tsysfs sysfs /sys
/etc/fstab;fstab包含了文件系统和存储设备的信息。同时fsck,mount等命令都利用该文件。
Sysfs文件系统导航
# ll /sys
total 0
drwxr-xr-x 2 root root 0 Nov 21 17:09 block
drwxr-xr-x 19 root root 0 Nov 21 17:09 bus
drwxr-xr-x 56 root root 0 Nov 21 17:09 class
drwxr-xr-x 4 root root 0 Nov 21 17:09 dev
drwxr-xr-x 14 root root 0 Nov 21 17:09 devices
drwxr-xr-x 5 root root 0 Nov 21 17:09 firmware
drwxr-xr-x 5 root root 0 Nov 21 17:09 fs
drwxr-xr-x 5 root root 0 Nov 21 17:09 hypervisor
drwxr-xr-x 7 root root 0 Nov 21 17:09 kernel
drwxr-xr-x 117 root root 0 Nov 21 17:09module
drwxr-xr-x 2 root root 0 Nov 21 17:10 power
最高层次的目录表示了代表了注册了sysfs的主要的几个子系统。这些目录在系统开启的时候,当这些子系统把他们自己注册到kobject core的时候就创建了。当这些子系统初始化后,他们开始去发现对象(注册到他们分别的目录)。下面将分别介绍。
Block:块设备子系统,包含了系统发现的每个块设备的信息,然后在每个块设备的子目录中包含了描述这个块设备的属性的文件,包括块设备的大小和dev_t号码。也有指向实际的物理设备的符号链接。并且,还有一个目录是关于IO调度器的接口的,主要是一些统计信息和一些可以用于优化系统性能的特征。
然后是一个块设备的分区。主要是关于这个分区的一些只读的属性。
Bus:总线子系统包含了内核支持注册的每种物理总线类型。每种总线类型包含了两个子目录,分别是devices和drivers。Devices目录包含了整个系统的那种总线的发现的每个设备。驱动目录包含了注册了这种总线的所有的设备驱动,每种设备的驱动有一个目录,使得可以查看和操作驱动的一些参数。
Class:代表了每个设备注册内核的每个设备class。一个设备class描述了一个设备的功能类型。一个设备可能有不止一个逻辑功能。每一个class和class对象可能包含一些属性(暴露的参数)用来描述和控制class object。
Devices:包含了一个全局的设备层次结构。因为设备都是按照上级和下级的关系出现的。有两种特殊类型的设备,一个是平台设备,另外一个是系统设备。
Firmware:包含处理固件的对象和属性。
Module:每个加载到内核的模块。
Power:power子系统
通用内核信息
初始化
利用的是fs/sysfs/mount.c的内容,通过sysfs_init这个函数来初始化的。这个函数被VFS的初始化函数直接调用(很早就被调用了,因为很多的subsystems都依靠于sysfs来注册他们的对象)
这个函数主要做了三件事情:
1. creat a kmem_cahe:这个cache用来sysfs_dirent对象的分配。(我们知道kmalloc和kfree用来实现数据结构的使用和释放,而更加高效的方式就是使用这种slab高速缓存管理器)。
sysfs_dir_cachep= kmem_cache_create("sysfs_dir_cache",sizeof(struct sysfs_dirent), 0, 0, NULL);
当需要分配一个你需要的结构体空间的时候,只需要调用kmen_cache_alloc函数。
2. 在VFS中注册。
register_filesystem(&sysfs_fs_type);
3. 内部的挂载。做这个是为了保证其他的内核代码总是可以使用sysfs,即使是在启动阶段。
sysfs_mnt= kern_mount(&sysfs_fs_type);
内核配置
Sysfs在默认情况下是被编译进了内核的。他依靠于CONFIG_SYSFS这个内核选项。(只是在CONFIG_EMBEDDED这个选项设置了的时候。这个菜可以进行自定义的配置。)
内核接口overview
对内核代码可见的函数大概分为三类;除了这些还有一些特殊类型的,那就是二进制属性和属性组。
1.内核对象(目录)
2.对象属性(常规文件)
3.对象之间的关系(符号链接)
对象的属性
对象的属性在外表现为sysfs中的常规文件,使用struct attribute数据结构来表示。它的函数主要有sysfs_create(remove,update)_file,文件的创建当然是名字,和mode,然后就是module的owner(这个待会再仔细说),然后就是创建在哪个目录下(由kobject的位置表示)。
关于属性和模块的关系?
owener是由create_file的调用者设定的,来指向这个属性代码所处的模块.比如说,网络设备有很多统计信息,通过sysfs表达为属性信息。这种统计属性应该放在外部模块中,这样就不需要加载了,能够使得网络设备更好的发挥作用。当这个外部模块加载后,里面包含的属于每一个注册的网络设备的属性都创建起来。这个模块也可以在任何时候卸载,将每个网络设备的属性从sysfs中移除掉。
当这个属性文件被访问的时候,owner这个字段是用来做引用计数的(通过update函数).VFS调用的属性文件操作通过sysfs内部函数设置。
包装的对象属性
struct attribute(数据结构和相应的函数)并没有包含读和写的属性。sysfs没有指定这些函数的格式和参数,这是一个显示的设计。
使用sysfs属性的子系统创建了一种新的数据类型用来包装struct attribute。通过定义一个包装纸数据结构struct device_attribute,这种数据我也没能力在这里描述了。这个可以让我们保护下层的代码,免受sysfs细节的影响。
当一个属性被读写的时候,sysfs通过kobject使用一个特殊的数据结构,叫做kset。这个结构包括基本的读写属性(特殊类型的kobjects)的操作,这些函数将kobject和属性转化为更高级别的对象,然后再把这些对象传送给show和store函数指针。再次说明,这帮助你确保了类型的安全,因为它保证了下层接受函数接收的参数是更高级别的对象。
一般来说,程序员都不喜欢对象之间的类型转换,因为如果数据结构中的字段的位置改变的话,这很容易导致很难发现的bugs。通过内核中帮助函数,执行迁移指针的减法操作来进行对象间类型转换,从而保证了对象类型安全(即使字段位置可能改变)。
读写属性
sysfs一定会尽量使得读写属性越简单越好,当一个属性文件打开后,一个页大小的page被分配用来在用户空间和内核空间来传递数据,当一个属性读的时候,这个buffer会被传送给下游的函数,比如show(是负责用来填充数据的,并且合适的格式化的)。这个数据然后被传递到用户空间。
而当写一个sysfs属性文件的时候,数据首先被复制到内核buffer,然后传给下游的函数(along with buffer的大小)。这个函数负责解析数据。一般认为写入buffer的数据是ascii字符,也认为写入的数据的大小不到一页的大小。如果按照传统,一个文件的大小应该不超过一个页。
然后就是属性的更新啦,如果一个属性的值改变了,内核代码可以通过update函数通知用户空间程序。
内核对象之间的关系
sysfs文件系统有通常的树结构,它代表的kobjects的层次组织。但是内核中对象间的关系常常比那个复杂。(符号链接)符号链接是为了防止冗余信息的出现。例子,考虑一个pci网络设备和驱动,当系统启动时,这个pci设备被发现,然后给他创建了一个sysfs目录,即使它还没有绑定到一个驱动。过了一些时间后,这个网络驱动才加载,这个驱动也可能不和任何设备发生绑定。这是一个和物理pci设备不一样的对象类型,所以也会给它创建一个新的目录。在driver的目录下面就会有一个符号链接,指向他绑定的真正的设备。
·属性组
为什么要有属性组呢?为了在一次调用的时候就可以轻松的添加或者删除一组属性。这个attribute_group数据结构和相应的函数也是有定义的,也可能是一个子目录。比如说网络设备的统计信息就是一个很好的例子。
二进制属性
这是一种特殊类型的常规文件。和procfs的接口有点像,因为有的需要已知的特殊的格式,比如说PCI配置空间。
现有的sysfs的用户
比如udev,使用了libsysfs的c语言中的库;另外pciutils也使用了sysfs来访问pci配置信息。