一、sysfs
sysfs是用来向用户空间导出内核对象的一种文件系统,通过它,用户空间程序可以查看、甚至修改内核数据结构。该文件系统是基于内核数据结构kobject建立起来的,同时该文件系统的目录结构反映了相关内核数据结构的层次结构。由于kobject是组成设备模型的基本结构,因此sysfs也包括了系统中设备的信息,它提供了系统硬件的拓扑信息。
由于sysfs提供了访问、修改内核数据结构的一种手段,因而内核模块也可以通过该文件系统向用户空间导出接口用于访问、修改模块的参数。
在引入sysfs后,内核向用户到处接口的方式有/proc文件系统,sysfs文件系统,ioctl命令。
虽然sysfs的信息来自于kobject,但是kobject和sysfs的关联不是自动建立的,必须通过kobject_add才能把一个kobject添加到sysfs中。
由于内核中使用kobject的主要部件是硬件相关的部分,因而sysfs包含的最主要的内容就是硬件相关的内容,包括总线、设备、驱动程序等。Sysfs挂载点为/sys
rover$ ls /sys
block bus class dev devices firmware fs hypervisor kernel module power
1.1 sysfs节点
sysfs使用sysfs_dirent来表示一个目录或文件节点,其数据结构如下:
struct sysfs_dirent {
atomic_t s_count;
atomic_t s_active;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
struct sysfs_dirent *s_parent;
const char *s_name;
struct rb_node s_rb;
union {
struct completion *completion;
struct sysfs_dirent *removed_list;
} u;
const void *s_ns; /* namespace tag */
unsigned int s_hash; /* ns + name hash */
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 short s_flags;
umode_t s_mode;
unsigned int s_ino;
struct sysfs_inode_attrs *s_iattr;
};
其中
- s_count:该sysfs节点的引用计数。它在创建一个新的sysfs_dirent时被设置为0。
- s_active:该sysfs节点的活动应用计数。每当内核部件访问一个sysfs节点的内容sysfs_elem_*时就必须持有该引用计数,在访问完毕后要相应的更新该引用计数。之所以在引用计数之外再添加该参数是因为,如果没有该域,则只要用户打开了一个sysfs文件,就无法将其删除了。再增加了该计数后,只要它为负值,就不能再操作该sysfs节点了(这个限制添加在获取活动引用计数上,如果引用计数为负值,则无法获取引用计数,因此获取活动引用计数时,必须检查返回值), 当该节点的所有使用者都消失时,就可以删除该sysfs节点了。它在创建新的sysfs_dirent时被设置为1。
- s_name:该sysfs_dirent的名字。
- s_parent:指向父节点
- s_rb:新的代码使用红黑树来管理同一目录下的子节点,同一目录下的兄弟节点被添加到一棵红黑树中,该域就是红黑树数据结构。红黑树的根为s_dir的children域。
- s_ino:该sysfs节点在sysfs中的id。
- s_flags:包含了该sysfs_dirent的类型信息和标志信息。类型指示了该sysfs_dirent是那种类型,类型包括SYSFS_DIR、SYSFS_KOBJ_ATTR、SYSFS_KOBJ_BIN_ATTR、SYSFS_KOBJ_LINK,每种类型对应于一个数据项结构,由于一个数据只能表示一种类型,因而这4种类型对应的数据结构被包括在一个枚举类型中。四种类型对应的数据结构分别为:
struct sysfs_elem_dir {
struct kobject *kobj;
unsigned long subdirs;
/* children rbtree starts here and goes through sd->s_rb */
struct rb_root children;
};
struct sysfs_elem_symlink {
struct sysfs_dirent *target_sd;
};
struct sysfs_elem_attr {
struct attribute *attr;
struct sysfs_open_dirent *open;
};
struct sysfs_elem_bin_attr {
struct bin_attribute *bin_attr;
struct hlist_head buffers;
};
struct sysfs_inode_attrs {
struct iattr ia_iattr;
void *ia_secdata;
u32 ia_secdata_len;
};
sysfs使用dentry的d_fsdata来保存对应的sysfs_dirent,因此通过dentry就能找到对应的sysfs_dirent。
在sysfs节点的四种类型中:
- 目录结构包含了该目录的红黑树的根节点信息以及该目录节点的kobject信息。
- 连接结构包含了指向目标sysfs节点的指针信息
- 而另外两种则包含了属性信息,即该节点对应的属性信息,属性信息对用户来说是最关键的。
1.2 sysfs节点属性
1.2.1属性信息定义如下:
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
- name为属性的名字,并且会作为文件名字出现在sysfs文件系统中
- mode即该属性的访问属性,最终反映为sysfs文件系统中该属性文件的访问属性
1.2.2 二进制属性信息
其定义如下
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
ssize_t (*write)(struct file *,struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
该属性各个域的含义很明显。
二进制属性本身就包含了该属性的读写操作指针信息,它和常规的属性是不一样的,因而二进制属性文件必须显式创建,它不能作为kobject的默认属性。相关的API如下:
int sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr);
void sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr);
1.2.3 创建链接文件
sysfs中常见的一个操作是创建链接文件,其相关API如下:
int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name);
void sysfs_delete_link(struct kobject *kobj, struct kobject *targ, const char *name);
这里只讨论了链接文件,二进制数形文件的创建,根据kobject的内容属性好像少了目录和常规属性的创建,常规属性的创建在kobject中有描述,而目录的创建则由kobject框架自动完成,不需要调用者关心。
1.3 自定义属性
如果一个内核部件需要定义自己的属性,实现方案也很简单,只需要定义如下的结构:
struct self_attribute{
struct attributeattr;
…
};
然后再sysfs需要属性时,将slef_attriute的attr提供给sysfs即可。这样sysfs仍可以使用自己所通用的struct attribute结构,而模块自己可以很容易的通过contanier_of宏得到自己所定义的自定义属性。
1.4 sysfs文件系统初始化
使用sysfs文件系统之前必须将其初始化并且挂载到系统中,该过程和proc文件系统完全类似。sysfs文件系统的的初始化主要完成:
- 调用kmem_cache_create创建sysfs文件系统所使用的专用缓冲区
- 调用register_filesystem注册sysfs文件系统,这里会提供proc文件系统自己的file_system_type,其中包括了用于mount的函数指针。在执行mount的时候会用到这些信息,并最终找到mount函数进行挂载操作。
在sysfs的mount函数中会调用sysfs_fill_super,它会
- 给出sysfs文件系统超级块所需要的信息(比如文件系统的超级块操作函数指针,超级块大小等)。
- 初始化根目录的inode,在这个过程中会指定与之对应的inode_operations和file_operations,有了这些信息后,VFS就可以在该文件系统上进行各种操作了(创建、删除、查找文件)。
- 创建sysfs文件系统的根目录, 并且将dentry的d_fsdata指向根目录的sysfs_dirent结构sysfs_root。sysfs的每一个节点都对应一个dentry。
从上述描述可以看出,其和proc文件系统的初始化、以及mount是类似的。
设置sysfs的inode_operations和file_operations的工作是由sysfs_get_inode函数完成的,它最终调用函数sysfs_init_inode来完成这个工作。
1.5 sysfs文件节点删除
在删除一个sysfs节点时,sysfs会首先保证内核没有任何对sysfs内容的引用,这通过s_active来保证,然后在对该kobject的常规引用也全部消失后,就会真正删除该kobject。其流程大致为:
- 首先将一个特殊值SD_DEACTIVATED_BIAS加到s_active上,然后检查结果是否仍然为该值,如果结果仍然是该值,则和其它文件的删除类似,根据引用计数是否为0进行相应的处理,否则
- 在该kobject的u.completion上等待
能够结束该等待的就是对活动引用计数的put动作,在活动引用计数的put操作中,会进行检查,如果减1后的值为SD_DEACTIVATED_BIAS则就调用complete(sd->u.completion)结束该等待。
1.6 sysfs文件系统的操作
在文件操作中,inode文件操作相对比较简单,如果是目录,则提供有一个查找函数用于查找一个dentry,如果是普通文件,则提供有设置、查看文件属性的函数。相对比较负责的是实际的文件操作。
为了在内核和用户空间交互数据,sysfs还提供了一个数据结构来缓冲这种数据:
struct sysfs_buffer {
size_t count;
loff_t pos;
char * page;
const struct sysfs_ops * ops;
struct mutex mutex;
int needs_read_fill;
int event;
struct list_head list;
};
- count:缓冲区中数据长度
- pos:当前位置
- page:指向存储数据的缓冲区
- ops:实际的文件操作指针
- needs_read_fill:缓冲区数据是否需要填充,在第一次读的时候肯定需要,如果在读之后没有写发生,则可以直接从缓冲区读而不需要重新填充。
- list:关联到同一个sysfs的缓冲区列表
缓冲区列表与打开该sysfs的文件以及该sysfs的sysfs_dirent的关系如下所示:
Sysfs_ops结构的内容如下,需要进行读写的节点需要提供相应的函数。
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
const void *(*namespace)(struct kobject *, const struct attribute *);
};
1.5.1打开文件
如果是普通属性文件,则在sysfs_get_inode中会将文件操作指针设置为sysfs_file_operations,其中包含了打开文件的函数sysfs_open_file,它会完成打开文件的操作。
在最新的代码中,要求所有的属性必须指定一个ktype,ktype中包含了一个struct sysfs_ops指针,用于保存相关联的文件操作指针。因此相关的代码逻辑比较简单:
- 找到对应的ktype
- 将sysfs_dirent的活动引用加1
- 获得文件操作指针
- 分配一个buffer,并对其进行初始化,同时将将文件操作赋给buffers
- 如果是第一次打开该sysfs节点,则初始化一个用于poll的等待队列,否则仅将新的buffer添加到buffer的列表中
- 将sysfs_dirent的活动引用减1
1.5.2读文件
如果是普通属性文件,则在sysfs_get_inode中会将文件操作指针设置为sysfs_file_operations,其中包含了打开文件的函数sysfs_read_file。该函数比较简单,它会
- 找到打开文件对应的buffer
- 查看是否需要填充
- 如果需要填充,则
- 将sysfs_dirent的活动引用加1
- 调用buffer中的sysfs_ops中的show方法来将信息读取到buffer中
- 将sysfs_dirent的活动引用减1
- 如果不需要填充,则直接从buffer中读取
1.5.3写文件
如果是普通属性文件,则在sysfs_get_inode中会将文件操作指针设置为sysfs_file_operations,其中包含了打开文件的函数sysfs_write_file,该函数也很简单,它。
- 找到打开文件对应的buffer
- 首先将内容写到buffer中
- 将sysfs_dirent的活动引用加1
- 调用buffer的sysfs_ops中的store方法来完成最后的写
- 将sysfs_dirent的活动引用减1
从上述描述可以看出,一个sysfs的实现者只需要将自己的真正的读写函数放到kobject的kobj_type的sysfs_ops中即可被调用到。
由于sysfs文件系统是基于kobject构建的,而kobject内嵌在其它各种内核数据结构中,相应的内核框架都已经包括了注册到sysfs中的代码,因而只需要遵循相应模块所属框架的规则即可完成向sysfs添加新的内容。