虚拟文件系统
博客专区 > China_OS 的博客 > 博客详情
虚拟文件系统
China_OS 发表于5年前
虚拟文件系统
  • 发表于 5年前
  • 阅读 323
  • 收藏 8
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

    虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口,系统中所有的文件系统不但依赖VFS共存,而且也依靠VFS系统共同工作。

通用文件系统接口
    VFS使得用户可以直接调用open、read等系统调用,而无需考虑具体文件系统和实际物理介质。这使得这些通用的系统调用可以跨越各种不同的文件系统和不同的介质执行。例如:我们可以使用标准的系统调用从一个文件系统拷贝文件到另一个文件系统,VFS把各种不同的文件系统抽象后采用统一的方式进行操作。VFS与块IO相结合,提供抽象、接口以及交融,使得用户空间的程序调用统一的系统调用访问各种文件,不管文件系统是什么,也不管文件系统位于何种介质,采用的命名策略是统一的。

文件系统抽象层
    之所以使用这种通用接口对所有类型的文件系统进行操作,是因为内核在他的底层文件系统接口上建立了一个抽象层,该抽象层使linux能够支持各种文件系统,即使他们在功能和行为上存在很大差别。为了支持多文件系统,VFS提供了一个通用的文件系统模型,该模型囊括了任何文件系统的常用功能和行为,他定义了所有文件系统都支持的、基本的、概念上的接口和数据结构,同时实际的文件系统也将自己的一些概念在形式上与VFS的定义保持一致。


unix文件系统
    unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装节点。文件系统使特殊的数据分层存储结构,它包含文件、目录和相关控制信息。在unix中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称为命名空间,所有已安装的文件系统都作为根文件系统树的树叶出现在系统中。文件其实可以做一个有序字节串,字节串中第一个字节是文件开头,最后一个字节是文件结尾,每个文件便于系统和用户调用,都被分配了一个便于理解的名字。文件通过目录组织起来,文件目录好比一个文件夹,用来容纳相关文件,在unix中目录属于普通文件,他列出包含在其中的文件。unix系统将文件的相关信息和文件本身这两个概念加以区分,例如访问权限等,这些文件相关信息有时被称为元数据,存贮在一个单独的数据结构中,该结构被称为索引节点(inode),所有这些信息都和文件系统的控制信息密切相关,文件系统的控制信息存储在超级块中。

VFS对象及其数据结构
    VFS其实采用的是面向对象的思路,使用一组数据结构来代表通用文件对象。这些数据结构类似于对象,使用C代码实现,VFS中有四个主要的对象类型:
         1 超级块对象
         2 索引节点对象
         3 目录项对象
         4 文件对象
    每个主要对象都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:
         super_operations对象包括内核针对特定文件系统所能调用的方法
         inode_operations对象包括内核针对特定文件所能调用的方法
         dentry_operations对象包括内核针对特定目录所能调用的方法
         file_operations对象包括进程针对已打开文件所能调用的方法
    操作对象作为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。

超级块对象
    各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常存放在磁盘特定扇区中的文件系统超级块或者文件系统控制块。对于非基于磁盘的文件系统,他们会在使用现场创建超级块并将其保存到内存中。

struct super_block {
        struct list_head        s_list;            /* list of all superblocks */
        dev_t                   s_dev;             /* identifier */
        unsigned long           s_blocksize;       /* block size in bytes */
        unsigned long           s_old_blocksize;   /* old block size in bytes */
        unsigned char           s_blocksize_bits;  /* block size in bits */
        unsigned char           s_dirt;            /* dirty flag */
        unsigned long long      s_maxbytes;        /* max file size */
        struct file_system_type s_type;            /* filesystem type */
        struct super_operations s_op;              /* superblock methods */
        struct dquot_operations *dq_op;            /* quota methods */
        struct quotactl_ops     *s_qcop;           /* quota control methods */
        struct export_operations *s_export_op;     /* export methods */
        unsigned long            s_flags;          /* mount flags */
        unsigned long            s_magic;          /* filesystem's magic number */
        struct dentry            *s_root;          /* directory mount point */
        struct rw_semaphore      s_umount;         /* unmount semaphore */
        struct semaphore         s_lock;           /* superblock semaphore */
        int                      s_count;          /* superblock ref count */
        int                      s_syncing;        /* filesystem syncing flag */
        int                      s_need_sync_fs;   /* not-yet-synced flag */
        atomic_t                 s_active;         /* active reference count */
        void                     *s_security;      /* security module */
        struct list_head         s_dirty;          /* list of dirty inodes */
        struct list_head         s_io;             /* list of writebacks */
        struct hlist_head        s_anon;           /* anonymous dentries */
        struct list_head         s_files;          /* list of assigned files */
        struct block_device      *s_bdev;          /* associated block device */
        struct list_head         s_instances;      /* instances of this fs */
        struct quota_info        s_dquot;          /* quota-specific options */
        char                     s_id[32];         /* text name */
        void                     *s_fs_info;       /* filesystem-specific info */
        struct semaphore         s_vfs_rename_sem; /* rename semaphore */
};
    文件系统在安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。在超级块对象中最重要的一个域是s_op,他指向超级块的操作函数表。 超级块操作函数表由super_operations结构体表示,该结构体中的每一项都是一个指向超级块操作函数的指针。当文件系统需要对超级块进行操作时,首先要在超级块对象中寻找需要的操作方法。

struct super_operations {
        struct inode *(*alloc_inode) (struct super_block *sb);
        void (*destroy_inode) (struct inode *);
        void (*read_inode) (struct inode *);
        void (*dirty_inode) (struct inode *);
        void (*write_inode) (struct inode *, int);
        void (*put_inode) (struct inode *);
        void (*drop_inode) (struct inode *);
        void (*delete_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        void (*write_super) (struct super_block *);
        int (*sync_fs) (struct super_block *, int);
        void (*write_super_lockfs) (struct super_block *);
        void (*unlockfs) (struct super_block *);
        int (*statfs) (struct super_block *, struct statfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
        void (*clear_inode) (struct inode *);
        void (*umount_begin) (struct super_block *);
        int (*show_options) (struct seq_file *, struct vfsmount *);
};
索引节点对象
    索引节点包含了内核在操作文件或目录时需要的全部信息。没有索引节点的文件系统通常将文件的描述信息作为文件的一部分来存放。不管哪种文件系统索引节点必须在内存中创建,以便文件系统使用。

struct inode {
        struct hlist_node       i_hash;              /* hash list */
        struct list_head        i_list;              /* list of inodes */
        struct list_head        i_dentry;            /* list of dentries */
        unsigned long           i_ino;               /* inode number */
        atomic_t                i_count;             /* reference counter */
        umode_t                 i_mode;              /* access permissions */
        unsigned int            i_nlink;             /* number of hard links */
        uid_t                   i_uid;               /* user id of owner */
        gid_t                   i_gid;               /* group id of owner */
        kdev_t                  i_rdev;              /* real device node */
        loff_t                  i_size;              /* file size in bytes */
        struct timespec         i_atime;             /* last access time */
        struct timespec         i_mtime;             /* last modify time */
        struct timespec         i_ctime;             /* last change time */
        unsigned int            i_blkbits;           /* block size in bits */
        unsigned long           i_blksize;           /* block size in bytes */
        unsigned long           i_version;           /* version number */
        unsigned long           i_blocks;            /* file size in blocks */
        unsigned short          i_bytes;             /* bytes consumed */
        spinlock_t              i_lock;              /* spinlock */
        struct rw_semaphore     i_alloc_sem;         /* nests inside of i_sem */
        struct semaphore        i_sem;               /* inode semaphore */
        struct inode_operations *i_op;               /* inode ops table */
        struct file_operations  *i_fop;              /* default inode ops */
        struct super_block      *i_sb;               /* associated superblock */
        struct file_lock        *i_flock;            /* file lock list */
        struct address_space    *i_mapping;          /* associated mapping */
        struct address_space    i_data;              /* mapping for device */
        struct dquot            *i_dquot[MAXQUOTAS]; /* disk quotas for inode */
        struct list_head        i_devices;           /* list of block devices */
        struct pipe_inode_info  *i_pipe;             /* pipe information */
        struct block_device     *i_bdev;             /* block device driver */
        unsigned long           i_dnotify_mask;      /* directory notify mask */
        struct dnotify_struct   *i_dnotify;          /* dnotify */
        unsigned long           i_state;             /* state flags */
        unsigned long           dirtied_when;        /* first dirtying time */
        unsigned int            i_flags;             /* filesystem flags */
        unsigned char           i_sock;              /* is this a socket? */
        atomic_t                i_writecount;        /* count of writers */
        void                    *i_security;         /* security module */
        __u32                   i_generation;        /* inode version number */
        union {
                void            *generic_ip;         /* filesystem-specific info */
        } u;
};
    一个索引节点代表文件系统中的一个文件,也可以是设备或管道之类的特殊文件。和超级块一样,索引节点对象中的inode_operations域也非常重要,他描述了VFS用以操作索引节点对象的所有方法,这些方法由文件系统实现。

struct inode_operations {
        int (*create) (struct inode *, struct dentry *,int);
        struct dentry * (*lookup) (struct inode *, struct dentry *);
        int (*link) (struct dentry *, struct inode *, struct dentry *);
        int (*unlink) (struct inode *, struct dentry *);
        int (*symlink) (struct inode *, struct dentry *, const char *);
        int (*mkdir) (struct inode *, struct dentry *, int);
        int (*rmdir) (struct inode *, struct dentry *);
        int (*mknod) (struct inode *, struct dentry *, int, dev_t);
        int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *);
        int (*readlink) (struct dentry *, char *, int);
        int (*follow_link) (struct dentry *, struct nameidata *);
        int (*put_link) (struct dentry *, struct nameidata *);
        void (*truncate) (struct inode *);
        int (*permission) (struct inode *, int);
        int (*setattr) (struct dentry *, struct iattr *);
        int (*getattr) (struct vfsmount *, struct dentry *, struct kstat *);
        int (*setxattr) (struct dentry *, const char *,const void *, size_t, int);
        ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
        ssize_t (*listxattr) (struct dentry *, char *, size_t);
        int (*removexattr) (struct dentry *, const char *);
};
目录项对象

    VFS把目录当做文件对待。路径中的每个组成部分都由一个索引节点对象表示,为了方便查找操作,VFS引入了目录项概念,每个dentry代表路径中的一个特定部分,在/bin/vi中:/、bin、vi都属于目录项,前两个是目录,后一个是普通文件。必须明确:在路径中每一部分都是目录项对象。VFS在执行目录项操作时会现场创建目录项对象。目录项对象由dentry结构体表示:

struct dentry {
        atomic_t                 d_count;      /* usage count */
        unsigned long            d_vfs_flags;  /* dentry cache flags */
        spinlock_t               d_lock;       /* per-dentry lock */
        struct inode             *d_inode;     /* associated inode */
        struct list_head         d_lru;        /* unused list */
        struct list_head         d_child;      /* list of dentries within */
        struct list_head         d_subdirs;    /* subdirectories */
        struct list_head         d_alias;      /* list of alias inodes */
        unsigned long            d_time;       /* revalidate time */
        struct dentry_operations *d_op;        /* dentry operations table */
        struct super_block       *d_sb;        /* superblock of file */
        unsigned int             d_flags;      /* dentry flags */
        int                      d_mounted;    /* is this a mount point? */
        void                     *d_fsdata;    /* filesystem-specific data */
        struct rcu_head          d_rcu;        /* RCU locking */
        struct dcookie_struct    *d_cookie;    /* cookie */
        struct dentry            *d_parent;    /* dentry object of parent */
        struct qstr              d_name;       /* dentry name */
        struct hlist_node        d_hash;       /* list of hash table entries */
        struct hlist_head        *d_bucket;    /* hash bucket */
        unsigned char            d_iname[DNAME_INLINE_LEN_MIN]; /* short name */
};

    与前面两个对象不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。目录项对象有三种状态:
        1 一个被使用的目录项对应一个有效的索引节点,并且表明该对象存在一个或者多个使用者。
        2 一个未被使用的目录项对应一个有效的索引节点,但是应指明VFS当前并未使用它,该目录项仍旧指向一个有效对象,被保存在缓存中以便需要时再使用它。
        3 一个负状态的目录项(无效目录项)没有对应的有效索引节点,因为索引节点已经被删除了,或者路径不正确,但是目录项仍然保留,以便快速解析以后的路径查询。

    VFS遍历路径名中的所有元素并将它们逐个的解析成目录项对象,还要达到最深层目录这是一个费力的工作,所以内核将目录项对象缓存在目录项缓存中(dcache)。缓存包括三部分:被使用的目录项链表,该链表通过索引节点对象中的i_dentry项连接相关索引节点,一个索引节点可能有多个连接,所以可能有多个目录项对象。最近被使用的双向链表,该链表包含了未被使用的和负状态的目录项对象,该链总是在头部插入数据,所以在回收内存时一般删除链尾的数据。散列表和相应的函数用来快速的将给定的路径解析为相关目录项对象。

    VFS一般会在目录项缓存中搜索路径名,如果找到了就无需花大力气解析了,如果在缓存中没有找到,VFS就必须自己通过遍历文件系统为每个路径分量解析路径,解析完毕后并将其目录项对象加入dcache中,而dcache在一定意义上也提供对索引节点的缓存,和目录项相关的索引节点对象不会被释放,因为目录项会让相关索引节点的使用计数为正,这样就可以保证索引节点留在内存中只要目录项被缓存,其相应的索引节点也就被缓存了。

文件对象
    文件对象表示进程已经打开的文件,文件对象包含我们非常熟悉的信息。文件对象是已打开文件在内存中的表示,该对象由open系统调用创建,由close系统调用撤销。多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象,文件对象仅仅在进程的观点上代表打开的文件而已,他反过来指向目录项对象,其实只有目录项对象才表示已打开的实际文件,虽然一个文件对应的文件对象不是唯一的,但其对应的索引节点和目录项对象是唯一的。文件对象由file结构体表示:

struct file {
        struct list_head       f_list;        /* list of file objects */
        struct dentry          *f_dentry;     /* associated dentry object */
        struct vfsmount        *f_vfsmnt;     /* associated mounted fs */
        struct file_operations *f_op;         /* file operations table */
        atomic_t               f_count;       /* file object's usage count */
        unsigned int           f_flags;       /* flags specified on open */
        mode_t                 f_mode;        /* file access mode */
        loff_t                 f_pos;         /* file offset (file pointer) */
        struct fown_struct     f_owner;       /* owner data for signals */
        unsigned int           f_uid;         /* user's UID */
        unsigned int           f_gid;         /* user's GID */
        int                    f_error;       /* error code */
        struct file_ra_state   f_ra;          /* read-ahead state */
        unsigned long          f_version;     /* version number */
        void                   *f_security;   /* security module */
        void                   *private_data; /* tty driver hook */
        struct list_head       f_ep_links;    /* list of eventpoll links */
        spinlock_t             f_ep_lock;     /* eventpoll lock */
        struct address_space   *f_mapping;    /* page cache mapping */
};
    文件操作表在文件对象中也非常重要,文件操作对象由file_operations结构体表示:

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, char *, size_t, loff_t);
        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        ssize_t (*aio_write) (struct kiocb *, const char *, size_t, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int);
        int (*aio_fsync) (struct kiocb *, int);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*readv) (struct file *, const struct iovec *,
                          unsigned long, loff_t *);
        ssize_t (*writev) (struct file *, const struct iovec *,
                           unsigned long, loff_t *);
        ssize_t (*sendfile) (struct file *, loff_t *, size_t,
                             read_actor_t, void *);
        ssize_t (*sendpage) (struct file *, struct page *, int,
                             size_t, loff_t *, int);
        unsigned long (*get_unmapped_area) (struct file *, unsigned long,
                                            unsigned long, unsigned long,
                                            unsigned long);
        int (*check_flags) (int flags);
        int (*dir_notify) (struct file *filp, unsigned long arg);
        int (*flock) (struct file *filp, int cmd, struct file_lock *fl);
};

和文件系统相关的数据结构
    除了以上几种VFS基础对象外,内核还使用另外一些标准的数据结构来管理文件系统的其他数据,一个是file_system_type,用来描述各种特定的文件系统类型。一个是vfsmount,用来描述一个安装文件系统的实例。

和进程相关的数据结构
    有三个数据结构将VFS层和系统的进程紧密的联系在一起,他们是:file_struct、fs_struct、namespace。
    file_struct,该结构体由进程描述符中的files目录项指向,所有与单个进程相关的信息都包含在里面。
    fs_struct,该结构体由进程描述符的fs域指向,他包含文件系统和进程相关信息。
    namespace,该结构体由进程描述符中的mmt_namespace域指向,他使得每一个进程在系统中都看到唯一的安装文件系统。默认情况下所有的进程共享同样的命名空间。


共有 人打赏支持
粉丝 392
博文 377
码字总数 482277
×
China_OS
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: