Linux Ext2 数据结构及文件存储解析

Ext2

任何Ext2分区中的第一个块从不受Ext2文件系统的管理,因为这一块是为分区的引导扇区保留的。Ext2分区的其作部分分成块组,每个块组分布如下所示。在Ext2文件系统中每个块大小相同,并按顺序存放。因此,内核可以从数组的整数索引很容易找到磁盘中的一个块组的位置。

Boot Block Block Group 1 Block Group2 ... Block Group n

Block Group

name Super Block 块组描述符 数据块位图 索引节点位图 索引节点表 数据块钱
count 1 n 1 1 n n

每个块组超级块相同,块组描述符不同,
因为除第一个块组的超级块和块组描述符由内核使用外,其余的均保持不变,只存放第一个块组超级块和(一组,换句话说不只是当前组的描述符还有其余组的描述符)块组描述符的拷贝。
上一节提到每个块组的大小相同,所以每个块组的超级块和块级描述符也必然相同。
所以其余块组的超级块只是第0个块组的copy,用于数据备份。用于e2fsck对数据进行还原。

由于块的位图必须存放在一个块中,块组大小计算公式为:

块组的大小=s/8*B
s为分区的总块数,B为每个分区的字节数

由公式可知分母B越大,块组大小越小

举例

32G分区,块大小为4k

4k*8=32K个数据块

32K*4K=128M

32G=32768M/128M=256

Super Block 字段

struct ext2_super_block {
     __le32     s_inodes_count;            /* 索引节点的总数 */
     __le32     s_blocks_count;             /* 块总数(所有的块) */
     __le32     s_r_blocks_count;          /* 保留的块数 */
     __le32     s_free_blocks_count;     /* 空闲块数 */
     __le32     s_free_inodes_count;     /* 空闲索引节点数 */
     __le32     s_first_data_block;         /* 第一个使用的块号(总为1,第0个为引导块) */
     
/* 块的大小 以2的幂次方表示,单位为k,所以0表示1024 1表示2048*/
     __le32     s_log_block_size;           

/*对于逻辑块较大的ext2文件系统,为了
      减少块内碎片问题,设置了fragment,
      即每个磁盘块内可再细分成多个fragment
      这个思想源自FFS,对于1024大小的磁盘块
      也就没有必要再划分fragment了
     因为最小的fragment大小就是1024字节
     目前与s_log_block_size 一致,因为还未实现
     __le32     s_log_frag_size;             /* 片的大小 */
     __le32     s_blocks_per_group;      /* 每组中的块数 */
     __le32     s_frags_per_group;        /* 每组中的片数 */
     __le32     s_inodes_per_group;     /* 每组中的索引节点数 */
     __le32     s_mtime;                        /* 最后一次安装操作的时间 */
     __le32     s_wtime;                        /* 最后一次写操作的时间 */
     __le16     s_mnt_count;                 /* 被执行安装操作的次数 */
     __le16     s_max_mnt_count;        /* 检查之前安装操作的次数 */
     __le16     s_magic;                   /* 魔术签名 */
     __le16     s_state;                    /* 文件系统状态标志 */
     __le16     s_errors;                   /* 当检测到错误时的行为 */
     __le16     s_minor_rev_level;        /* 次版本号 */
     __le32     s_lastcheck;                  /* 最后一次检查的时间 */
     __le32     s_checkinterval;            /* 两次检查之间的时间间隔 */
     __le32     s_creator_os;                /* 创建文件系统的操作系统 */
     __le32     s_rev_level;             /* 主版本号 */
     __le16     s_def_resuid;          /* 保留块的缺省UID */
     __le16     s_def_resgid;          /* 保留块的缺省用户组ID */
     /*
      * These fields are for EXT2_DYNAMIC_REV superblocks only.
      *
      * Note: the difference between the compatible feature set and
      * the incompatible feature set is that if there is a bit set
      * in the incompatible feature set that the kernel doesn't
      * know about, it should refuse to mount the filesystem.
      * 
      * e2fsck's requirements are more strict; if it doesn't know
      * about a feature in either the compatible or incompatible
      * feature set, it must abort and not try to meddle with
      * things it doesn't understand...
      */
     __le32     s_first_ino;           /* 第一个非保留的索引节点号 */
     __le16   s_inode_size;           /* 磁盘上索引节点结构的大小 */
     __le16     s_block_group_nr;      /* 这个超级块的块组号 */
     __le32     s_feature_compat;      /* 具有兼容特点的位图 */
     __le32     s_feature_incompat;      /* 具有非兼容特点的位图 */
     __le32     s_feature_ro_compat;      /* 只读兼容特点的位图 */
     __u8     s_uuid[16];          /* 128位文件系统标识符 */
     char     s_volume_name[16];      /* 卷名 */
     char     s_last_mounted[64];      /* 最后一个安装点的路径名 */
     __le32     s_algorithm_usage_bitmap; /* 用于压缩 */
     /*
      * Performance hints.  Directory preallocation should only
      * happen if the EXT2_COMPAT_PREALLOC flag is on.
      */
     __u8     s_prealloc_blocks;     /* 预分配的块数 */
     __u8     s_prealloc_dir_blocks;     /* 为目录预分配的块数 */
     __u16     s_padding1;             /* 按字对齐 */
     ……
     __u32     s_reserved[190];     /* 用null填充1024字节 */
};

组描述符和位图

由一组(注意是一组不是一个)块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。

struct ext2_group_desc
{
    __u32   bg_block_bitmap;     /* 组中块位图所在的块号 */
    __u32   bg_inode_bitmap;        /* 组中索引节点位图所在块的块号 */
    __u32   bg_inode_table;     /*组中索引节点表的首块号 */
    __u16   bg_free_blocks_count;  /* 组中空闲块数 */
    __u16   bg_free_inodes_count;  /* 组中空闲索引节点数 */
    __u16   bg_used_dirs_count; /*组中分配给目录的节点数 */
    __u16   bg_pad;                   /*填充,对齐到字*/
    __u32[3] bg_reserved;            /*用null填充12个字节*/
}

位图是位的序列,其中0表示对应的索引节点块或数据块是空闲的,1表示占用。因为位图必须存储在一个块中,又因为块的大小可以是1k 2k 4k.因此一个单独的位图描述8192 16384 32768 个块的状态。

索引节点表

  • 对应一个物理文件的描述

一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。

组中索引节点总数(在超级块的s_inodes_per_group)/每块的索引节数

所有索引节点的大小相同,即128字节,一个1K的块可以包含8个索引节点,4K的块可以包含32个索引节点。
索引节点表占用的块数
struct ext2_inode {
     __le16     i_mode;          /* 文件类型和访问权限 */
     __le16     i_uid;          /* 拥有者标识符 */
     __le32     i_size;          /* 以字节为单位的文件长度 */
     __le32     i_atime;     /* 最后一次访问文件的时间 */
     __le32     i_ctime;     /* 索引节点最后改变的时间 */
     __le32     i_mtime;     /* 文件内容最后改变的时间 */
     __le32     i_dtime;     /* 文件删除的时间 */
     __le16     i_gid;          /* 用户组标识符低16位 */
     __le16     i_links_count;     /* 硬链接计数器 */
     __le32     i_blocks;     /* 文件的数据块数 */
     __le32     i_flags;     /* 文件标志 */
     union {
          struct {
               __le32  l_i_reserved1;
          } linux1;
          struct {
               __le32  h_i_translator;
          } hurd1;
          struct {
               __le32  m_i_reserved1;
          } masix1;
     } osd1;                    /* 特定的操作系统信息 1 */
     __le32     i_block[EXT2_N_BLOCKS]; /* 指向数据块的指针 (表示文件的块不一定连续) */
     __le32     i_generation;     /* 文件版本(当网络文件系统访问文件时) */
     __le32     i_file_acl;     /* 文件访问控制列表 */
     __le32     i_dir_acl;     /* 目录访问控制列表 */
     __le32     i_faddr;     /* 片的地址 */
     union {
          struct {
               __u8     l_i_frag;     /* Fragment number */
               __u8     l_i_fsize;     /* Fragment size */
               __u16     i_pad1;
               __le16     l_i_uid_high;     /* these 2 fields    */
               __le16     l_i_gid_high;     /* were reserved2[0] */
               __u32     l_i_reserved2;
          } linux2;
          struct {
               __u8     h_i_frag;     /* Fragment number */
               __u8     h_i_fsize;     /* Fragment size */
               __le16     h_i_mode_high;
               __le16     h_i_uid_high;
               __le16     h_i_gid_high;
               __le32     h_i_author;
          } hurd2;
          struct {
               __u8     m_i_frag;     /* Fragment number */
               __u8     m_i_fsize;     /* Fragment size */
               __u16     m_pad1;
               __u32     m_i_reserved2[2];
          } masix2;
     } osd2;                    /* 特定的操作系统信息 2 */
};

数据块

根据不同的文件类型有以下几种情况

  • 常规文件,文件的数据存储在数据块中。
  • 目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。
  • 符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
  • 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。

文件存储

Ext2所认可的文件类型(普通文件、管道文件等)以不同的方式使用数据块。有些文件不存放数据,因此根本就不需要数据块。

file_type字段存放指定文件类型的值(见下表):

文件类型 描述
0 未知
1 普通文件
2 目录
3 字符设备
4 块设备
5 命名管道
6 套接字
7 符号链接
  • 普通文件

普通文件是最常见的情况,我们要重点关注它。但普通文件只有在开始有数据时才需要数据块。普通文件在刚创建时是空的,并不需要数据块;也可以用truncate()或open()系统调用清空它。这两种情况是相同的,例如,当你发出一个包含字符串 > filename的shell命令时,shell创建一个空文件或截断一个现有文件。

  • 目录

Ext2以一种特殊的文件实现了目录,这种文件的数据块把文件名和相应的索引节点号存放在一起。特别说明的是,这样的数据块包含了类型为ext2_dir_entry_2的结构:

#define EXT2_NAME_LEN 255
struct ext2_dir_entry_2 {
     __le32     inode;               /* 索引节点号 */
     __le16     rec_len;          /* 目录项长度 */
     __u8     name_len;          /* 文件名长度 */
     __u8     file_type;              /* 文件类型 */
     char     name[EXT2_NAME_LEN];     /* 文件名 */
};

因为该结构最后一个name字段是最大为EXT2_NAME_LEN(通常是255)个字符的变长数组,因此这个结构的长度是可变的。此外,因为效率的原因,目录项的长度总是4的倍数,并在必要时用null字符(/0)填充文件名的末用的name_len字段存放实际的文件名长度(参见下图)。


Linux Ext2 数据结构及文件存储解析_第1张图片
dir-file.gif

rec_len字段可以被解释为指定一个有效目录项的指针:它是偏移量,与目录项的起始地址相加就得到下一个有效目录的起始地址。
为了删除一个目录项,把它的inode字段置为0并适当地增加前一个有效目录项rec_len字段的值就足够了

仔细看一下上边图中的rec_len字段,你会发现oldfile项已被删除,因为usr的rec_len字段被置为 12+16(usr和oldfile目录项的长度)。

  • 符号链接

如前所述,如果符号链接的路径名小于等于60个字符,就把它存放在索引节点的i_blocks字段,该字段是由15个4字节整数组成的数组,因此无需数据块。但是,如果路径名大于60个字符,就需要一个单独的数据块。

  • 设备文件、管道和套接字

这些类型的文件不需要数据块。所有必要的信息都存放在索引节点中。

你可能感兴趣的:(Linux Ext2 数据结构及文件存储解析)