走马看VFS_0917_0921

    这个本来是0917_0921的周总结,但是老是用*周总结为文章标题,有时候很难知道文章的具体内容是什么。所以从这个星期开始,题目做出更改,周总结系列到此结束。但是题目依然有日期的标号,比如xxx_0917_0921。

    由于板子在这个星期的绝大部分时间都不在我手上,所以我决定先暂停一下项目的进度,把上次看了一点的VFS重新再看看。当然这几天的学习也不可能学习很多,但总的来说概念变得清晰了,能看懂一些相关的文档和代码了。

    我这次把以前的VFS文档进行更新,包括纠正一些错误。

    了解VFS比较不容易,因为涉及的数据结构多,而且它们的关系比较复杂。但是也不算特别复杂,给点耐心还是能搞清楚的。下面的代码都源自我当前使用的内核代码,内核版本为2.6.21.3

    在Unix中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称为命名空间(namespace)所有的已安装文件系统都作为根文件系统树的枝叶出现在系统中。而文件的相关信息和文件本身是两个概念,例如访问控制权限、大小、拥有者、创建信息等等等等信息。文件相关信息,有时称为文件的元数据,被存储在一个单独的数据结构中,该结构被称为索引节点(inode)。所有这些信息和文件系统控制信息密切交融,文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。

    VFS有四个主要的数据结构:超级块、索引节点、目录项、文件。(源代码在 ../include/fs.h

1.超级块对象 super_block

    它代表一个已安装文件系统。对于非磁盘的文件系统(如基于内存的文件系统,sysfs),它们会在使用现场创建超级块并将其保存到内存中。

2.索引节点对象 inode

    它代表一个文件(也可以是设备或管道),包含了内核在操作文件或目录时需要的全部信息。

3.目录项对象 dentry

    它代表一个目录项,是路径的一个组成部分。路径的每个组成部分都由一个索引节点对象表示,虽然它们可以统一由索引节点表示,但是VFS经常需要执行目录相关的操作,比如路径名查找等。

    为了方便查找操作,VFS才引入目录项的概念。需要明确一点:在路径中,包括普通文件在内,每一部分都是目录项对象。而且目录项并没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。

4.文件对象 file

    它代表由进程打开的文件。文件对象是已打开的文件在内存中的表示。该对象(不是物理文件)由相应的open()系统调用创建,由close()系统调用销毁,所有的这些文件相关调用都是文件操作表中定义的方法。文件对象仅仅在进程观点上代表已打开的文件,它反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象无疑是唯一的。

    类似于目录项对象,文件对象实际上没有对应的磁盘数据。所以结构体中同样没有代表其对象是否为dirty,是否需要写回磁盘的标志。文件对象通过f_dentry指向相关的目

    录项对象。目录项对指向相关的索引节点,索引节点会记录文件是否dirty

    除此之外还有三个很重要的数据结构

第一个是:file_system_type描述各种特定的文件系统类型。

struct file_system_type {

    const char *name; //文件系统的名字

    int fs_flags;

    int (*get_sb) (struct file_system_type *, int, //从磁盘读取超级块

    const char *, void *, struct vfsmount *);

    void (*kill_sb) (struct super_block *); //终止访问超级块

    struct module *owner;

    struct file_system_type * next;

    struct list_head fs_supers; //超级块对象链表

    struct lock_class_key s_lock_key;

    struct lock_class_key s_umount_key;

};

第二个是:vfsmount描述一个安装文件系统的实例。

    get_sb()函数从磁盘读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。每种文件系统都只有一个file_system_type结构。

    当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。该结构体用来代表文件系统的实例--换句话说,代表一个安装点

struct vfsmount {

    struct list_head mnt_hash;

    struct vfsmount *mnt_parent; /* fs we are mounted on */

    struct dentry *mnt_mountpoint; /* dentry of mountpoint */

    struct dentry *mnt_root; /* root of the mounted tree */

    struct super_block *mnt_sb; /* pointer to superblock */

    struct list_head mnt_mounts; /* list of children, anchored here */

    struct list_head mnt_child; /* and going through their mnt_child */

    int mnt_flags;

    char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */

    struct list_head mnt_list;

    struct list_head mnt_expire; /* link in fs-specific expiry list */

    struct list_head mnt_share; /* circular list of shared mounts */

    struct list_head mnt_slave_list;/* list of slave mounts */

    struct list_head mnt_slave; /* slave list entry */

    struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */

    struct mnt_namespace *mnt_ns; /* containing namespace */

    atomic_t mnt_count;

    int mnt_expiry_mark; /* true if marked for expiry */

    int mnt_pinned;

};

第三个是:namespacemnt_namespace/pid_namespace)描述文件系统层次结构。这个数据结构和进程相关,由进程描述符的namesparce域指向。

struct mnt_namespace {

    atomic_t count; //结构使用记数

    struct vfsmount * root; //根目录的安装点对象

    struct list_head list; //连接已安装文件的系统的链表,这些元素组成全体命名空间

    wait_queue_head_t poll;

    int event;

};

    带着这些知识,我们现在就去看看VFS是如何实现的。不过在此之前,大家应该明白以上的结构体之间是如何关联的,这非常重要。(强烈推荐ibm dw上《解析 Linux 中的 VFS 文件系统机制》一文)

    VFS 是一种软件机制,也许称它为 Linux 的文件系统管理者更确切点,与它相关的数据结构只存在于物理内存当中。所以在每次系统初始化期间,Linux 都首先要在内存当中构造一棵 VFS 的目录树(Linux 的源代码里称之为 namespace),实际上便是在内存中建立相应的数据结构。VFS 目录树在 Linux 的文件系统模块中是个很重要的概念,不要将其和实际文件系统目录树混淆,VFS 中的各目录其主要用途是用来提供实际文件系统的挂载点,当然在 VFS 中也会涉及到文件级的操作。

    在众多的文件系统中,我打算着重介绍roofs文件系统的注册过程。对于内核来说,安装文件系统主要有两个步骤:

1.内核安装rootfs文件系统,它会提供一个空目录(/)作为初始安装点;

2.内核通过空目录安装其他真正的文件系统(ext2ext3)

    那么为什么内核一定要安装rootfs,没有它一样可以安装其他文件系统。引用ulk3的一句原话:Well, the rootfs filesystem allows the kernel to easily change the real root filesystem

    完成上面所说的第一个步骤在init_rootfs()init_mount_tree()两个函数里面。它们在系统初始化的时候执行。

rootfs声明:

static struct file_system_type rootfs_fs_type = {

    .name = "rootfs", //文件系统的名字

    .get_sb = rootfs_get_sb,

    .kill_sb = kill_litter_super,

};


int __init init_rootfs(void)

{

    return register_filesystem(&rootfs_fs_type); //注册rootfs

}



static int rootfs_get_sb(struct file_system_type *fs_type,

int flags, const char *dev_name, void *data, struct vfsmount *mnt)

{

    return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super,

mnt);

}

get_sb_nodev()这个函数做了不少事情,我们应该看一下

int get_sb_nodev(struct file_system_type *fs_type,

int flags, void *data, int (*fill_super)(struct super_block *, void *, int),

struct vfsmount *mnt)

{

    int error;

    //分配一个超级块,设备标志符s_devmajor设为0minor根据安装的文件系统而异

    struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);


    if (IS_ERR(s))

        return PTR_ERR(s);

    //flag拷贝到超级块的flag

    s->s_flags = flags;

    //fill_super分配inode和相应的dentry,然后初始化超级块

    error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);

    if (error) {

        up_write(&s->s_umount);

        deactivate_super(s);

        return error;

    }

    s->s_flags |= MS_ACTIVE;

    return simple_set_mnt(mnt, s);

}


好,下面到 init_mount_tree了。

static void __init init_mount_tree(void)

{

    struct vfsmount *mnt;

    struct mnt_namespace *ns;

    //挂载前面已经注册了的 rootfs 文件系统(之前注册rootfs已经生成sbinodedentry,现在

    //再生成mnt,文件系统结构基本完善)

    mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);

    if (IS_ERR(mnt))

        panic("Can't create rootfs");


    ns = kmalloc(sizeof(*ns), GFP_KERNEL);

    if (!ns)

        panic("Can't allocate initial namespace");

    //使用记数设为1

    atomic_set(&ns->count, 1);

    //链表的初始化

    INIT_LIST_HEAD(&ns->list);

    init_waitqueue_head(&ns->poll);

    ns->event = 0;

    //将描述符链表mnt_list添加到nslist里面(mnt代表了一个文件系统的实例)

    list_add(&mnt->mnt_list, &ns->list);

    //nsmnt之间建立联系

    ns->root = mnt;

    mnt->mnt_ns = ns;


    init_task.nsproxy->mnt_ns = ns;

    get_mnt_ns(ns);


    //下面目的在于将 do_kern_mount() 函数中建立的 mnt dentry 信息记录在了 init_task

    //进程的进程数据块中,这样所有以后从 init_task 进程 fork 出来的进程也都先天地继承

    //这一信息

    set_fs_pwd(current->fs, ns->root, ns->root->mnt_root);

    set_fs_root(current->fs, ns->root, ns->root->mnt_root);

}

    前面通过do_kern_mount("rootfs", 0, "rootfs", NULL);来挂载前面已经注册了的 rootfs 文件系统。这看起来似乎有点奇怪,因为根据前面的说法,似乎是应该先有挂载目录,然后再在其上挂载相应的文件系统,然而此时 VFS 似乎并没有建立其根目录。没关系,这是因为这里我们调用的是 do_kern_mount(),这个函数内部自然会创建我们最关心也是最关键的根目录(Linux 中,目录对应的数据结构是dentry)。而do_kern_mount主要是通过调用 vfs_kern_mount完成其功能。

*下面的代码只具有主要功能部分

struct vfsmount *

vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)

{

    struct vfsmount *mnt;

    char *secdata = NULL;

    int error;


    if (!type)

        return ERR_PTR(-ENODEV);


    error = -ENOMEM;

    //分配mnt

    mnt = alloc_vfsmnt(name);

    if (!mnt)

        goto out;

    //获得超级块

    error = type->get_sb(type, flags, name, data, mnt);

    if (error < 0)

        goto out_free_secdata;

    error = security_sb_kern_mount(mnt->mnt_sb, secdata);

    if (error)

        goto out_sb;

    //mnt的安装点和根目录项都是 /

    mnt->mnt_mountpoint = mnt->mnt_root;

    mnt->mnt_parent = mnt;

    //释放s_umount

    up_write(&mnt->mnt_sb->s_umount);

    free_secdata(secdata);

    return mnt;

}

    要是没有看过《解...》一文就看上面的代码,肯定会一头雾水。其实上面的代码只是一个脉络,很多细节的实现都没有看到,特别是如何构架目录树。具体来说就是控制VFS的结构体,让它们互相关联。

    其实,归根到底, 其实最终目的不过是要在内存中建立一颗 VFS 目录树而已。

    init_mount_tree() 这个函数为 VFS 建立了根目录 "/",而一旦有了根,那么这棵数就可以发展壮大,比如可以通过系统调用 sys_mkdir 在这棵树上建立新的叶子节点等,所以系统设计者又将 rootfs 文件系统挂载到了这棵树的根目录上。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值