简单的文件系统

文章深入探讨了Linux内核中的关键概念,包括网络sk_buff的克隆与复制,进程和线程的架构,Docker所依赖的内核技术。同时,详细介绍了sysfs文件系统,它是如何导出内核对象并允许修改其数据结构的,以及kobject机制在sysfs中的作用,包括kobject的层次结构、属性和与sysfs的关联。此外,还提到了文件系统的操作,如打开、读写文件以及目录遍历的过程。
摘要由CSDN通过智能技术生成

【推荐阅读】

浅谈linux 内核网络 sk_buff 之克隆与复制

深入linux内核架构--进程&线程

了解Docker 依赖的linux内核技术

建议本文搭配上一篇《proc文件系统》进行阅读

顺序文件

基本上,必须提供一个 struct file_operations 的实例,其中一些函数指针指向一些 seq_ 例程,这样就可以利用顺序文件的标准实现了。kprobes子系统的做法如下:

kernel/kprobes.c
static struct file_operations debugfs_kprobes_operations = {
.open = kprobes_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};

唯一需要实现的方法是 open 。实现该函数不需要多少工作量,简单的一行代码就可以将文件关联

到顺序文件接口:

kernel/kprobes.c
static struct seq_operations kprobes_seq_ops = {
.start = kprobe_seq_start,
.next = kprobe_seq_next,
.stop = kprobe_seq_stop,
.show = show_kprobe_addr
};
static int __kprobes kprobes_open(struct inode *inode, struct file *filp)
{
    return seq_open(filp, &kprobes_seq_ops);
}

seq_open将file中的private字段设置为seq_file。

<seq_file.h>
struct seq_file {
    char *buf;
    size_t size;
    size_t from;
    size_t count;
    loff_t index;
...
    const struct seq_operations *op;
...
};
与虚拟文件系统的关联

首先,该函数需要从VFS层的 struct file 获得 seq_file 实例。回想前文, seq_open 通过private_data 建立了该关联。

如果有些数据等待写出(如果 struct seq_file 的 count 成员为正值),则使用 copy_to_user 将其复制到用户层。此外,还需要更新 seq_file 的各个状态成员。

下一步,会产生新的数据。在调用 start 之后,内核接连调用 show 和 next ,直至填满可用的缓冲区。最后,调用 stop ,使用 copy_to_user 将生成的数据复制到用户空间。

用libfs编写文件系统

使用libfs建立的虚拟文件系统,其文件和目录层次结构可使用 dentry 树产生和遍历。这意味着在该文件系统的生命周期内,所有的 dentry 都必须驻留在内存中。除非通过 unlink 或 rmdir 显式删除,否则不能消失。但这个要求很容易做到:代码只需要确保所有 dentry 的使用计数都是正值即可。

sysfs


sysfs是一个向用户空间导出内核对象的文件系统,它不仅提供了察看内核内部数据结构的能力,还可以修改这些数据结构。特别重要的是,该文件系统高度层次化的组织:sysfs的数据项来源于内核对象(kobject),而内核对象的层次化组织直接反映了sysfs的目录布局中。由于系统的所有设备和总线都是通过kobject组织的,所以sysfs提供了系统的硬件拓扑的一种表示。

注意,kobject与sysfs之间的关联不是自动建立的。独立的kobject实例默认情况下并不是集成到sysfs。要使一个对象在sysfs文件系统中可见,需要调用kobject_add。但如果kobject是某个内核子系统的成员,那么向sysfs的注册是自动进行的。

概述

<linux/kobject.h>
struct kobject {
    const char      * k_name;
    struct kref     kref;
    struct list_head    entry;
    struct kobject      * parent;
    struct kset     * kset;
    struct kobj_type    * ktype;
    struct sysfs_dirent * sd;
};
struct kset {
    struct kobj_type    *ktype;
    struct list_head    list;
    spinlock_t      list_lock;
    struct kobject      kobj;
    struct kset_uevent_ops  *uevent_ops;
};

kobject包含在一个层次化的组织中。最重要的一点是,他们可以有一个父对象,可以包含到一个kset中。这决定了kobject出现在sysfs层次结构中的位置:如果存在父对象,那么需要在父对象对应的目录中新建立一项。否则,将其放置到kobject所在的kset所属的kobject对应的目录中。

每个kobject在sysfs中都表示为一个目录。出现在该目录中的文件是对象的属性。用于导出和设置属性的操作由对象所属的子系统提供。

总线、设备、设备驱动程序和类是使用kobject机制的主要内核对象,因而也占据了sysfs中几乎所有的数据项。

数据结构

目录项
<fs/sysfs/sysfs.h>
struct sysfs_dirent {
    atomic_t        s_count;
    atomic_t        s_active;
    struct sysfs_dirent *s_parent;
    struct sysfs_dirent *s_sibling;//用于连接同一父结点所有子结点
    const char      *s_name;
    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 int        s_flags;//设置sysfs数据项的类型
    ino_t           s_ino;
    umode_t         s_mode;
    struct iattr        *s_iattr;//属性
};

根据sysfs数据项的类型不同,与之关联的数据类型也不同。由于一个数据项一次只能表示一种类型,封装了与sysfs项相关数据的结构,都群集到一个匿名联合中。

<fs/sysfs/sysfs.h>
struct sysfs_elem_dir {//目录
    struct kobject      *kobj;
    /* children list starts here and goes through sd->s_sibling */
    struct sysfs_dirent *children;//将所有子结点通过s_sibling连接起来。
};
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;
};

与任何其他的文件系统类似,sysfs数据项也由struct dentry实例表示。两种层次的表示之间,通过dentry->d_fsdata建立关联。

属性
<linux/sysfs.h>
struct attribute {
    const char      *name;
    struct module       *owner;
    mode_t          mode;
};
struct attribute_group {//属性组
    const char      *name;
    struct attribute    **attrs;
};

对于可读写的属性,需要提供两个方法。

<sysfs.h>
struct sysfs_ops {
    ssize_t (*show)(struct kobject *, struct attribute *,char *);
    ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
};

对于二进制属性,用于读取和修改数据的方法,通常对每个属性都是不同的。这一点反映到了数据结构中:

<sysfs.h>
struct bin_attribute {
    struct attribute    attr;
    size_t          size;
    void            *private;
    ssize_t (*read)(struct kobject *, struct bin_attribute *,
            char *, loff_t, size_t);
    ssize_t (*write)(struct kobject *, struct bin_attribute *,
             char *, loff_t, size_t);
    int (*mmap)(struct kobject *, struct bin_attribute *attr,
            struct vm_area_struct *vma);
};
声名新属性

考虑通用硬盘代码如何定义一个结构,即可将一个属性以及读写该属性的方法关联起来:

<genhd.h>
struct disk_attribute {
    struct attribute attr;
    ssize_t (*show)(struct gendisk *, char *);
    ssize_t (*store)(struct gendisk *, const char *, size_t);
};
block/genhd.c
static struct sysfs_ops disk_sysfs_ops = {
    .show = &disk_attr_show,
    .store = &disk_attr_store,
};
block/genhd.c
static ssize_t disk_attr_show(struct kobject *kobj, struct attribute *attr,
char *page)
{
    struct gendisk *disk = to_disk(kobj);
    struct disk_attribute *disk_attr =
    container_of(attr,struct disk_attribute,attr);
    ssize_t ret = -EIO;
    if (disk_attr->show)
        ret = disk_attr->show(disk,page);
    return ret;
}

装载文件系统

mount系统调用最终将填充超级块的工作委托给sys_fill_super。

static int sysfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct inode *inode;
    struct dentry *root;
    sb->s_blocksize = PAGE_CACHE_SIZE;
    sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
    sb->s_magic = SYSFS_MAGIC;
    sb->s_op = &sysfs_ops;
    sb->s_time_gran = 1;
    sysfs_sb = sb;
    /* get root inode, initialize and unlock it */
    inode = sysfs_get_inode(&sysfs_root);//创建一个新的struct inode实例,作为整个sysfs树的起点
    if (!inode) {
        pr_debug("sysfs: could not get root inode\n");
        return -ENOMEM;
    }
    /* instantiate and link root dentry */
    root = d_alloc_root(inode);//创建root dentry
    if (!root) {
        pr_debug("%s: could not get root dentry!\n",__FUNCTION__);
        iput(inode);
        return -ENOMEM;
    }
    root->d_fsdata = &sysfs_root;//在sysfs_dirent和dentry之间建立一个关联
    sb->s_root = root;
    return 0;
}

初始化sysfs inode的操作

static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{
    struct bin_attribute *bin_attr;
    inode->i_blocks = 0;
    inode->i_mapping->a_ops = &sysfs_aops;
    inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info;
    inode->i_op = &sysfs_inode_operations;
    inode->i_ino = sd->s_ino;
    lockdep_set_class(&inode->i_mutex, &sysfs_inode_imutex_key);
    if (sd->s_iattr) {
        /* sysfs_dirent has non-default attributes
         * get them for the new inode from persistent copy
         * in sysfs_dirent
         */
        set_inode_attr(inode, sd->s_iattr);
    } else
        set_default_inode_attr(inode, sd->s_mode);
    /* initialize inode according to type */
    switch (sysfs_type(sd)) {
    case SYSFS_DIR:
        inode->i_op = &sysfs_dir_inode_operations;
        inode->i_fop = &sysfs_dir_operations;
        inode->i_nlink = sysfs_count_nlink(sd);
        break;
    case SYSFS_KOBJ_ATTR:
        inode->i_size = PAGE_SIZE;
        inode->i_fop = &sysfs_file_operations;
        break;
    case SYSFS_KOBJ_BIN_ATTR:
        bin_attr = sd->s_bin_attr.bin_attr;
        inode->i_size = bin_attr->size;
        inode->i_fop = &bin_fops;
        break;
    case SYSFS_KOBJ_LINK:
        inode->i_op = &sysfs_symlink_inode_operations;
        break;
    default:
        BUG();
    }
    unlock_new_inode(inode);
}

文件和目录操作

实现文件系统操作的函数在sysfs和内部数据结构之间充当了胶水层的角色。

sysfs内部与虚拟文件系统之间的关联是在打开文件时建立的。

打开文件

为便于在用户层和sysfs实现之间交换数据,内核需要提供一些缓冲区:

struct sysfs_buffer {
    size_t          count;//指定缓冲区中数据的长度
    loff_t          pos;//内部数据当前位置
    char            * page;//指向一页,用于存储数据
    struct sysfs_ops    * ops;//属于与打开文件想关联的sysfs数据项
    struct mutex        mutex;
    int         needs_read_fill;
    int         event;
    struct list_head    list;//每个打开的文件都由一个struct file的实例表示,
                             //它通过file->private_data关联到一个sysfs_buffer的实例。
                             //多个打开的文件可以引用同一sysfs数据项,
                             //因此多个sysfs_buffer可以关联到同一个struct sysfs_dirent实例。
                             //所有这些缓冲区都集中到一个链表中,链表元素是sysfs_buffer->list。
                             //表头是sysfs_open_dirent(buffers字段)的一个实例,
                             //并且sysfs_open_dirent关联到sysfs_dirent(sysfs_elem_attr.open)
};

sysfs_file_operations提供了sys_open_file方法,在打开文件时调用

<fs/sysfs/file.c>
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;
    struct sysfs_ops * ops = NULL;
    int error;
    /* need attr_sd for attr and ops, its parent for kobj */
    if (!sysfs_get_active_two(attr_sd))//获得与sysfs数据项关联的kobject实例的活动引用。
        return -ENODEV;
    /* if the kobject has no ktype, then we assume that it is a subsystem
     * itself, and use ops for it.
     */
    //查找打开文件的sysfs_ops操作
    if (kobj->kset && kobj->kset->ktype)
        ops = kobj->kset->ktype->sysfs_ops;
    else if (kobj->ktype)
        ops = kobj->ktype->sysfs_ops;
    else
        ops = &subsys_sysfs_ops;
    error = -EACCES;
    /* No sysfs operations, either from having no subsystem,
     * or the subsystem have no operations.
     */
    if (!ops)
        goto err_out;
    /* File needs write support.
     * The inode's perms must say it's ok,
     * and we must have a store method.
     */
    if (file->f_mode & FMODE_WRITE) {
        if (!(inode->i_mode & S_IWUGO) || !ops->store)
            goto err_out;
    }
    /* File needs read support.
     * The inode's perms must say it's ok, and we there
     * must be a show method for it.
     */
    if (file->f_mode & FMODE_READ) {
        if (!(inode->i_mode & S_IRUGO) || !ops->show)
            goto err_out;
    }
    /* 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);//分配一个sysfs_buffer的实例,
    if (!buffer)
        goto err_out;
    mutex_init(&buffer->mutex);
    buffer->needs_read_fill = 1;
    buffer->ops = ops;
    file->private_data = buffer;//关联
    /* make sure we have open dirent struct */
    error = sysfs_get_open_dirent(attr_sd, buffer);
    if (error)
        goto err_free;
    /* open succeeded, put active references */
    sysfs_put_active_two(attr_sd);
    return 0;
 err_free:
    kfree(buffer);
 err_out:
    sysfs_put_active_two(attr_sd);
    return error;
}
读写文件的内容
读取数据

sysfs_read_file->fill_read_buffer

->simple_read_from_buffer

写入数据

sysfs_write_file->fill_write_buffer

->flush_write_buffer

目录遍历

sysfs_dir_inode_operations的lookup方法是目录遍历的关键。

<fs/sysfs/dir.c>
static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
                struct nameidata *nd)
{
    struct dentry *ret = NULL;
    struct sysfs_dirent *parent_sd = dentry->d_parent->d_fsdata;
    struct sysfs_dirent *sd;
    struct inode *inode;
    mutex_lock(&sysfs_mutex);
    sd = sysfs_find_dirent(parent_sd, dentry->d_name.name);
    /* no such entry */
    if (!sd) {
        ret = ERR_PTR(-ENOENT);
        goto out_unlock;
    }
    /* attach dentry and inode */
    inode = sysfs_get_inode(sd);
    if (!inode) {
        ret = ERR_PTR(-ENOMEM);
        goto out_unlock;
    }
    /* instantiate and hash dentry */
    dentry->d_op = &sysfs_dentry_ops;
    dentry->d_fsdata = sysfs_get(sd);
    d_instantiate(dentry, inode);
    d_rehash(dentry);
 out_unlock:
    mutex_unlock(&sysfs_mutex);
    return ret;
}
struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd,
                       const unsigned char *name)
{
    struct sysfs_dirent *sd;
    for (sd = parent_sd->s_dir.children; sd; sd = sd->s_sibling)
        if (!strcmp(sd->s_name, name))
            return sd;
    return NULL;
}

属性组成了目录的各个目录项,该函数试图找到一个具有特定名称、属于某个struct sysfs_dirent实例对应的目录下的属性。

向sysfs添加内容

......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值