sysfs文件系统是一个伪文件系统,类似这种类型的文件系统还有proc、ramfs、tempfs等。他们的一个特点就是并不是基于物理磁盘的,而是基于内存的。这里除了sysfs很重要之外,还有一个proc文件系统也比较重要,proc文件系统主要是用来管理内核的重要数据的。
我们重点需要理解sysfs文件系统的作用,其实作用就是向用户空间展示系统设备的信息。值得一提的是,在该文件系统下,用户空间是不支持创建和删除文件的,因为sysfs文件系统没有提供这两个方法给用户空间,但内核空间是可以创建的。
前情提要
设备模型的关键结构体kobject会组成设备模型的树形结构,而sysfs的关键结构体sysfs_dirent也是类似的树形的结构,VFS中的dentry同样是类似的树形结构。
sysfs目录文件的创建都是由设备模型的上层构件(bus device driver class)在注册的时候调用它们内含的kobject(设备模型的底层基石)的添加注册操作,而kobject的操作就调用sysfs文件系统的具体方法,所以在sys目录下你就可以看到那些目录。
先来看看两个数据结构:
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;
};
struct sysfs_dirent {
atomic_t s_count;
atomic_t s_active;
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;
};
在VFS里面对一个文件的抽象是通过inode和dentry完成的,而在具体的文件系统里面,会有和自己文件系统对应的数据结构来完成抽象,例如:ext2fs_inode、ramfs_inode、ext2fs_dentry、ramfs_dentry。他们都是两个对象,但是在sysfs里面只用了一个sysfs_dirent对象来表示一个文件,他算是inode和dentry的综合。
回归正题,sysfs_dirent和kobject是怎么联系起来的呢,其实就是通过下面这个结构体联系起来的,如下所示,sysfs_elem_dir里面有一个kobject成员,所以就可以联系起来了。
struct sysfs_elem_dir {
struct kobject *kobj;
unsigned long subdirs;
struct rb_root children;
};
sysfs目录创建
在sysfs里面创建目录,只能通过操控kobject来实现,而这个操作也只能在内核空间里面操作,所以在用户层是没法mkdir的。不信的可以去试一下,在sys目录下执行mkdir命令。
int sysfs_create_dir(struct kobject * kobj)
{
enum kobj_ns_type type;
struct sysfs_dirent *parent_sd, *sd;
const void *ns = NULL;
int error = 0;
if (kobj->parent)//判断要创建的目录有没有父目录
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
没有就连接在根目录下,即:sys目录下
error = create_dir(kobj, parent_sd, type, ns, kobject_name(kobj), &sd);
if (!error)
kobj->sd = sd;
return error;
}
static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd,
enum kobj_ns_type type, const void *ns, const char *name,
struct sysfs_dirent **p_sd)
{
struct sysfs_addrm_cxt acxt;
struct sysfs_dirent *sd;
int rc;
sd = sysfs_new_dirent(name, mode, SYSFS_DIR);//要创建目录就需要有一个sysfs_dirent
//所以这里分配一个sysfs_dirent
sd->s_dir.kobj = kobj;//sysfs_dirent和要创建的目录的kobject建立连接
/* link in */
sysfs_addrm_start(&acxt, parent_sd);
rc = sysfs_add_one(&acxt, sd);//加入到sysfs文件系统,构成树形结构
sysfs_addrm_finish(&acxt);
return rc;
}
经过sysfs_add_one方法以后,要创建的目录其实就已经在树状图里面了,在sys目录下构成了层次关系,这里我画了一张简略图,sys目录结构如下图所示:
sysfs创建文件
sysfs文件系统里面,创建目录主要是让sysfs_dirent和kobject产生联系,然后使用sysfs_add_one添加到树状结构里面。但是我们上面提到,sysfs里面文件是没有kobject的,但是sysfs_dirent也表示文件,所以这时候,文件是通过什么和sysfs_dirent联系起来的呢?其实就是attribute结构。
而创建文件时,和创建目录的过程如出一辙,不同的点就是让sysfs_dirent与attribute产生联系,如下代码:
sd = sysfs_new_dirent(attr->name, mode, type);
sd->s_ns = ns;
sd->s_attr.attr = (void *)attr;//就是这里不一样
sysfs_dirent_init_lockdep(sd);
sysfs_addrm_start(&acxt, dir_sd);
rc = sysfs_add_one(&acxt, sd);
sysfs_addrm_finish(&acxt);
疑问
有没有想过,所有的文件系统都需要遵守VFS那一套,而VFS又是依靠dentry、inode来找到具体文件的,现在在sysfs文件系统下分析了目录、属性文件的创建过程,我们发现这些目录、文件并没有dentry和inode进行关联起来,那么如何能通过VFS找到里面的文件呢?
其实这个关联的过程,是在打开文件的时候做的,也就是open过程里面,换句话说,还是要通过VFS里面来做这个事,只能说,VFS真的强!
sysfs文件系统下打开过程
通过《文件系统》一篇我们知道open调用在经过路径解析以后,会对最终文件进行查找,一开始会在dentry cache里面找,找不到就会分配一个dentry(这一步是在looup_dcache做的)然后调用lookup_real,具体文件系统的lookup函数,比如sysfs_lookup,先看一下lookup_real方法。
static struct dentry *lookup_real(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct dentry *old;
/* Don't create child dentry for a dead directory. */
if (unlikely(IS_DEADDIR(dir))) {
dput(dentry);
return ERR_PTR(-ENOENT);
}
old = dir->i_op->lookup(dir, dentry, flags);//这里就是去调用具体文件系统的lookup
if (unlikely(old)) {
dput(dentry);
dentry = old;
}
return dentry;
}
由此就来到了具体文件系统的lookup,这里不妨猜测一下,我们前面创建的目录、文件都是没有inode的,所以lookup肯定会有分配这个结构的操作并作出初始化,并且还会和上面VFS分配的dentry绑定起来,下面给出代码。
static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct dentry *ret = NULL;
struct dentry *parent = dentry->d_parent;
struct sysfs_dirent *parent_sd = parent->d_fsdata;
struct sysfs_dirent *sd;
struct inode *inode;
enum kobj_ns_type type;
const void *ns;
mutex_lock(&sysfs_mutex);
type = sysfs_ns_type(parent_sd);
ns = sysfs_info(dir->i_sb)->ns[type];
sd = sysfs_find_dirent(parent_sd, ns, dentry->d_name.name);
dentry->d_fsdata = sysfs_get(sd);
/* attach dentry and inode */
inode = sysfs_get_inode(dir->i_sb, sd);//分配inode,初始化inode
/* instantiate and hash dentry */
ret = d_materialise_unique(dentry, inode);//dentry和inode建立联系,并加入到hash dentry
}
至此,返回到文件的open过程,又经过一系列的处理,最终是会调用到具体文件系统的open方法,对于sysfs来讲,就是sysfs_open_file,方法如下。
static int sysfs_open_file(struct inode *inode, struct file *file)
{
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
struct sysfs_buffer *buffer;
const struct sysfs_ops *ops;
int error = -EACCES;
/* every kobject with an attribute needs a ktype assigned */
if (kobj->ktype && kobj->ktype->sysfs_ops)
ops = kobj->ktype->sysfs_ops;
/* No error? Great, allocate a buffer for the file, and store it
* it in file->private_data for easy access.
*/
error = -ENOMEM;
buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);//分配buffer,为读写做准备
buffer->needs_read_fill = 1;
buffer->ops = ops;
file->private_data = buffer;
}
sysfs普通文件只有attribute对象,目录文件有kobject对象,kobject对象里面有操作集,这里要赋值给ops,这里涉及到设备模型的知识,暂且不在本篇文章讨论。下面就来看一下读写过程。
sysfs文件系统读写文件
有必要提前说一下,因为sysfs是基于内存的,不是物理磁盘的,所以读写的内容最后都是保存在内存里面的,而且也不会涉及到块设备的操作。
读文件过程
读过程,在经过VFS后同样会来到具体文件系统的读方法,对于sysfs就是sysfs_read_file,方法实现如下。
static ssize_t sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t
*ppos)
{
struct sysfs_buffer * buffer = file->private_data;//拿到buffer
ssize_t retval = 0;
mutex_lock(&buffer->mutex);
if (buffer->needs_read_fill || *ppos == 0) {
retval = fill_read_buffer(file->f_path.dentry,buffer);//这个应该算是最重要的
if (retval)
goto out;
}
//下面这个方法就是把文件数据交给用户,调用了copy_to_usr
retval = simple_read_from_buffer(buf, count, ppos, buffer->page,
buffer->count);
}
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
const struct sysfs_ops * ops = buffer->ops;
int ret = 0;
ssize_t count;
if (!buffer->page)
buffer->page = (char *) get_zeroed_page(GFP_KERNEL);//申请页内存
/* need attr_sd for attr and ops, its parent for kobj */
if (!sysfs_get_active(attr_sd))
return -ENODEV;
buffer->event = atomic_read(&attr_sd->s_attr.open->event);
//下面这个show方法,是具体设备提供的,反正就是往申请的页内存里面写东西
count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);
}
写文件过程
写文件过程和读过程很类似,最终的就是调用copy_from_usr方法,把数据写到buffer。但是里面不会用到设备的show方法,而是store方法。
总结
关于sysfs文件系统就介绍这么多,要知道sysfs_dirent和kboject、attribute的联系,有什么用,剩下的就是创建文件、目录这些了,以及在sysfs文件系统是怎么跟VFS中的dentry、inode建立联系的,最后是知道对于sysfs文件系统下的文件读写操作流程。
上述讲的都是关于sysfs文件系统本身的东西,实际上根目录下的sys目录/sys,是和Linux设备模型有关系的,sys目录下的所有文件,都是Linux内核在启动过程期间创建的,最后挂载sysfs 到 sys,输出到用户空间,给用户看。关于Linux设备模型也是一个非常重要的知识点,后续会学习。