linux VFS概述以及内核源代码分析

报告内容

 一、           概述

Linux能够支持各种不同的文件系统是通过VFS实现的,由于不同的物理文件系统具有不同的组织结构和不同的处理方式,为了能够处理各种不同的物理文件系统,操作系统必须把它们所具有的特性进行抽象,并建立一个面向各种物理文件系统的转换机制,通过这个转换机制,把各种不同物理文件系统转换为一个具有统一共性的虚拟文件系统。
VFS是一个软件层,用来处理与 Unix标准文件系统相关的所有系统调用,是用户应用程序与文件系统实现之间的抽象层。它实际上向Linux 内核和系统中运行的进程提供了一个处理各种物理文件系统的公共接口,通过这个接口使得不同的物理文件系统在内核看来都是相同的。
VFS通用文件模型将文件以及其操作抽象为以下几种对象类型:超级块对象( superblock object)存放文件系统相关信息;索引节点对象(inode object)存放具体文件的一般信息;文件对象(file object)存放已打开的文件和进程之间交互的信息;目录项对象(dentry object)存放目录项与文件的链接信息。
 

 二、           VFS相关的数据结构以及其分析

1.超级块对象
VFS 超级块是各种具体文件系统在安装时建立的,并在这些文件系统卸载时自动删除,它只存在于内存中。VFS超级块在 inculde/Linux/fs.h 中定义,即数据结构 super_block ,该结构及其主要域的含义如下:

struct super_block {

      struct list_head       s_list;             /* 指向超级块链表的指针 */
/*
* 包含该具体文件系统的块设备标识符。

*例如,对于 /dev/hda1,其设备标识符为 0x301

*/
      kdev_t                   s_dev;
   /* 该具体文件系统中数据块的大小,以字节为单位 */
      unsigned long        s_blocksize;
/* 块大小的值占用的位数,例如,如果块大小为 1024 字节,则该值为 10*/
      unsigned char         s_blocksize_bits;
      unsigned char         s_dirt;          /* 修改标志 */
      unsigned long long s_maxbytes;       /* 文件的最大长度 */
      struct file_system_type   *s_type;
/* 指向某个特定的具体文件系统的用于超级块操作的函数集合的指针 */
      struct super_operations   *s_op;
      struct dquot_operations *dq_op;  /* 指向磁盘限额方法的指针 */
      unsigned long        s_flags;
   /* 魔数,即该具体文件系统区别于其它文系统的一个标志 */
      unsigned long        s_magic;
      struct dentry          *s_root;
      struct rw_semaphore      s_umount;
      struct semaphore    s_lock;
      int                 s_count;
      atomic_t         s_active;
 
      struct list_head       s_dirty;               /* 已修改索引节点的链表 */
      struct list_head       s_locked_inodes;      /* 涉及 I/O 的索引节点的链表 */
      struct list_head       s_files;               /* 分配给超级的文件对象的链表 */
 
      struct block_device *s_bdev;
      struct list_head       s_instances;
      struct quota_mount_options s_dquot;        /* 磁盘限额的选项 */
/* 一个共用体,其成员是各种文件系统的 fsname_sb_info 数据结构 */
      union {
             struct minix_sb_info      minix_sb;
             struct ext2_sb_info ext2_sb;
             struct ext3_sb_info ext3_sb;
             struct hpfs_sb_info hpfs_sb;
             struct ntfs_sb_info ntfs_sb;
             struct msdos_sb_info     msdos_sb;
             struct isofs_sb_info isofs_sb;
             struct nfs_sb_info   nfs_sb;
             struct sysv_sb_info sysv_sb;
             struct affs_sb_info affs_sb;
             struct ufs_sb_info   ufs_sb;
             struct efs_sb_info   efs_sb;
             struct shmem_sb_info    shmem_sb;
             struct romfs_sb_info      romfs_sb;
             struct smb_sb_info smbfs_sb;
             struct hfs_sb_info   hfs_sb;
             struct adfs_sb_info adfs_sb;
             struct qnx4_sb_info       qnx4_sb;
             struct reiserfs_sb_info    reiserfs_sb;
             struct bfs_sb_info   bfs_sb;
             struct udf_sb_info udf_sb;
             struct ncp_sb_info ncpfs_sb;
             struct usbdev_sb_info   usbdevfs_sb;
             struct jffs2_sb_info jffs2_sb;
             struct cramfs_sb_info     cramfs_sb;
             void               *generic_sbp;
      } u;
      struct semaphore s_vfs_rename_sem;
      struct semaphore s_nfsd_free_path_sem;
};
所有超级块对象(每个已安装的文件系统都有一个超级块)以双向环形链表的形式链接在一起。链表中第一个元素和最后一个元素的地址分别存放在super_blocks变量的s_list域的 next 和 prev域中。
2.索引节点对象
文件系统处理文件所需要的所有信息都放在称为索引节点的数据结构中。VFS索引节点的数据结构 inode /includ/fs/fs.h 中定义如下:
struct inode {
/********** 描述索引节点高速缓存管理的域 ***********/
      struct list_head       i_hash;      /* 指向哈希链表的指针 */
      struct list_head       i_list;       /* 指向索引节点链表的指针 */
      struct list_head       i_dentry;    /* 指向目录项链表的指针 */
     
      struct list_head       i_dirty_buffers;
      struct list_head       i_dirty_data_buffers;
/********** 描述文件信息的域 ****************/
      unsigned long        i_ino;     /* 索引节点号 */

      atomic_t         i_count;         /*引用计数器*/

      kdev_t                   i_dev;     /*设备标识号 */

      umode_t                i_mode;
      nlink_t                  i_nlink;
      uid_t                     i_uid;      /* 文件拥有者标识号 */
      gid_t                     i_gid;      /* 文件拥有者所在组的标识号 */
      kdev_t                   i_rdev;     /* 实际设备标识号 */
      loff_t                    i_size;
      time_t                   i_atime;    /* 文件的最后访问时间 */
      time_t                   i_mtime;    /* 文件的最后修改时间 */
      time_t                   i_ctime;    /* 节点的修改时间 */
      unsigned int           i_blkbits;   /* 块的位数 */
      unsigned long        i_blksize;    /* 块大小 */
      unsigned long        i_blocks;    /* 该文件所占块数 */
      unsigned long        i_version;    /* 版本号 */
      struct semaphore    i_sem;
      struct semaphore    i_zombie;   /* 僵死索引节点的信号量 */

      struct inode_operations *i_op;   /*索引节点的操作*/

      struct file_operations     *i_fop;    /* 指向缺省的文件操作 */

      struct super_block *i_sb;     /*指向该文件系统超级块的指针 */

      wait_queue_head_t i_wait;    /* 指向索引节点等待队列的指针 */
      struct file_lock       *i_flock;   /* 指向文件加锁链表的指针 */
/************ 用于分页机制的域 ***************/
      struct address_space       *i_mapping; /* 把所有可交换的页面管理起来 */
      struct address_space       i_data;
      struct dquot           *i_dquot[MAXQUOTAS];
      /* 以下几个域应当是联合体 */
      struct list_head       i_devices;     /* 设备文件形成的链表 */

      struct pipe_inode_info   *i_pipe;   /*指向管道文件*/

      struct block_device *i_bdev;      /*指向块设备文件的指针*/

      struct char_device   *i_cdev;      /* 指向字符设备文件的指针 */
 
      unsigned long        i_dnotify_mask;    /* 目录通知事件标志 */
      struct dnotify_struct       *i_dnotify;
      unsigned long        i_state;          /* 索引节点的状态标志 */
      unsigned int           i_flags;        /* 文件系统的安装标志 */
      unsigned char         i_sock;        /* 如果是套接字文件则为真 */
 
      atomic_t         i_writecount;       /* 写进程的引用计数 */
      unsigned int           i_attr_flags;    /* 文件创建标志 */
      __u32                   i_generation;
    /*
* 类似于超级块的一个共用体,其成员是各种具体
* 文件系统的 fsname_inode_info 数据结构
*/
      union {
             struct minix_inode_info        minix_i;
             struct ext2_inode_info          ext2_i;
             struct ext3_inode_info          ext3_i;
             struct hpfs_inode_info          hpfs_i;
             struct ntfs_inode_info           ntfs_i;
             struct msdos_inode_info        msdos_i;
             struct umsdos_inode_info      umsdos_i;
             struct iso_inode_info            isofs_i;
             struct nfs_inode_info            nfs_i;
             struct sysv_inode_info          sysv_i;
             struct affs_inode_info           affs_i;
             struct ufs_inode_info            ufs_i;
             struct efs_inode_info            efs_i;
             struct romfs_inode_info        romfs_i;
             struct shmem_inode_info             shmem_i;
             struct coda_inode_info          coda_i;
             struct smb_inode_info           smbfs_i;
             struct hfs_inode_info            hfs_i;
             struct adfs_inode_info           adfs_i;
             struct qnx4_inode_info         qnx4_i;
             struct reiserfs_inode_info      reiserfs_i;
             struct bfs_inode_info            bfs_i;
             struct udf_inode_info            udf_i;
             struct ncp_inode_info           ncpfs_i;
             struct proc_inode_info          proc_i;
             struct socket                  socket_i;
             struct usbdev_inode_info        usbdev_i;
             struct jffs2_inode_info          jffs2_i;
             void                      *generic_ip;
      } u;
};
3.目录项对象

dentry 的定义在include/linux/dcache.h中:

    struct dentry {
        atomic_t d_count;              /* 目录项引用计数器 */

        unsigned int d_flags;          /*目录项标志*/

        struct inode * d_inode;       /*与文件名关联的索引节点*/

        struct dentry * d_parent;       /*父目录的目录项*/

        struct list_head d_hash;        /*目录项形成的哈希表*/

        struct list_head d_lru;         /*未使用的 LRU 链表*/

        struct list_head d_child;       /*父目录的子目录项所形成的链表*/

        struct list_head d_subdirs;     /* 该目录项的子目录所形成的链表 */
        struct list_head d_alias;       /* 索引节点别名的链表 */
        int d_mounted;              /* 目录项的安装点 */

        struct qstr d_name;             /*目录项名(可快速查找) */

        unsigned long d_time;           /* d_revalidate函数使用*/

        struct dentry_operations *d_op;   /* 目录项的函数集 */
        struct super_block * d_sb;         /* 目录项树的根(即文件的超级块) */

     unsigned long d_vfs_flags;  

        void * d_fsdata;                /* 具体文件系统的数据 */

        unsigned char d_iname[DNAME_INLINE_LEN];   /*短文件名*/

};
4.文件对象
file 结构在 include/linux/fs.h 中定义如下:
struct file {
struct list_head        f_list;      /* 所有打开的文件形成一个链表 */

       struct dentry          *f_dentry;      /*指向相关目录项的指针*/

       struct vfsmount         *f_vfsmnt;/*指向VFS安装点的指针*/

       struct file_operations     *f_op;     /*指向文件操作表的指针*/

       atomic_t         f_count;            /* 文件对象的引用计数器 */

       unsigned int          f_flags;        /*打开文件时所指定的标志*/

       mode_t                  f_mode;        /* 文件的打开模式 */
       loff_t                    f_pos;         /* 文件的当前位置 */
/* 预读标志、要预读的最多页面数、上次预读后的文
件指针、预读的字节数以及预读的页面数 */

       unsigned long               f_reada, f_ramax, f_raend, f_ralen, f_rawin;

       struct fown_struct   f_owner;

       unsigned int           f_uid, f_gid;

       int                 f_error;

       unsigned long        f_version;

       void               *private_data;    /* tty 驱动程序所需 */
       /* 用于直接访问缓冲区的描述符 */

       struct kiobuf          *f_iobuf;

       long               f_iobuf_lock;
};
 

 三、           文件系统的mount操作

把一个文件系统(或设备)安装到一个目录点时要用到的主要数据结构为vfsmount,定义于include/linux/mount.h中:
struct vfsmount
{

        struct list_head mnt_hash;

        struct vfsmount *mnt_parent;    /*所挂载的文件系统*/

        struct dentry *mnt_mountpoint;  /*挂载点目录*/

        struct dentry *mnt_root;        /*挂载文件树的根目录*/

        struct super_block *mnt_sb;    /*指向超级块的指针*/

        struct list_head mnt_mounts;   /*描述符父链表的头*/

        struct list_head mnt_child;     /*用于描述父链表的指针*/

        atomic_t mnt_count;

        int mnt_flags;

        char *mnt_devname;          /*设备名称*/

        struct list_head mnt_list;

};
用户(一般是root ) 在挂载文件系统时,要指定三种信息:文件系统的名称、包含文件系统的物理块设备和文件系统在已有文件系统中的挂载点。

例如: # mount 2t msdos/dev/hdc/mnt/usr其中msdos 是要安装的文件系统类型, /dev/hdc 是文件系统所在的设备,/mnt/usr是安装点。

VFS 对上述命令的执行过程如下:

(1) 寻找对应的文件系统信息,VFS 通过filesystems在file- system- type 组成的链表中根据指定的文件系统名称搜索文件系统类型信息。

(2) 如果在上述链表中找到匹配的文件系统,则说明内核具有对该文件系统的内建支持。 否则,说明该文件系统可能由可装载模块支持,VFS 会请求内核装入相应原文件系统模块,此时,该文件系统在VFS 中注册并初始化。

(3) 不管是哪种情况,如果VFS 无法找到指定的文件系统,则返回错误。

(4) VFS 检验指定的物理块设备是否已经安装。 如果指定的块设备已被安装,则返回错误。

(5) VFS 查找作为新文件系统安装点目录的VFS索引节点,该VFS 索引节点可在索引节点高速缓存中,也有可能需要从安装点所在的块设备中读取。
(6) 如果该挂载点已经安装有其它文件系统则返回错误。 因为同一目录只能同时安装一个文件系统。
(7) VFS 安装代码为新的文件系统分配超块,并将安装信息传递给该文件系统的超块读取例程。
(8) 文件系统的超块读取例程将对应文件系统的信息映射到VFS 超块中。 如果在此过程中发生错误,则返回错误。
 

 四、           VFS的read操作

Linux中应用程序对read()的系统调用,将引起内核调用sys_read()服务例程,这完全和其它系统调用类似。在fs/目录下的read_write.c文件有sys_read()的定义,如下:

asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)

{

       ssize_t ret;

       struct file * file;

       ret = -EBADF;

       file = fget(fd);

       if (file) {

              if (file->f_mode & FMODE_READ) {

                     ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,

                                          file, file->f_pos, count);

                     if (!ret) {

                            ssize_t (*read)(struct file *, char *, size_t, loff_t *);

                            ret = -EINVAL;

                            if (file->f_op && (read = file->f_op->read) != NULL)

                                   ret = read(file, buf, count, &file->f_pos);
                     }
              }

              if (ret > 0)

                     dnotify_parent(file->f_dentry, DN_ACCESS);

              fput(file);
       }

       return ret;

}
由以上代码可以看到,sys_read()定义了一个file结构(参见“二、4.文件对象”)的指针,然后调用了其f_op字段中的中指向read方法的指针。这样一来,应用程序对read的系统调用就被转化为相对间接的调用:file->f_op->read(…);
struct file_operations {

       struct module *owner;

       loff_t (*llseek) (struct file *, loff_t, int);

       ssize_t (*read) (struct file *, char *, size_t, loff_t *);

……
};
super_block对象中最后一个u联合体对象包含属于具体文件系统的超级块信息,例如,若超级块对象指的是ntfs文件系统,该字段就存放ntfs_sb_info数据结构,该结构包含磁盘分配位掩码和其它与VFS的通用文件模型无关的数据。inode对象中也有一个u联合体字段,用于存放属于具体文件系统的索引节点信息,如果索引节点指的是一个ntfs文件,该字段就存放一个名为ntfs_inode_info的数据结构。
每一个文件系统都有自己的文件操作(file_operations)集合,来执行read()、write()之类的操作。当内核将一个索引节点从磁盘装入内存,就会把指向这些文件操作的指针存放在file_operations结构中,而该结构的地址存放在索引节点对象的i_fop字段中。当进程打开这个文件时,VFS就用存放在索引节点中的这个地址初始化新文件对象的f_op字段,使得对文件操作的后续调用能够使用这些函数。如果需要,VFS也可以通过在f_op字段中存放一个新值而修改文件操作的集合。loff_t和ssize_t正是与具体文件系统相关的两个变量。

你可能感兴趣的:(Linux)