Sysfs overview

最近想把驱动模型彻底整明白,翻译了几篇文档,这篇sysfs是Patrick Mochel 2003年写的,现在代码有些变化,不过可以让我们基本了解sysfs这个东西。发现个有趣的事情,一般看kernel的东西都会在文件头部看到作者的信息,然后google.com搜这些大侠的名字和相关模块都能找到相关的知识~~

sysfs说穿了是kernel和userspace打交道的一个借口,kernel的结构被export到sysfs,通过对sysfs的操作用户空间就能与kernel进行交互~~


一、摘要
1、Sysfs是一个in-memory的文件系统,是linux2.6内核用来向用户空间传递信息的一个接口。Sysfs的内部目录结构是很严格的基于kernel内部的数据结构的。在文件系统内部的所有文件一般都是ASCII文件,每一个值对应着一个文件。


二、介绍
Sysfs是一种表达kernel object的机制,包括object的相关值,以及他们之间相互关系。它提供了两个接口:一个kernel编程接口(Internal),kernel可以使用这个接口来将内核数据export到用户空间;另一个是用户接口(External),供用户观察和操作来自kernel export出来的数据。下面是kernel内部数据结构与sysfs文件系统之间的映射关系:
INTERNAL EXTERNAL
Kernel Objects Directoies
Object Atrributes Regular Files
Object Relationships Symbolic Links

Sysfs是内核基础结构很核心一部分,可以用简单的接口来处理简单的事情,不过和大部分内核基础结构一样,它很抽象,代码量大而复杂,所以这里在讲述细节之前先看看sysfs的一个历程。首先我们简单而动人的描述一下Sysfs的起源,然后如何mount和访问sysfs,再然后描述一下sysfs的目录组织以及sysfs subsystem的一个布局。这样对于一个读者就可以能清晰地理解通过sysfs发布的内核数据结构的组织结构,以及数据结构的具体内容。这篇文档的主要目的是提供一个对sysfs内部接口的概述,包括数据结构以及用来export linux struct到用户空间的函数。这里的linux struct就是前面提到的Internal3个结构。这篇文档还描述了两个额外的regular files interface:属性组(Attribute Groups)和二进制属性(Binary Attributes)


三、Sysfs的历史
Sysfs是一个in-memory的文件系统,基于最初的ramfs。ramfs出现在2.4kernel稳定的时候,它展示了用当时也是新的VFS来写一个简单的文件系统是一件多么容易的事情。sysfs最早是被称为ddfs(device driver filesystem),主要用来debug一些新的驱动模块,debug代码使用procfs来export device tree,但是在严厉的linux torvalds的要求下,最后它转而使用一个基于ramfs的新型文件系统来export

device tree。在新的驱动模型被合并到kernel2.5的时候,它又被更名为driverfs。然后在接下来的kernel2.5的开发过程中,驱动模型和driverfs的一些基础特性被证明是对其他子系统也是有用的。然后这个是kobjects被开发出来提供一个核心的object管理机制,这个时候driversfs才被更名为sysfs。


四、Mounting Sysfs
Sysfs可以像其他的memory-based文件系统一样在用户空间被mount:
mount -t sysfs sysfs /sys
Sysfs也可以放在/etc/fstab里面在boot的时候自动被mount,大多数基于kernel2.6的linux版本在/etc/fstab里面有sysfs的注册:
sysfs /sys sysfs noauto 0 0


五、Sysfs导航
Sysfs由一些目录,文件以及符号链接构成,我们可以大致看看其目录结构:
1、Sysfs根目录下是所有注册到sysfs的子系统:
|-- block
|-- bus
|-- class
|-- dev
|-- devices
|-- firmware
|-- fs
|-- kernel
|-- module
`-- power

2、block目录下的所有子目录代表着系统中所有被发现的块设备。
3、bus目录下的每个子目录都是kernel支持并已经注册的总线类型。一般来说每个子目录(总线类型)下包含两个目录,一个是devices另外一个是drivers,devices下是这个总线类型下的所有设备,这些设备都是符号链接他们分别指向真正的设备sys/devices/xxx; derivers下是所有注册在这个总线上的driver,每个driver子目录下是一些可以观察和修改的driver参数。
4、class目录下包含所有注册在kernel里面的设备类型,每一个设备类型表达具有一种功能的设备。每个设备类型子目录下都是这种设备类型的各种设备的符号链接,这些链接指向sys/devices/xxx下的具体设备。设备类型和设备没有一一对应的关系,一个物理设备可能具备都中设备类型。
5、devices目录是全局设备结构体系,他包含所有被发现的注册在各种总线上的各种物理设备。一般来说所有的物理设备都按其在总线上的拓扑结构来显示,但这里有两个例外,就是platform devices和system devices。platform devices一般是挂在芯片内部高速或者低速总线上的各种控制器和外设,他们能被CPU直接寻址。system devices不是外设,他是芯片内部的核心结构,比如CPU,timer等,

他们一般没有相关的driver,但是会有一些体系结构相关的该吗来配置他们。
6、firmware目录包含对firmware object和属性进行操作和观察的接口?
7、module目录包含了所有被载入kernel的模块,一般来说每个目录下都一个recnt属性,来记录现在的有多少个对这个模块的引用。
8、power目录是对正在使用的power子系统的描述。


六、Sysfs的代码组织
Sysfs所有的代码都在fs/sysfs/里面,它还有一个公共的函数接口定义在include/linux/sysfs.h,我

们来看看fs/sysfs里面的具体内容:
fs/sysfs/sysfs.h sysfs的内部头文件,包含一些供本地调用的函数声明;
fs/sysfs/mount.c 包含一些和VFS进行交互的数据结构,方法,以及必要的初始化函数;
fs/sysfs/inode.c 这个文件包含了一些共享的内部函数,用来申请和释放核心的文件系统objects
fs/sysfs/dir.c 这个文件包含一些用来在sysfs体系结构中创建和删除目录的接口;
fs/sysfs/file.c 这个文件包含了一些用来在sysfs体系结构中创建和删除常规ASCii文件的接口;
fs/sysfs/group.c 这个文件包含了一些用来在sysfs中同时创建多个常规文件的帮助;
fs/sysfs/symlink.c 这个文件包含一些用来在sysfs体系结构中创建和删除符号链接的接口;
fs/sysfs/bin.c 这个文件包含了一些用来在sysfs体系结构中创建和删除二进制文件的接口;


七、Sysfs的初始化
Sysfs使用fs/sysfs/mount.c里面的sysfs_init函数来进行初始化,这个函数被VFS初始化代码直接调用,因为很多依赖sysfs的子系统在初始化的时候都需要在sysfs里面注册object,所以sysfs的初始化一定要尽早。这个初始化过程主要完成3个事情:
1、创建一个kmem_cache,这个cache主要用来分配sysfs_dirent objects;
2、在VFS里面注册,使用register_filesystem()来注册一个sysfs_fs_type类型的文件系统,并命令为sysfs;
3、在内部将sysfs mount上,这是为了确保sysfs对其他kernel code是一直有效的,甚至在启动的过程中,而不需要用户显示的来mount;一旦上面的三个步骤完成,sysfs的功能就全部起来了,可以被其他的内部代码使用了。


八、Sysfs的配置
Sysfs默认会被编译进kernel,主要依赖于CONFIG_SYSFS这个选项。CONFIG_SYSFS只有在CONFIG_EMBEDDED被设置的情况下才是可见的。


九、kernel相关接口概述
Sysfs相对于其他kernel code的接口根据想export到用户空间的object的种类的不同分为3种:
Kernel Objects (Directories)
Object Attributes (Regular Files)
Object Relationships (Symbolic Links)
另外还有两种用来export属性到用户空间的子类型,这两个子类型主要是为了使用户export其他文件更为简单,这两种类型都会在sysfs里面创建常规文件:
Attribute Groups
Binary Files
对所有的sysfs接口函数,第一个参数都是kobject。sysfs-core假定在函数调用过程中kobject一直都是有效的,调用者必须确认在调用过程中有可能的锁会修改object的内容。对于基本上所有的接口函数(sysfs_create_dir除外),sysfs-core都假定k->dentry是一个有效的dentry,并且已经被分配空间并初始化。
所有对sysfs接口函数的调用都必须在进程上下文中,而且不能在获得自旋锁的情况下对其进行调用,因为对这些函数的调用可能会导致sleep,所以一般都使用信号量。


十、kernel objects
kernel objects通过sysfs以目录的方式来export到用户空间,相关的函数主要有:
int sysfs_create_dir(struct kobject * k);
void sysfs_remove_dir(struct kobject * k);
int sysfs_rename_dir(struct kobject *, const char * new_name);
sysfs_create_dir是唯一一个不需要注意kobject相关的directory是否创建的函数,因为是由它来创建这个directory的,这个函数调用必须有效的两个参数分别是k->parent和k->name ;

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;
};

创建目录(sysfs_create_dir)
由kobject参数来决定directory的位置以及名字,一个新的目录一般会建立在k->parent的目录下面,k->parent默认可以为空,这样目录就会建立在sysfs的顶层目录下。应该尽量避免在顶层目录下建立目录,除非你使用kobject/sysfs model实现了或者移植了一个新的顶层子系统。在所有的情况下,如果你将一个kobject注册到一个子系统,subsystem会根据对这个kobject的了解填充那块区域(k->parent ?).
当sysfs_create_dir被调用,会为要创建的目录allocate一个k->dentry,这个dentry用于VFS的各种操作。这样一个用户可见的节点就被创建了,sysfs会根据内部实现为这个新目录填充需要的file_operations,用来供VFS标准方法调用。(现在应该是没有dentry了,这里应该是sysfs_dirent)

删除目录(sysfs_remove_dir)
对一个目录的删除会同时删除这个目录下所有的常规文件

重命名目录(sysfs_rename_dir)
当这个函数调用的时候,sysfs会为kobject重新申请一个dentry,然后改变这个kobject的name选项。


十一、Object Attributes
objtects的属性可以通过sysfs的常规文件export到用户空间,使用struct attribute的结构体。
int sysfs_create_file(struct kobject *, const struct attribute *);
void sysfs_remove_file(struct kobject *, const struct attribute *);
int sysfs_update_file(struct kobject *, const struct attribute *);

struct attribute {
const char *name;
struct module *owner;
mode_t mode;
};

struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};

sysfs_create_file()使用attribute里面的name和mode来创建文件节点,文件所在的目录由kobject来决定。attribue的owner指向这些属性属于的模块,而不是这个kobject所属的模块,因为attribute文件可以在任何时候创建或者删除,他们不需要在这个kobject注册的时候就一定要被创建。举一个例子,一个网络设备可能有很多属性可以在sysfs里面被export成文件,可能这些attributes属于一个外部

模块,即使没有这个模块网络设备也能正常工作。当这个外部模块被载入的时候,它所具有的attribues也会被创建,从而使得所有的网络设备具有这些属性,同时这个外部模块也可以在任何时候被卸载,这样这些属性文件又会被删除。因此这些attributes的owner必须属于他们所属的模块,而不是kobject所属的模块。
owner选项用于这个attribute被访问的时候引用计数。从VFS过来的函数调用会被sysfs转换成内部调用,这样sysfs就能够跟踪每一个调用并进行必要的操作。当一个attribute文件被打开的时候,sysfs会分别增加kobject所属的目录和拥有这个attribute的模块的引用计数。这样就保证在attribute文件被访问的时候,kobject不会被释放,模块也不会被卸载。

attribute并没有定义对其内容进行读写的方法,sysfs也没有明确定义这些函数的格式或者参数。这是一个设计上的考虑,主要用来简化下层方法的实现(言外之意这些访问的函数是由自己来设计的?)。子系统会使用sysfs的attribute来创建一个新的数据结构,将attribute嵌套在其里面,就像前面提到的obj_attribute,这种实现可以有效保护底层的实现细节。
当我们读写一个attribute的时候,sysfs会通过kobject访问kset,kset包含一些针对特定类型kobject attribute的读写函数,这些函数会完成从kobject以及attribute到高层object和attribute的转换,然后将转化后的kobject和attribute传给show/store函数(应该说的是container_of(),但看代码这个转换应该定义在ktype里面,container_of应该也和kset没什么关系啊?)
sysfs用尽量简单的方式来读写attribute。当一个attribute被打开的时候,会申请一个PAGE_SIZE大小的buffer用来在kernel和userspace。当需要读取一个attribute的时候,这个buffer会被传送到底层函数(比如这里的obj_attribute::show),底层函数会按照相应的格式去填充这个buffer。当要写一个sysfs attribute文件的时候,数据会首先写到kernel buffer,然后会传到底层函数进行处理。


十二、Object Relationships
object relationship使用符号链接实现:
int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);
void sysfs_remove_link(struct kobject *, char *name);
其中第一个参数是符号链接所在的目录,第二个是符号链接到的目录,name是符号链接的文件名。


十三、Attribute Groups
Attribute group说穿了就是一组attributes的集合,方便一组attributes的同时加入和删除。
struct attribute_group{
char *name;
struct attribute **attrs;
};

int sysfs_create_group(struct kobject *, const struct attribute_group *);
void sysfs_remove_group(struct kobject *, const struct attribute_group *);

attribute_group中的name选项是可选的,如果存在就会创建一个kobject文件夹来存放所有的attributes文件。加入attributes的过程中只要一个出错,则所有已加入的attribute也会被删除,并返回出错信息。一个关于attribute_group很好的例子是net/core/net-sysfs.c


十四、binary attributes

PS:

这里补充一点文件系统的基础知识,让我们从逻辑上产生一个连贯的理解。当然VFS这个东西我没仔细看过,想看的可以去看Documentation/filesystems目录下的文档,我这里只是想把一些只是串联起来。

VFS(Virtual File System or Virtual Filesystem Switch)虚拟文件系统,实现了对各种文件系统进行访问的统一接口。它于文件系统和MTD相对memory,FB相对于display的概念是一样的。文件系统这个东西理解起来有点玄乎,linux把一切资源都看成文件,从狭义上我们可以将文件理解为存储在disk上的一些信息,从广义上来说,一切有组织有次序地存放在memory里面的信息都可以理解为文件。

从最简单的思维来理解,一个文件系统我们需要的是目录结构,以及对各种文件的一组操作。从面向对象的角度来说,就是两个class,一个目录,一个文件,两个class分别定义了对这个class的操作。这样我们就好理解了,VFS为用户空间提供了统一的接口,比如说open/write/close/read之类的,而具体的实现必须由具体的文件系统来实现。VFS为这些文件系统的实现也提供了统一的接口,简单的来说就是结构体的实现:file_operations dentry_operations inode_operations 这3个结构体分别对应文件,目录,以及索引结点的操作。这里的file是提供给用户空间的接口,由具体的文件系统来实现;dentry和inode这两个概念是VFS提出的公共接口,每个文件系统都必须具体实现其内部信息。其中dentry存储着文件名,文件的上级目录等信息,由它来构成了文件系统的树形结构关系,而inode则记载了具体的文件信息,文件在存储介质上的分布(比如说在memory里面的哪个块哪个页之类的)。dentry和inode是多对一的关系,因为一个文件可能对应着多个文件名(硬链接软链接的概念)。dentry和inode都是存储在内存中的信息,它的原始信息一般都有一个载体,比如说disc上的某个位置的数据,或者干脆它就没有载体,断电后它的数据就会丢失,实际上我们打开一个文件,VFS都会在内存中建立一个dentry和一个inode。

当我们创建一个文件系统的实例的时候,比如说mount某个文件系统,VFS都会创建一个super_block的结构体,这个结构体记载这个文件系统的整体信息,比如说大小,块大小等等,这个super_block实际上就是整个文件系统在内存中的一个抽象,对应着他有一个super_operations。另外每当我们mount一个文件系统的时候都会创建一个vfsmount的结构体,当我们要访问到这个文件系统mount到的文件的时候就会访问这个vfsmount,然后找到目标文件系统。

你可能感兴趣的:(view)