Linux Kernel - The Virtual Filesystem

虚拟文件系统是内核的子系统,实现了供用户空间编程用的文件系统相关的接口,其抽象特性使得不同的文件系统看起来用起来都一样,如将 ext2, ext3 格式的文件系统 mount 到不同的目录,ls 指令可以查看两个不同的文件系统内容。
        

Unix Filesystems

文件系统是数据的分层存储,这些数据遵循特定的结构。在 Unix 中,文件系统挂载在称为 namespace 的全局层次结构中的特定挂载点,这使所有挂载的文件系统显示为单个树中的条目。文件是一个有序的字节串,首字节表示文件头,末字节表示文件尾。目录实际上也是文件,列出了包含在其中的文件,由于这种观念,文件的操作同样适合于目录操作。Unix 系统对文件的概念和其相关信息作了区分,如访问权限,产生时间等,这些称为文件元数据,存储在另一个数据结构中,inode(index node),所有的信息(文件系统及其控制信息)联合在一起存储在超级块中。

Unix 文件系统将这些概念(文件、目录项、索引节点、超级块)实现为其物理磁盘布局的一部分。非 Unix 文件系统,如FAT或NTFS,仍然可以在Linux中工作,但是它们的文件系统代码必须表现出这些概念。VFS 就是设计来和这些文件系统工作的。

VFS Objects and Their Data Structures

VFS 是面向对象的。。。包括 4 个基本对象类型:
1. 超级块对象,代表一个特定的挂载的文件系统,对应存储在磁盘指定区域的文件系统超级块。
2. 索引节点对象,代表内核对文件或目录操作所需的所有信息,可以从磁盘获取。
3. 目录项对象,是一个路径中包括文件在内的所有组件,如  /bin/vi 中, /,bin,vi 均为目录项对象,不对应磁盘上任何数据结构
4. 文件对象,代表和一个进程关联的打开的文件,不对应磁盘上任何数据结构,指向相关联的目录项对象
既然面向对象了,那数据抽象和行为抽象应有体现,每个对象都包含一个 operations 对象

  • super_operations object  - write_inode() / sync_fs() /...  kernel 对指定文件系统调用
  • inode_operations object - create() /link() /...  kernel 对指定文件调用
  • dentry_operations object - d_compare() / d_delete() / ...   kernel 对指定目录项调用
  • file_operations object - read() / write()  / ...  kernel 进程对打开的的文件调用

目录项状态:used,unused,negative
used dentry → a valid inode (d_inode points to an associated inode && d_count > 0)
unused dentry → a valid inode (d_inode points to an inode && d_count = 0 )
negative dentry → not associated with a valid inode (d_inode = NULL)
目录项缓存:把路径名解析成目录项对象后缓存起来,避免每次访问时重复解析。

Data Structures Associated with Filesystems
file_system_type 描述每个文件系统的功能和行为。

#include <linux/fs.h>
struct file_system_type {
    const char          *name;     /* filesystem’s name */
    int                 fs_flags;  /* filesystem type flags */

    /* the following is used to read the superblock off the disk */
    struct super_block *(*get_sb) (struct file_system_type *, int,
                                   char *, void *);

    /* ... */
};

 vfsmount 记录所有的挂载点信息。

#include <linux/mount.h>

struct vfsmount {
    struct list_head mnt_hash;         /* hash table list */
    struct vfsmount  *mnt_parent;      /* parent filesystem */
    struct dentry    *mnt_mountpoint;  /* dentry of this mount point */
    struct dentry    *mnt_root;        /* dentry of root of this fs */

    /* ... */
};

Data Structures Associated with a Process
三个数据结构将 VFS layer 和系统进程联系在一起,files_struct, fs_struct, and namespace
与进程相关的文件和文件描述符信息都包含在 files_struct 内,fd_array 指向打开的文件对象列表。

#include  <linux/fdtable.h>

struct files_struct {
    atomic_t           count;      /* usage count */
    struct  fdtable    *fdt;       /* pointer to other fd table */
    struct  fdtable    fdtab;      /* base fd table */
    spinlock_t         file_lock;  /* per-file lock */
    int                next_fd;    /* cache of next available fd */
    struct embedded_fd_set close_on_exec_init; /* list of close-on-exec fds */
    struct embedded_fd_set open_fds_init       /* list of open fds */
    struct file            *fd_array[NR_OPEN_DEFAULT];    /* base files array */
};

fs_struct 包含与进程相关的文件系统信息,并由进程描述符中的 fs 字段指向。保存了当前目录和根目录。

#include  <linux/fs_struct.h>

struct fs_struct {
    int      users; /* user count */
    rwlock_t lock; /* per-structure lock */
    int      umask; /* umask */
    int      in_exec; /* currently executing a file */
    struct  path     root; /* root directory */
    struct  path     pwd;  /* current working directory */
};

namespace 使每个进程对于已挂载的文件系统的拥有唯一视图,不只是根目录,而是整个文件系统层次结构。

扫描二维码关注公众号,回复: 13302610 查看本文章
#include <linux/mnt_namespace.h>

struct mnt_namespace {
    atomic_t         count; /* usage count */
    struct vfsmount  *root; /* root directory */
    struct list_head list;  /* list of mount points */
    wait_queue_head_t poll; /* polling waitqueue */
    int               event;  /* event count */
};

ext2 文件系统

一个磁盘可以划分成多个分区,每个分区必须先用格式化工具(例如某种mkfs命令)格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局的信息。下图是一个磁盘分区格式化成ext2文件系统后的存储布局。

文件系统中存储的最小单位是块(Block),一个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、 2048或4096字节。而上图中启动块( Boot Block)的大小是确定的,就是1KB,启动块是由PC标准规定的,用来存储磁盘分区信息和启动 信息,任何文件系统都不能使用启动块。启动块之后才是ext2文件系统的开始,ext2文件系统将 整个分区划成若干个同样大小的块组( Block Group),每个块组都由以下部分组成。

  • 超级块( Super Block)
    描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。 超级块在每个块组的开头都有一份拷贝。
  • 块组描述符表( GDT, Group Descriptor Table)
    由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组 描述符( Group Descriptor) 存储一个块组的描述信息,例如在这个块组中从哪里开始 是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类 似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级 块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数 据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文 件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当 第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。
  • 块位图( Block Bitmap)
    用来描述整个块组中哪些块已用哪些块空闲的, 它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这 个bit为0表示该块空闲可用。
    为什么用df命令统计整个磁盘的已用空间非常快呢?因为只需要查看每个块组的块位图即 可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因 为不可避免地要搜遍整个目录的所有文件。
  • inode位图( inode Bitmap)
    一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型 (常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命 令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一 个块组中的所有inode组成了inode表。
  • 数据块( Data Block)
    对于常规文件,文件的数据存储在数据块中。
    对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它 所在目录的数据块中,除文件名之外, ls -l 命令看到的其它信息都保存在该文件 的inode中。
    对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目 标路径名较长则分配一个数据块来保存。
    设备文件、 FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号 保存在inode中。

VFS 文件操作如下图

猜你喜欢

转载自blog.csdn.net/wu472269100/article/details/104678587
今日推荐