sysfs文件系统是内核2.6的1个特性,通过in-memory文件系统的方式把内核对象(object)信息暴露给用户程序。参考:https://www.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols⑵005/mochel.pdf
note:本文是对sysfs的1些简单介绍,有很多不足的地方----对代码的结构都介绍的不够详细,相干代码在内核fs/sysfs目录下
sysfs是1种代表内核对象的机制,内核对象的属性和内核对象相互之间的关系。从sysfs的目的来看,是把本来在procfs中的,关于装备的部份独立出来,以“装备层次结构”的情势显现。它提供了两个组件,1个内核编程接口来把这些item通过sysfs暴露出来;另外1个组件是用户接口,来view和操作这些item(对应着相应的内核对象)。从内核的角度来看,sysfs是作为1个基础架构的核心组件,所以接口很简单,要履行的任务也很简单。
sysfs是内核和用户空间的1个通道。用户空间的程序可以通过很多方式来利用这个信息。比如说现有的利用就有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的,通过输出1个装备tree来进行相干的调试。不过大概是由于装备模型的出现,加上Linus Torvarlds不停的催促,因而就有了这个文件系统。
在2.5.1的时候,它把名字改成了driverfs,又隔了1年,由于这个装备模型和driverfs证明了对其他的子系统也有作用,kobject这个数据结构被用来提供1种中心化的控制机制,最后从driverfs改名字为sysfs,来表明它对所有的子系统是没有偏见的。
对每一个在sysfs中发现的目录,有1个kobject潜伏在内核某处,每一个kobject输出1个或多个属性,它出现在kobject的sysfs目录,作为包括内核产生的信息的文件。sysfs文件系统所读写的信息是寄存在kobject中的;
那末怎样理解kobject所在的位置呢?Kobject是基础的结构,它保持装备模型在1起,初始的它作为1个简单的援用计数,但是他的责任随着时间增长,并且有了它自己的战场;1个kobject,很少对它自己感兴趣,它存在仅仅是为了结合1个高级对象到装备模型。Kobject被嵌入到其他结构(比如bus和device,相当于cpp的容器)中,kobject结构的作用相当于C++中的基类。由于C语言没有直接表达继承的功能,因此将1个结构嵌入到另外一个结构的事情必须使用(如device,bus和drivers都是典型的容器。这些容器就是通过kobject连接起来了,组成1个树状结构)。
但是c语言这类结构嵌入的设计方式,常常会出现1些问题,比如在这里,对1个struct kobject指针,甚么是包括这个结构的指针呢?这也许可使用内核中经常使用的宏container_of。
机器上有各种各样的装备,装备需要装备驱动来使得利用程序可使用它。在2.5内核开发循环中,是为内核创建1个统1的装备模型。这需要1个通用的描写系统结构的抽象。linux装备模型是1个复杂的数据结构,装备模型代码负责这些方面,而不强加于驱动作者之上。和装备模型的直接交互通常由其他各种内核子系统处理。通过sysfs和用户空间交互也是1个装备模型的功能。
虽然说kobject可以类比是对象的基类,那末kset则像是kobject的基类,顶层容器类。当我们读或写1个属性的时候,sysfs会访问1个特殊的结构,通过kobject来访问的ksets结构。这个结构包括1些基本的读写特殊类型的kobject属性的操作。这些函数把kobject和它的属性翻译成更高级别的对象,然后传递给show和store函数。1个kset包括1个子系统指针(称为subsys),那末甚么是子系统呢?
1个子系统是作为1个整体对内核1个高级部份的代表. 子系统常常(但是否是1直)出现在 sysfs 层次的顶级. 1些内核中的例子子系统包括block_subsys(/sys/block, 给块装备), devices_subsys(/sys/devices, 核心装备层次),1个子系统由1个简单结构代表:
1个子系统, 因此, 其实只是1个对 kset 的包装, 有1个旗标丢在里面.
计算机本科的操作系统课程应当要求写过装备驱动程序。用mknode命令创建装备文件。然后通过内核提供的接口来注册字符装备,主要是完成几个参数的提供(主装备号,名字和文件操作的数据结构的填充)。随着内核功能的增加,这个结构的定义也愈来愈复杂,不过我们写驱动的时候也只要写1些最基本的。
加载mount -tsysfs sysfs /sys
/etc/fstab;fstab包括了文件系统和存储装备的信息。同时fsck,mount等命令都利用该文件。
# 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号码。也有指向实际的物理装备的符号链接。并且,还有1个目录是关于IO调度器的接口的,主要是1些统计信息和1些可以用于优化系统性能的特点。
然后是1个块装备的分区。主要是关于这个分区的1些只读的属性。
Bus:总线子系统包括了内核支持注册的每种物理总线类型。每种总线类型包括了两个子目录,分别是devices和drivers。Devices目录包括了全部系统的那种总线的发现的每一个装备。驱动目录包括了注册了这类总线的所有的装备驱动,每种装备的驱动有1个目录,使得可以查看和操作驱动的1些参数。
Class:代表了每一个装备注册内核的每一个装备class。1个装备class描写了1个装备的功能类型。1个装备可能有不止1个逻辑功能。每个class和class对象可能包括1些属性(暴露的参数)用来描写和控制class object。
Devices:包括了1个全局的装备层次结构。由于装备都是依照上级和下级的关系出现的。有两种特殊类型的装备,1个是平台装备,另外1个是系统装备。
Firmware:包括处理固件的对象和属性。
Module:每一个加载到内核的模块。
Power:power子系统
初始化
利用的是fs/sysfs/mount.c的内容,通过sysfs_init这个函数来初始化的。这个函数被VFS的初始化函数直接调用(很早就被调用了,由于很多的subsystems都依托于sysfs来注册他们的对象)
这个函数主要做了3件事情:
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);
当需要分配1个你需要的结构体空间的时候,只需要调用kmen_cache_alloc函数。
2. 在VFS中注册。
3. 内部的挂载。做这个是为了保证其他的内核代码总是可使用sysfs,即便是在启动阶段。
Sysfs在默许情况下是被编译进了内核的。他依托于CONFIG_SYSFS这个内核选项。(只是在CONFIG_EMBEDDED这个选项设置了的时候。这个菜可以进行自定义的配置。)
对内核代码可见的函数大概分为3类;除这些还有1些特殊类型的,那就是2进制属性和属性组。
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没有指定这些函数的格式和参数,这是1个显示的设计。
使用sysfs属性的子系统创建了1种新的数据类型用来包装struct attribute。通过定义1个包装纸数据结构struct device_attribute,这类数据我也没能力在这里描写了。这个可让我们保护下层的代码,免受sysfs细节的影响。
当1个属性被读写的时候,sysfs通过kobject使用1个特殊的数据结构,叫做kset。这个结构包括基本的读写属性(特殊类型的kobjects)的操作,这些函数将kobject和属性转化为更高级别的对象,然后再把这些对象传送给show和store函数指针。再次说明,这帮助你确保了类型的安全,由于它保证了下层接受函数接收的参数是更高级别的对象。
1般来讲,程序员都不喜欢对象之间的类型转换,由于如果数据结构中的字段的位置改变的话,这很容易致使很难发现的bugs。通过内核中帮助函数,履行迁移指针的减法操作来进行对象间类型转换,从而保证了对象类型安全(即便字段位置可能改变)。
读写属性
sysfs1定会尽可能使得读写属性越简单越好,当1个属性文件打开后,1个页大小的page被分配用来在用户空间和内核空间来传递数据,当1个属性读的时候,这个buffer会被传送给下游的函数,比如show(是负责用来填充数据的,并且适合的格式化的)。这个数据然后被传递到用户空间。
而当写1个sysfs属性文件的时候,数据首先被复制到内核buffer,然后传给下游的函数(along with buffer的大小)。这个函数负责解析数据。1般认为写入buffer的数据是ascii字符,也认为写入的数据的大小不到1页的大小。如果依照传统,1个文件的大小应当不超过1个页。
然后就是属性的更新啦,如果1个属性的值改变了,内核代码可以通过update函数通知用户空间程序。
sysfs文件系统有通常的树结构,它代表的kobjects的层次组织。但是内核中对象间的关系常常比那个复杂。(符号链接)符号链接是为了避免冗余信息的出现。例子,斟酌1个pci网络装备和驱动,当系统启动时,这个pci装备被发现,然后给他创建了1个sysfs目录,即便它还没有绑定到1个驱动。过了1些时间后,这个网络驱动才加载,这个驱动也可能不和任何装备产生绑定。这是1个和物理pci装备不1样的对象类型,所以也会给它创建1个新的目录。在driver的目录下面就会有1个符号链接,指向他绑定的真实的装备。
为何要有属性组呢?为了在1次调用的时候就能够轻松的添加或删除1组属性。这个attribute_group数据结构和相应的函数也是有定义的,也多是1个子目录。比如说网络装备的统计信息就是1个很好的例子。
这是1种特殊类型的常规文件。和procfs的接口有点像,由于有的需要已知的特殊的格式,比如说PCI配置空间。
比如udev,使用了libsysfs的c语言中的库;另外pciutils也使用了sysfs来访问pci配置信息。