sysfs原理
1 sysfs简介
sysfs是一个in-memory的文件系统,是内核(对象)用来向用户空间传递信息的一个接口。sysfs的内部目录结构是很严格的基于kernel内部的数据结构的。在文件系统内部的所有文件一般都是ASCII文件,每一个值对应着一个文件。
在sys中,提供了两个接口:一个kernel编程接口(Internal),kernel可以使用这个接口来将内核数据export到用户空间;另一个是用户接口(External),供用户观察和操作来自kernelexport出来的数据。下面是kernel内部数据结构与sysfs文件系统之间的映射关系:
Kernel Objects:Directoies(对应sys下的一个目录);
Object Atrributes: RegularFiles(对应sys下的一个文件,文件中值就是属性值);
Object Relationships:Symbolic Links(对应sys下的链接文件)。
2 sysfs的结构
在ubuntu11.04(linux-2.6.38)的根目录下有一个文件夹叫做sys,这个就是sysfs。
图1 sysfs
bus –目录下的每个子目录都是kernel支持并已经注册的总线类型。一般来说每个子目录(总线类型)下包含两个目录:一个是devices,另外一个是drivers。devices下是这个总线类型下的所有设备,这些设备都是符号链接他们分别指向真正的设备/sys/devices/xxx;drivers下是所有注册在这个总线上的driver,每个driver子目录下是一些可以观察和修改的driver参数。在内核中对应的结构体为struct bus_type { … };
device -该目录是全局设备结构体系,他包含所有被发现的注册在各种总线上的各种物理设备。一般来说所有的物理设备都按其在总线上的拓扑结构来显示,但这里有两个例外,就是platform devices和system devices。platform devices一般是挂在芯片内部高速或者低速总线上的各种控制器和外设,他们能被CPU直接寻址。system devices不是外设,他是芯片内部的核心结构,比如CPU,timer等,他们一般没有相关的driver,但是会有一些体系结构相关的代码来配置他们。对应的结构体为struct device { … };
class -系统中设备的类型(声卡,网卡,显卡,输入设备等),每一个设备类型表达具有一种功能的设备。每个设备类型子目录下都是这种设备类型的各种设备的符号链接,这些链接指向/sys/devices/xxx下的具体设备。设备类型和设备没有一一对应的关系,一个物理设备可能具备几种设备类型。同一类中包含的设备可能连接到不同的总线,对应的结构体为struct class { … };
dev -在内核中注册的设备驱动程序,对应的结构体为struct device_driver{ … };
以上bus、device、class、dev是可感受到的对象,在内核中都用相应的结构体来描述。而实际上,按照面向对象的思想,程序需要抽象出一个最基本的对象,这就是设备模型的核心对象kobject。
3 挂载sysfs
sysfs可以像其他的memory-based文件系统一样在用户空间被mount:
$mount -t sysfs sysfs/sys
sysfs也可以放在/etc/fstab里面在boot的时候自动被mount,大多数基于linux发行版在/etc/fstab里面有sysfs的注册:
sysfs /sys sysfs noauto 0 0
4 sysfs的代码组织
sysfs所有的代码都在内核目录/fs/sysfs/里面,它还有一个公共的函数接口定义在内核目录/include/linux/sysfs.h,下面是内核目录/fs/sysfs/里面的具体内容:
sysfs.h:sysfs的内部头文件,包含一些供本地调用的函数声明;
mount.c:包含一些和VFS进行交互的数据结构,函数,以及必要的初始化函数;
inode.c:这个文件包含了一些共享的内部函数,用来申请和释放核心的文件系统的objects;
dir.c:这个文件包含一些在sysfs体系结构中创建和删除目录的接口;
file.c:这个文件包含了一些在sysfs体系结构中创建和删除常规ASCII文件的接口;
group.c:这个文件包含了一些在sysfs中同时创建多个常规文件的帮助;
symlink.c:这个文件包含一些在sysfs体系结构中创建和删除符号链接的接口;
bin.c:这个文件包含了一些在sysfs体系结构中创建和删除二进制文件的接口。
Kobject的所有代码都在内核目录/lib/kobject.c里面,头文件在内核目录/include/linux/kobject.h。
5 sysfs的初始化
sysfs使用内核目录/fs/sysfs/mount.c里面的sysfs_init函数来进行初始化,这个函数被vfs初始化代码直接调用,因为很多依赖sysfs的子系统在初始化的时候都需要在sysfs里面注册object,所以sysfs的初始化一定要尽早。这个初始化过程主要完成3件事:(1)创建一个kmem_cache,这个cache主要用来分配sysfs_dirent结构体的对象;
(2)在VFS里面注册,使用register_filesystem()来注册一个sysfs_fs_type类型的文件系统,并命名为sysfs;
(3)在内部将sysfs挂载上,这是为了确保sysfs对其他kernel code是一直有效的,甚至在启动的过程中,而不需要用户显示的来mount。
一旦上面的三个步骤完成,sysfs的功能就全部起来了,可以被其他的内部代码使用了。
6 sysfs的配置
sysfs默认会被编译进kernel,主要依赖于CONFIG_SYSFS这个选项。CONFIG_SYSFS只有在CONFIG_EMBEDDED被设置的情况下才是可见的。
7 kernel相关接口
sysfs的对象(object)可分为3种:
Kernel Objects (Directories在sysfs中以目录的形式出现)
Object Attributes (Regular Files在sysfs中以文件的形式出现)
Object Relationships (Symbolic Links在sysfs中以链接的形式出现)
另外还有两种用来导出属性到用户空间的子类型:
Attribute Groups
Binary Files
这两个子类型主要是为了使用户导出其他文件更为简单,这两种类型都会在sysfs里面创建常规文件。对所有的sysfs接口函数,第一个参数都是kobject。sysfs假定在函数调用过程中kobject一直都是有效的,调用者必须确认在调用过程中是否存在可能的锁会修改object的内容。对于基本上所有的接口函数(sysfs_create_dir除外),sysfs都假定kobj->sd是一个有效的目录项,并且已经分配空间并初始化。所有对sysfs接口函数的调用都必须在进程上下文中,而且不能在获得自旋锁的情况下对其进行调用,因为对这些函数的调用可能会导致sleep,所以一般都使用信号量。
8 Linux底层原理
8.1 sysfs目录、文件创建原理
在内核目录/fs/sysfs/dir.c定义了与目录操作有关的函数,例如sysfs_create_dir是用来创建目录的,sysfs_remove_dir则是用来删除目录的,在sysfs中,目录表示一个内核对象(kobject),因此这些函数都会有一个struct kobject的指针参数。同理,在内核目录/fs/sysfs/file.c定义了与文件操作相关的函数,例如sysfs_create_file是用来建立文件的,sysfs_remove_file是用来删除文件的,这些函数也需要一个struct kobject指针参数,因为在sysfs下,文件代表了目录(内核对象)的属性。
8.2 相关函数举例
下面以几个sysfs函数为例来说明。
8.1.1 sysfs_create_dir
该函数在内核目录/fs/sysfs/dir.c中定义为:
/**
* sysfs_create_dir - createa directory for an object.
* @kobj: objectwe're creating directory for.
*/
intsysfs_create_dir(struct kobject * kobj);
该函数会在/sys下建立文件夹,由kobj参数来决定目录的位置以及名字,目录会建立在kobj->parent的目录下面, kobj->parent可以为空,这样目录就会建立在sysfs的顶层目录下。程序应该避免在顶层目录下建立目录,除非已实现或者移植一个新的顶层子系统。该函数会为要创建的目录分配一个kobj->sd(struct sysfs_dirent),这个sd用于VFS的各种操作。这样一个用户可见的节点就被创建了,sysfs会根据内部实现为这个新目录填充需要的file_operations,用来供vfs标准函数调用。
8.1.2 sysfs_remove_dir
该函数在内核目录/fs/sysfs/dir.c中定义为:
voidsysfs_remove_dir(struct kobject * kobj)
该函数会在/sys下删除文件夹和其中的文件(属性),由kobj参数来决定目录的位置以及名字。
8.1.3 sysfs_rename_dir
该函数在内核目录/fs/sysfs/dir.c中定义为:
int sysfs_rename_dir(struct kobject *kobj, const char *new_name)
该函数会用来对目录重新命名。
8.2 kobject
kobject通过sysfs以目录的方式来导出到用户空间(在/sys/下以目录的形式出现),该结构体在内核目录/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; };
8.2.1 kobject的初始化
该过程比较复杂,但是必须的步骤如下:
①将整个kobject清零,通常使用memset函数。
②调用kobject_init函数。kobject_init函数还会调用kobject_init_internal函数,设置kobject的引用计数为1。
③调用kobject_add函数将kobj对象加入Linux设备层次。
8.2.2 与kobject有关的函数:
(1) kobject_init
该函数为kobject的初始化函数,它在内核目录/lib/kobject.c中被定义为:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
该函数参数是一个已经创建了的kobject对象的地址和该对象对应的kobj_type,该函数会将kobject对象的引用计数初始化为1(kref_init(&kobj->kref);),此外,kobj_type包含了kobject被释放时需要调用的函数以及属性文件列表,以及这些文件的读写函数。
(2) kobject_set_name
该函数在内核目录/lib/kobject.c中被定义为:
int kobject_set_name(struct kobject *kobj, const char *format, ...);
主要功能是设置指定kobject的名称,即name成员,参数format表示格式化的字符串,与printf的格式化字符串相同,用于填充name成员。
(3) kobject_get和kobject_put
这两个函数在内核目录/lib/kobject.c中被定义为:
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject * kobj);
这两个函数是对引用计数进行操作的,在kobject中,只要对象的引用计数存在,这个对象(和支持它的代码)就必须继续存在。kobject_get函数对计数加1,同时返回该对象的指针。而kobject_put函数将对象的引用计数减1,如果引用计数降为0,还会调用kobject_release释放该kobject对象。
(4) kobject_add
这个函数在内核目录/lib/kobject.c中被定义为:
int kobject_add(struct kobject * kobj);
该函数将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引用计数,在其parent指向的父目录下创建文件节点,并启动该类型内核对象的hotplug函数。
(5)kobject_init_and_add
这个函数在内核目录/lib/kobject.c中被定义为:
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
该函数实际上是将kobject_init()和kobject_add()合并在一起。
(6) kobject_create
这个函数在内核目录/lib/kobject.c中被定义为:
struct kobject *kobject_create(void)
该函数会使用kzalloc()动态地申请一个kobject结构体,并调用kobject_init()进行初始化。
(7) kobject_create_and_add
这个函数在内核目录/lib/kobject.c中被定义为:
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
该函数实际上是kobject_create()和kobject_add()的合并版本。
(8) kobject_del
这个函数在内核目录/lib/kobject.c中被分别定义为:
void kobject_del(struct kobject *kobj);
该函数从Linux设备层次(hierarchy)中删除kobj对象。
8.3 attribute
属性是以文件的形式输出到sysfs的目录当中。在内核中用一个结构体表示:
struct attribute { const char *name; mode_t mode; #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lock_class_key *key; struct lock_class_key skey; #endif };
attribute这个结构体中name就是属性的名字,在sysfs下为文件的名字;mode代表这个文件的读写属性,例如S_IRUGO对应只读属性。通常attribute不单独使用,常内嵌到别的结构体内部使用。例如,在devices子系统,attribute内嵌到结构体device_attribute;而在bus子系统,attribute内嵌到结构体bus_attribute中,等等。在这些结构体中,通常还定义了attribute的读写操作函数show和store。sysfs通过函数sysfs_create_file()建立文件,attribute结构体的变量指针就会作为参数传递给这个函数,而在上述子系统通常都会定义自己的创建文件函数,这些函数都会调用sysfs_create_file()。
8.3.1 猜测
在sysfs中,kobject->kobj_type中内嵌了一个struct attribute **default_attrs,即指针的指针,在C语言中,这种指针的指针通常用于表示一个列表,目录下的所有默认属性文件就存在于这个列表中(当然还有一些非默认的文件是不存在于这个表中的),对于所有属性(包括非缺省)都有一个共同的show和store,即包含于kobj_type中的sysfs_ops结构体,当系统调用过来的时候,首先调用的是sysfs_ops的show(读)或store(写),然后sysfs_ops的show或store又会去寻找属性文件自己show或store,并调用。而属性文件自己的show和store就存在于driver_attribute、class_attribute等等各子系统的相应结构体中。
8.4 kref
kref表示该对象的引用的计数,跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时,该对象将结束自己的有效生命期并可被删除。内核提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为0时,所有该对象使用的资源将被释放。
8.5 kobj_type
Kobj_type数据结构在Linux-2.6.38中被定义成:
struct kobj_type { void (*release)(struct kobject *kobj); const struct sysfs_ops *sysfs_ops; struct attribute **default_attrs; const struct kobj_ns_type_operations*(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); };
release函数用于释放kobject占用的资源。sysfs_ops指针指向sysfs操作表或一个sysfs文件系统缺省属性列表。sysfs操作表包括两个函数store()和show()。当用户态读取属性时(read系统调用),show()函数被调用,该函数将指定属性值存入buffer中返回给用户态;而store()函数用于存储用户态传入的属性值。
8.6 kset
kset最重要的是建立上层(sub-system)和下层的(kobject)的关联性。kobject也会利用它了分辨自已是属于那一个类型,然後在/sys下建立正确的目录位置。而kset的优先权比较高,下层kobject会利用自已的*kset成员找到自已所属的kset,把*ktype成员指定成kset->kobj->ktype,除非没有定义kset,才会用自身的ktype来建立关系(例如bus、class这些建立在sysfs顶层目录的文件夹)。Kobject通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合,在内核中用kset数据结构表示,定义为:
/** * 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; const struct kset_uevent_ops *uevent_ops; };
kset其实也是一个kobject,因此kset也是一个目录,其内嵌的kobject就提供了这样的功能。但kset通常是一个父目录,其中包含很多子目录,每个子目录对应一个kobject。所有这些kobject被组织成一个双向循环链表,list正是该链表的头。kset也需要一个kobj_type结构,该结构被kset中的所有子kobject共享,而该结构由kset下的kobject->ktype提供(这里容易搞混淆,kset的kobject是kset下所有list列表中的kobject的父对象)。kset也需要维护引用计数,以确定自己的生命周期,同样地,这也依赖于自己的kobject。