文件系统磁盘管理

磁盘空间布局

  Extx将磁盘划分为等份的若干区域(最后一个区域可能会小一些),这些区域称为块组(block group)。磁盘以块组为单位进行管理。每个块组再划分为相同大小的block,这些block按功能分为原数据区和数据区。原数据区域也是占用block空间,但是是用于描述管理磁盘的信息,其中块0的原数据区域是相对比较复杂的,其包含了引导块、超级块、块组描述符、GDT、数据块位图、inode位图、inode表。出块组0外的其他块组就稍微简单些只有数据位图、inode位图、inode表。

  • Boot block:引导块,引导操作系统启动的区域,其并非文件系统的一部分,而是预留给操作系统使用的。
  • super block:超级块,文件系统的入口,记录了整个文件系统的描述信息,如格式化时指定的文件系统逻辑块大小、逻辑块的数量、inode的数量、根节点的ID和文件系统的特性相关信息。文件系统挂载时会从这里读取获取信息,可以通过dumpe2fs命令查看文件系统的超级块信息。
  • GDT:group descriptor table,块组描述表,紧跟在超级块之后,对块组信息的描述。块组描述符信息时以列表的形式组织,每个列表包括对应块组中数据位图的位置、inode位图的位置、inode表的位置信息。
  • Reserved GDT:预留块组描述表,为块组描述符预留的空间。
  • Block bitmap:数据块位图,标识当前块组中那些数据块被使用了,1个bit对应一个data block。
  • Inode bitmap:inode位图,与block bitmap类似,标识inode table中inode的使用情况。
  • Inode table:inode 表,inode是文件系统非常重要的概念,每个文件或目录对应一个inode来描述,inode 表中存储了inode的数据结构实体,即文件系统中有多少个文件+目录就有多少个inode的实体。
  • Data block:数据块,实际文件的内容,块组中出勤原数据剩余的就是数据块了,这些数据块也分为等分的大小,一般数据块的大小为1KB或2KB或4KB,由系统进行设置。

  可以使用dumpe2fs来查看磁盘块信息,可以看到Group0有super block,gdt,reserved GDT。其他组没有superblock,但是可能会有backup superrblock,主要用于备份使用。大部分group只有block bitmap、inode bitmap、inode table。

文件数据管理

间接块的文件数据管理(ext2)

  文件占用了磁盘中的那些空间是通过inode来进行标识,一个文件对应一个inode,在struct ext2_inode结构体有一个成员i_block[EXT2_N_BLOCKS],该成员的作用就是标识了当前文件占用的是磁盘中那些数据块。

struct ext2_inode {
    __le16  i_mode;     /* File mode */
    __le16  i_uid;      /* Low 16 bits of Owner Uid */
    __le32  i_size;     /* Size in bytes */
    __le32  i_atime;    /* Access time */
    __le32  i_ctime;    /* Creation time */
    __le32  i_mtime;    /* Modification time */
__le32  i_dtime;    /* Deletion Time */
......
    __le32  i_block[EXT2_N_BLOCKS];/* Pointers to blocks */  EXT2_N_BLOCKS=15
    __le32  i_generation;   /* File version (for NFS) */
    __le32  i_file_acl; /* File ACL */
__le32  i_dir_acl;  /* Directory ACL */
......
}

#define EXT2_NDIR_BLOCKS        12
#define EXT2_IND_BLOCK          EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK         (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK         (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS           (EXT2_TIND_BLOCK + 1)

  直接索引:数组的前12个索引项一一对应磁盘的某一个数据块,假设磁盘的数据块大小是4K,那么前12个索引项可支持的文件大小为12*4KB=48KB,这种索引的方式速度是最快的,但是也不能无限制的增加数组的大小来一一对应数据块,这样耗费的内存空间将是很大的。为了解决这个问题,当文件大小超过一定大小将使用间接索引的方式。
  间接索引:间接索引有2级、3级间接索引,以2级间接索引为例i_block[12]指向磁盘不是实际的数据块,而是指向下一级的地址,下一级地址中的数据块内容指向的数据块才是实际的内容。相当于使用磁盘的空间来存储数据块的索引,类似于虚拟内存的多级页表。
  小结:在ext2的inode节点中有i_blocks[15]元素的数组,其中前12个元素存储的是文件数据的磁盘地址,第13个元素存储的地址所指向的磁盘数据存储的不是文件内容数据而是指向下一级的地址,也就是说该数组的元素和实际存储文件数据的磁盘块多出一个磁盘块用于存储磁盘的地址,这个磁盘块称为间接块。由于文件系统中块的大小是确定的,而地址长度和数组元素也是确定的,因此可以确定文件的最大大小。同时,每个地址指向的就是一个块,这种方式的特点就是磁盘的块大小是等分的,缺点就是如果用户写入远大于文件系统块的数据,还是要切分为指定大小的块,同时文件越大需要的元数据越多,寻址的级数也会变多,效率就会变差。

基于Extent的文件数据管理(ext4)

  Ext2的缺点所有的数据块大小是等分的,数据块的索引也是固定大小的,每个索引只能索引一个数据块,那么对于大文件来说,比如一个电影大小几个G,那么就需要很多的索引和很多的数据块组组合存储,这样的效率是及低的,在ext4中则进行了改进,主要的核心原理就是一个索引对应的块大小是不定的,比如一个3GB的电影文件,用一个索引就解决,只要在索引中给定磁盘块地址和长度就可以检索到,如下图各索引只要给定磁盘地址加上长度就可以索引到具体的文件所划分的磁盘块。

  当然实际在实现时没这么简单,上面只是一个简化版本模型便于理解,但是核心原理就是上面的方式。在ext4文件系统上引入了extent机制,extent的数据存储方式,使用B+树的方式,而inode中的i_blocks[15]就变成了B+树的树根。使用extents,用一个struct ext4_extent结构就可以映射多个数据块,减少原数据块的使用。
  Extent树的每个节点可以分为内部节点和叶子节点,每个节点的构成为ext4_extent_header+内容,内容根据内部节点和叶子节点有所不同,①如果节点是内部节点(ext4_extent_header.eh_depth>0),ext4_extent_header后面紧跟的是extent_header.eh_entries个struct ext4_extent_idx,也称为索引节点②如果节点是叶子节点(ext4_extent_header.eh_depth=0),ext4_extent_header后面紧跟的是extent_header.eh_entries个Extent项struct ext4_extent数据结构。
  原inode.iblocks[15]则存储的是Extent树的根节点,一共是60字节前12字节用于存储struct ext4_extent_header,后48字节用于存储4个struct ext4_extent_idx(每个12字节)。
struct ext4_inode:

struct ext4_inode {
    __le16  i_mode;     /* File mode */
    __le16  i_uid;      /* Low 16 bits of Owner Uid */
    __le32  i_size_lo;  /* Size in bytes */
    __le32  i_atime;    /* Access time */
    __le32  i_ctime;    /* Inode Change time */
    __le32  i_mtime;    /* Modification time */
    __le32  i_dtime;    /* Deletion Time */
    __le16  i_gid;      /* Low 16 bits of Group Id */
    __le16  i_links_count;  /* Links count */
    __le32  i_blocks_lo;    /* Blocks count */
    __le32  i_flags;    /* File flags */
......
    __le32  i_block[EXT4_N_BLOCKS];/* Pointers to blocks */  EXT4_N_BLOCKS=15
......
}

#define EXT4_NDIR_BLOCKS        12
#define EXT4_IND_BLOCK          EXT4_NDIR_BLOCKS
#define EXT4_DIND_BLOCK         (EXT4_IND_BLOCK + 1)
#define EXT4_TIND_BLOCK         (EXT4_DIND_BLOCK + 1)
#define EXT4_N_BLOCKS           (EXT4_TIND_BLOCK + 1)

struct ext4_inode:

ext4_extents.h
struct ext4_extent_header {
    __le16  eh_magic;   /* 支持不同的格式 */
    __le16  eh_entries; /* 跟在头部后面的项目个数 */
    __le16  eh_max;     /* 项目中的存储容量 */
    __le16  eh_depth;   /* 树的深度 */
    __le32  eh_generation;  /* generation of the tree */
};

struct ext4_extent_idx,extent树的内部节点,也称为索引节点。

ext4_extents.h
struct ext4_extent_idx {
    __le32  ei_block;   /* index covers logical blocks from 'block' */
    __le32  ei_leaf_lo; /*指向下一级的物理数据块,可以是索引节点或叶子节点 */
    __le16  ei_leaf_hi; /* 物理数据块的高16位 */
    __u16   ei_unused;
};