伪文件系统sysfs

        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设备模型也是一个非常重要的知识点,后续会学习。

  • 26
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux文件系统是指Linux内核提供的一种特殊的文件系统,其中最常见的是/proc目录下的procfs文件系统。它是一个在内存中存在的文件系统,不占用硬盘空间。通过procfs文件系统,用户可以查看系统硬件和当前正在运行的进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。此外,Linux内核还提供了其他文件系统,如sysfs和devfs,它们也以文件的形式向用户提供了访问系统内核数据的接口。通过这些数据接口,用户和应用程序可以获取系统的信息,并且内核也允许用户修改一些参数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Linux详解(内核和文件系统)](https://blog.csdn.net/weixin_44983653/article/details/94732559)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Linux文件系统](https://blog.csdn.net/PythonLife/article/details/114768584)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值