VFS(虚拟文件系统)

Linux文件系统

在使用计算机的过程中,文件是经常被提到的概念,例如可执行文件、文本文件等,这里说的文件是一个抽象的概念,
它是存放一切数据化信息的仓库。用户为了保存数据或信息,首先要创建-一个文件,然后把数据或信息写人该文件。
最终这些数据被保存到文件的载体上,通常情况下是磁盘上,只要给出存放文件的路径和文件名,文件系统就可以
在磁盘上找到该文件的物理位置,并把它调人内存供用户使用。在这个过程中文件系统起着举足轻重的作用,
通过文件系统我们才能根据路径和文件名访问到文件,以及对文件进行各种操作。

从系统角度来看,文件系统是对文件存储器空间进行组织和分配,负责文件的存储并对存人的文件进行保护和检索的系统。
具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。

文件结构

文件结构是文件存放在磁盘等存储设备上的组织方法。主要体现在对文件和目录的组织上。目录提供了管理文件的一个方便而有效的途径。Linux 使用标准的目录结构,在Linux安装的时候,安装程序就已经为用户创建了文件系统和完整而固定的目录组成形式,并指定了每个目录的作用和其中的文件类型。
Linux采用的是树状结构。最上层是根目录,其他所有目录都是从根目录出发而生成的。 微软的DOS和Windows也是采用树状结构,但是在DOS和Windows中这样的树状结构的根是磁盘分区的盘符,有几个分区就有几个树状结构,它们之间的关系是并列的。但是在Linux中,无论操作系统管理几个磁盘分区,这样的目录树只有一个。

下面列出一些主要目录的功能:

/bin 二进制可执行命令。
/dev 设备特殊文件。
/etc 系统管理和配置文件。
/home 用户主目录的基点,比如用户user 的主目录就是/ home/ user。
/lib 标准程序设计库,又叫动态链接共享库。
/sbin 系统管理命令,这里存放的是系统管理员使用的管理程序。
/tmp 公用的临时文件存储点。
/root 系统管理员的主目录。
/mnt 用户临时安装其他文件系统的目录。
/proc 虚拟的目录,不占用磁盘空间,是系统内存的映射。可直接访问这个目录来获取系统信息。
/var 某些大文件的溢出区,例如各种服务的日志文件。
/usr 最庞大的目录,要用到的应用程序和文件几乎都在这个目录下。

索引节点

Linux文件系统使用索引结点来记录文件信息。 索引结点是一个数据结构,它包含文件的长度、创建时间、修改时间、权限、所属关系、磁盘中的位置等信息。每个文件或目录都对应一个索引结点,文件系统把所有的索引结点形成一个数组,系统给每个索引结点分配一个号码,也就是该结点在数组中的索引号,称为索引结点号。文件系统正是靠这个索引结点号来识别文件的。可以用 Is一i 命令查看文件的索引结点。

注:我这里只简单的提了些Linux文件系统的基础,想详细了解的可以去看下《鸟哥的Linux私房菜》。

虚拟文件系统

Linux最初采用的是MINIX的文件系统,但是,MINIX是一-种教学用操作系统,其文件系统的大小限于64MB,文件名长度也限于14个字节。
所以,Linux经过一段时间的改进和发展,特别是吸取了UNIX文件系统多年改进所积累的经验,最后形成了Ext2 文件系统。

为了保证Linux的开放性,设计人员必须考虑如何使Linux除支持Ext2文件系统外,还能支持其他各种不同的文件系统。为此,就必须将各种不同文件系统的操作和管理纳入到一个统一的框架中,使得用户程序可以通过同一个文件系统界面,也就是同一组系统调用,能够对各种不同的文件系统以及文件进行操作。这样,用户程序就可以不关心各种不同文件系统的实现细节,而使用系统提供的统一、抽象、虚拟的文件系统界面。这种统一的框架就是所谓的虚拟文件系统转换(Virtual Filesystem Switch) ,一般简称虚拟文件系统(VFS)。

VFS与具体文件系统的关系:
VFS(虚拟文件系统)_第1张图片
可以看出:VFS相当于一个开关,各个文件系统相当于其下的分支,进程通过VFS层的系统调用到达各个分支对具体的文件系统进行操作

VFS中对象的引入

虚拟文件系统(VFS)的第一个词是“虚拟”,这就意味着,这样的文件系统在磁盘(或其他存储介质上)并没有对应的存储信息。
那么,这样一个虚无的文件系统到底是怎样形成的?
尽管Linux支持多达几十种文件系统,但这些真实的文件系统并不是一下子都挂在系统中的,它们实际上是按需被挂载的。
VFS的信息都来源于“实”的文件系统,所以VFS必须承载各种文件系统的共有属性。
另外,这些实的文件系统只有安装到系统中,VFS才予以认可,也就是说,VFS只管理挂载到系统中的实际文件系统。

那么,VFS到底要管理那些对象呢?
因为Linux在文件系统的设计中,全然汲取了UNIX的设计思想。而UNIX在文件系统的设计中抽象出4个概念:文件,目录项,索引节点,超级块

超级块对象:描述已安装文件系统。
索引节点对象:描述一个文件。
目录项对象:描述一个目录项(路径的组成部分)。
文件对象:描述由进程打开的文件。

下面我们会详细介绍VFS的4个主要对象。

注:Linux下“万物皆文件”,所以没有目录对象,也就是说,目录不同与目录项,却和文件相同。

超级块对象

超级块用来描述整个文件系统的信息。对每个具体的文件系统来说,都有各自的超级块,如Ext2超级块和Ext3超级块,它们存放于磁盘上。
当内核在对一个文件系统进行初始化和注册时在内存为其分配一个超级块,这就是VFS超级块。
也就是说,VFS超级块是各种具体文件系统在安装时建立的,并在这些文件系统卸载时被自动删除,可见,VFS超级块只存在于内存中。
/* 
 * 超级块结构中定义的字段非常多,
 * 这里只介绍一些重要的属性
 */
struct super_block 
{
     
    struct list_head    s_list;               /* 指向所有超级块的链表 */
    const struct super_operations    *s_op; /* 超级块方法 */
    struct dentry        *s_root;           /* 目录挂载点 */
    struct mutex        s_lock;            /* 超级块信号量 */
    struct list_head    s_inodes;           /* inode链表 */
    struct mtd_info        *s_mtd;            /* 存储磁盘信息 */
    ...
    void *s_fs_info;						/*指向具体文件系统的超级块*/
};

struct super_operations 
{
     
    void (*read_inode) (struct inode *);                 /* 从磁盘读取某个文件系统的inode */
    int (*write_inode) (struct inode *, int);             /* 将索引节点写入磁盘,wait表示写操作是否需要同步 */
    void (*put_inode) (struct inode *);           		  /* 逻辑上释放索引节点 */
    void (*delete_inode) (struct inode *);                /* 从磁盘上删除索引节点 */
    void (*put_super) (struct super_block *);             /* 卸载文件系统时由VFS调用,用来释放超级块 */
    void (*write_super) (struct super_block *);           /* 用给定的超级块更新磁盘上的超级块 */
    ...
};

与超级块关联的方法就是超级块操作表, 由super_operations来描述。
所有超级块对象都以双向循环链表的形式链接在一起。

索引节点对象

文件系统处理文件所需要的所有信息都放在称为索引结点的数据结构中。
文件名可以随时更改,但是索引结点对文件是唯一的,并且随文件的存在而存在。
具体文件系统的索引结点是存放在磁盘上的,是一种静态结构,要使用它,必须调入内存,填写VFS的索引结点。
因此,也称VFS索引结点是动态结点。
/* 
 * 索引节点结构中定义的字段非常多,
 * 这里只介绍一些重要的属性
 */
struct inode 
{
     
    struct hlist_node    i_hash;     /* 散列表,用于快速查找inode */
    struct list_head    i_list;        /* 索引节点链表 */
    struct list_head    i_sb_list;  /* 超级块链表超级块  */
    struct list_head    i_dentry;   /* 目录项链表 */
    unsigned long        i_ino;      /* 节点号 */
    unsigned int        i_nlink;    /* 硬链接数 */
    uid_t            i_uid;          /* 使用者id */
    gid_t            i_gid;          /* 使用组id */
    struct timespec        i_atime;    /* 最后访问时间 */
    struct timespec        i_mtime;    /* 最后修改时间 */
    struct timespec        i_ctime;    /* 最后改变时间 */
    const struct inode_operations    *i_op;  /* 索引节点操作函数 */
    const struct file_operations    *i_fop;    /* 缺省的索引节点操作 */
    struct super_block    *i_sb;              /* 相关的超级块 */
    struct address_space    *i_mapping;     /* 相关的地址映射 */
    struct address_space    i_data;         /* 设备地址映射 */
    unsigned int        i_flags;            /* 文件系统标志 */
    void            *i_private;             /* fs 私有指针 */
    ...
};
 
struct inode_operations 
{
     
    /* 创造一个新的磁盘索引节点 */
    int (*create) (struct inode *,struct dentry *,int);
    /* 在特定文件夹中寻找索引节点,该索引节点要对应于dentry中给出的文件名 */
    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
    /* 创建硬链接 */
    int (*link) (struct dentry *,struct inode *,struct dentry *);
    /* 从一个符号链接查找它指向的索引节点 */
    void * (*follow_link) (struct dentry *, struct nameidata *);
    /* 在 follow_link调用之后,该函数由VFS调用进行清除工作 */
    void (*put_link) (struct dentry *, struct nameidata *, void *);
    /* 该函数由VFS调用,用于修改文件的大小 */
    void (*truncate) (struct inode *);
    ...
};

与索引节点关联的方法就是索引节点操作表, 由inode_operations来描述。
不同的文件系统,其每个函数的具体实现是不同的,也不是每个函数都必须实现,没有实现的函数对应的域应置为NULL。

目录项对象

每个文件除了有一个索引结点inode数据结构外,还有一个目录项dentry数据结构,dentry结构中有个d_inode指针指向相应的inode结构。
那么,既然inode结构和dentry结构都是对文件各方面属性的描述,那为什么不把这两个结构合二为一呢?
这是因为二者所描述的目标不同:
dentry结构代表的是逻辑意义上的文件,所描述的是文件逻辑上的属性,因此,目录项对象在磁盘上并没有对应的映像;
而inode结构代表的是物理意义上的文件,记录的是物理上的属性,对于一个具体的文件系统,它在磁盘上就有对应的映像。
所以说,一个索引结点对象可能对应多个目录项对象。

在使用的时候在内存中创建目录项对象,其实通过索引节点已经可以定位到指定的文件,
但是索引节点对象的属性非常多,在查找,比较文件时,直接用索引节点效率不高,所以引入了目录项的概念。
每个目录项对象都有三种状态:被使用,未使用和负状态

  • 被使用:对应一个有效的索引节点,并且该对象由一个或多个使用者
  • 未使用:对应一个有效的索引节点,但是VFS当前并没有使用这个目录项
  • 负状态:没有对应的有效索引节点(可能索引节点被删除或者路径不存在了)

目录项的目的就是提高文件查找,比较的效率,所以访问过的目录项都缓存在slab中。

/* 目录项对象结构 */
struct dentry 
{
     
    atomic_t d_count;       /* 使用计数 */
    unsigned int d_flags;   /* 目录项标识 */
    spinlock_t d_lock;        /* 单目录项锁 */
    int d_mounted;          /* 是否登录点的目录项 */
    struct inode *d_inode;    /* 相关联的索引节点,通过这个索引节点就可以读取到文件数据 */
    struct hlist_node d_hash;    /* 目录项形成的哈希表 */
    struct dentry *d_parent;    /* 父目录的目录项对象 */
    struct qstr d_name;         /* 目录项名称 */
    struct list_head d_lru;        /* 未使用的链表 */
    struct list_head d_subdirs;    /* 子目录链表 */
    struct list_head d_alias;    /* 索引节点别名链表 */
    unsigned long d_time;        /* 重置时间 */
    const struct dentry_operations *d_op; /* 目录项操作相关函数 */
    struct super_block *d_sb;    /* 文件的超级块 */
    void *d_fsdata;            /* 文件系统特有数据 */
    unsigned char d_iname[DNAME_INLINE_LEN_MIN];    /* 短文件名 */
    ...
};
 
struct dentry_operations 
{
     
    /* 该函数判断目录项对象是否有效。VFS准备从dcache中使用一个目录项时会调用这个函数 */
    int (*d_revalidate)(struct dentry *, struct nameidata *);
    /* 为目录项对象生成hash值 */
    int (*d_hash) (struct dentry *, struct qstr *);
    /* 比较 qstr 类型的2个文件名 */
    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
    /* 当目录项对象的 d_count 为0时,VFS调用这个函数 */
    int (*d_delete)(struct dentry *);
    /* 当目录项对象将要被释放时,VFS调用该函数 */
    void (*d_release)(struct dentry *);
    /* 当目录项对象丢失其索引节点时(也就是磁盘索引节点被删除了),VFS会调用该函数 */
    void (*d_iput)(struct dentry *, struct inode *);
    char *(*d_dname)(struct dentry *, char *, int);
    ...
};

与目录项关联的方法就是目录项操作表, 由dentry_operations来描述。
一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表一个文件,要么代表一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。但是,一个inode却可能对应着不止一个dentry结构;也就是说,一个文件可以有不止一个文件名或路径名。所以在inode结构中有一个队列i_dentry;凡是代表着同一个文件的所有目录项都通过其dentry结构中的d_alias域挂入相应inode结构中的i_dentry队列。

文件对象

每个打开的文件都用一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置或偏移量。
每次打开一个文件,文件位置一般都被置为0,此后的读或写操作都将从文件的开始处进行,但是可以通过执行系统调用 lseek(随机定位)
对这个文件位置进行修改。Linux在 file文件对象中保存了打开文件的文件位置,这个对象称为打开的文件描述(Open File Description)。

那么,为什么不把文件位置存放在索引结点中,而要设一个 file数据结构呢?
struct file 
{
     
    struct list_head f_list;				/* 所有打开的文件形成一个链表 */
    struct dentry *f_dentry;				/* 与文件相关的目录项对象 */
    struct vfsmount *f_vfsmnt;				/* 该文件所在的已安装文件系统 */
    loff_t f_pos;							/* 文件的当前位置 */
    const struct file_operations    *f_op; /* 文件操作函数 */
    unsigned short f_count;        		/* 文件对象引用计数 */
};

struct file_operations 
{
     
    /* 用于更新偏移量指针,由系统调用lleek()调用它 */
    loff_t (*llseek) (struct file *, loff_t, int);
    /* 由系统调用read()调用它 */
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    /* 由系统调用write()调用它 */
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    /* 由系统调用 aio_read() 调用它 */
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    /* 由系统调用 aio_write() 调用它 */
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    /* 将给定文件映射到指定的地址空间上,由系统调用 mmap 调用它 */
    int (*mmap) (struct file *, struct vm_area_struct *);
    /* 创建一个新的文件对象,并将它和相应的索引节点对象关联起来 */
    int (*open) (struct inode *, struct file *);
    /* 当已打开文件的引用计数减少时,VFS调用该函数 */
    int (*flush) (struct file *, fl_owner_t id);
};

VFS(虚拟文件系统)_第2张图片
VFS(虚拟文件系统)_第3张图片

你可能感兴趣的:(Linux,linux)