Linux ext2, ext3, ext4 文件系统解读[1]

时间:2022-01-01 19:36:24


ext2 文件系统结构分析:


首先来看一下ext2文件系统的结构示意图:

Linux ext2, ext3, ext4 文件系统解读[1]

Block


对于ext2文件系统来说,硬盘分区首先被分割为一个一个的“Block”,每个Block就是实际用来存储数据的单元,大小相同,Block按照0123的顺序进行编号,第一个Block的编号为0。对于ext2文件系统来说,支持的Block的大小有1024字节/2048字节/4096字节,Block的大小在创建文件系统的时候可以通过参数指定,如果不指定,则会从/etc/mke2fs.conf文件中读取对应的值。原则上,Block的大小与数量在格式化后就不能够发生改变了,每个Block内只会存放至多一个文件的数据(即不会出现两个文件的数据被放入同一个Block的情况),如果文件超过一个Block,则会占用多个Block来存放文件,如果小于一个Block大小,则这个Block剩余的空间就浪费掉了。


Block Group


多个Block聚合在一起形成一个Block Group,每个BlockGroup包含的Block数量相同,具体是在SuperBlock中通过s_block_per_group属性定义的(最后一个Block Group除外,最后剩下的Block数量可能小于s_block_per_group,这些Block会被划分到最后一个BlockGroup中)。


Boot Block


每个磁盘分区的开头1024字节大小都预留为分区的启动扇区,存放引导程序和数据,所以又叫引导块。引导块在第一个Block,即Block 0中存放,但是未必占满这个Block,原因是Block的大小可能大于1024字节。


Super Block


Super Block是紧邻BootBlock的一个大小为1024字节的配置块,Super Block可能在Block 0或者Block 1中,当Block大小为1024字节时,由于Boot Block恰好占满了Block0,则Super Block会占用Block 1;如果Block大小为4096字节,则Super Block会在Block 0中紧邻Boot Block的位置(即1024~2047字节)。


ext2文件系统中,每个Block Group中开头都会存放一个SuperBlock的备份。(可以参考前面的示意图)


ext2Super Block的结构:


struct ext2_super_block {

__le32        s_inodes_count;        /* Inodes count */

__le32        s_blocks_count;        /* Blocks count */

__le32        s_r_blocks_count;        /* Reserved blocks count */

__le32        s_free_blocks_count;        /* Free blocks count */

__le32        s_free_inodes_count;        /* Free inodes count */

__le32        s_first_data_block;        /* First Data Block */

__le32        s_log_block_size;        /* Block size */

__le32        s_log_frag_size;        /* Fragment size */

__le32        s_blocks_per_group;        /* # Blocks per group */

__le32        s_frags_per_group;        /* # Fragments per group */

__le32        s_inodes_per_group;        /* # Inodes per group */

__le32        s_mtime;                /* Mount time */

__le32        s_wtime;                /* Write time */

__le16        s_mnt_count;                /* Mount count */

__le16        s_max_mnt_count;        /* Maximal mount count */

__le16        s_magic;                /* Magic signature */

__le16        s_state;                /* File system state */

__le16        s_errors;                /* Behaviour when detecting errors */

__le16        s_minor_rev_level;         /* minor revision level */

__le32        s_lastcheck;                /* time of last check */

__le32        s_checkinterval;        /* max. time between checks */

__le32        s_creator_os;                /* OS */

__le32        s_rev_level;                /* Revision level */

__le16        s_def_resuid;                /* Default uid for reserved blocks */

__le16        s_def_resgid;                /* Default gid for reserved blocks */

/*

* 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;                 /* First non-reserved inode */

__le16   s_inode_size;         /* size of inode structure */

__le16        s_block_group_nr;         /* block group # of this superblock */

__le32        s_feature_compat;         /* compatible feature set */

__le32        s_feature_incompat;         /* incompatible feature set */

__le32        s_feature_ro_compat; /* readonly-compatible feature set */

__u8        s_uuid[16];                /* 128-bit uuid for volume */

char        s_volume_name[16];         /* volume name */

char        s_last_mounted[64];         /* directory where last mounted */

__le32        s_algorithm_usage_bitmap; /* For compression */

/*

* Performance hints.  Directory preallocation should only

* happen if the EXT2_COMPAT_PREALLOC flag is on.

*/

__u8        s_prealloc_blocks;        /* Nr of blocks to try to preallocate*/

__u8        s_prealloc_dir_blocks;        /* Nr to preallocate for dirs */

__u16        s_padding1;

/*

* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.

*/

__u8        s_journal_uuid[16];        /* uuid of journal superblock */

__u32        s_journal_inum;                /* inode number of journal file */

__u32        s_journal_dev;                /* device number of journal file */

__u32        s_last_orphan;                /* start of list of inodes to delete */

__u32        s_hash_seed[4];                /* HTREE hash seed */

__u8        s_def_hash_version;        /* Default hash version to use */

__u8        s_reserved_char_pad;

__u16        s_reserved_word_pad;

__le32        s_default_mount_opts;

         __le32        s_first_meta_bg;         /* First metablock block group */

__u32        s_reserved[190];        /* Padding to the end of the block */

};


从上面的结构中可以看到,Super Block中记录了InodeBlock的数量,预留的Block数量,空闲BlockInode的数量,Block的大小,Inode大小,每个Block Group中有多少BlockInode,文件系统挂在时间,最近一次写入数据时间,最近一次磁盘检查(fsck)时间,挂载标记为等等。


除了这些比较有意义的信息外,还在s_magic属性中记录的Magic签名,对于ext2ext3文件系统来说,s_magic的值等于 0xEF53,如果不等于这个值的话,则这个磁盘分区一定不是一个有效的ext2ext3文件系统。


s_log_block_size中的记录需要通过计算转换为实际的Block大小,公式为:BlockSize=1<<(s_log_block_size+10)Byte,即当s_log_block_size0时,通过将1左移(0+10)位,得到Block的大小为1024字节,当这个值为2时,通过将1左移(2+10)位,得到Block的大小为4096字节。


Group Descriptor


每个Block Group都对应一个Group Descriptor,所有这些Group Descriptor都被一起存放在紧邻Super Block的若干个Block中,如果Block大小为1024字节,则这些GroupDescriptor恰好紧邻着Super Block,如果Block大小为4096字节,则由于Boot BlockSuperBlock仅占用了Block 0的前2048字节,所以第一个Group DescriptorSuper Block之间会有一定的空间被浪费掉。


在每个Block Group中紧邻Super Block(或其备份)的若干个Block中都保存了这些Group Descriptor的备份。(可以参考前面的示意图)


ext2文件系统的GroupDescriptor的结构:


struct ext2_group_desc

{

   __u32     bg_block_bitmap;        /* Blocks bitmap block */

   __u32     bg_inode_bitmap;        /* Inodes bitmap block */

   __u32     bg_inode_table;         /* Inodes table block */

   __u16     bg_free_blocks_count;   /* Free blocks count */

   __u16     bg_free_inodes_count;   /* Free inodes count */

   __u16     bg_used_dirs_count;     /* Directories count */

   __u16     bg_pad;

   __u32     bg_reserved[3];

};


从上面的数据结构中可以看到,每个Group Descriptor中定义了Data Block Bitmap指针,Inode Bitmap指针,InodeTable指针,分别指向这个Group Descriptor对应的Block Group中对应的Data Block BitmapInode BitmapInode Table的起始位置。此外,还包含了这个Block Group的空闲BlockInode的数量等信息。


Data Block Bitmap


Data Block Bitmap位于与Group Descriptor相邻的Block中,其中包含的每个比特的数据都对应这个Bitmap所在的Block Group中的DataBlock,如果某个比特的值为0,则表示其对应的Data Block中有数据,如果值为1,则表示对应的Data Block是空闲的。假设一个Block的大小为1024字节,则一个Data Block Bitmap的大小为1024*8比特,所以这种情况下一个Block Group最多只能有1024*8Block


Inode Bitmap:


Inode Bitmap位于DataBlock Bitmap相邻的Block中,与Data BlockBitmap类似,Inode Bitmap中的每个比特的数据都对应这个Bitmap所在的Block Group中的Inode,如果某个比特的值为0,则表示其对应的Inode中有数据,如果值为1,则表示对应的Inode是空闲的。假设一个Block大小为1024字节,则一个Inode Bitmap的大小为1024*8比特,所以这种情况下一个BlockGroup中最多只能包含1024*8Inode


Inode Table:


Inode Table从与InodeBitmap相邻的Block开始存放,但是Inode Table并不一定只占据1BlockInode Table中实际就存放着一个一个Inode,这些Inode与这个Block Group相对应,映射到这个Block Group中的Data Block进行文件的存储和读取。Inode Table占用的空间与单个Inode大小以及Inode密度有关。


Inode :


Inode,即Index Node,顾名思义,用于文件索引,和Block一样,Inode也有自己的编号,但是Inode编号是从1开始,依次增长(1234等等)。看一下ext2文件系统Inode的结构:


struct ext2_inode {

   __u16 i_mode;            /* File mode */

   __u16 i_uid;             /* Low 16 bits of Owner Uid */

   __u32 i_size;            /* Size in bytes */

   __u32 i_atime;           /* Access time */

   __u32 i_ctime;           /* Creation time */

   __u32 i_mtime;           /* Modification time */

   __u32 i_dtime;           /* Deletion Time */

   __u16 i_gid;             /* Low 16 bits of Group Id */

   __u16 i_links_count;     /* Links count */

   __u32 i_blocks;          /* Blocks count */

   __u32 i_flags;           /* File flags */

   union {

      struct {

         __u32 l_i_reserved1;

      } linux1;

      struct {

         __u32 h_i_translator;

      } hurd1;

      struct {

         __u32 m_i_reserved1;

      } masix1;

   } osd1;                          /* OS dependent 1 */

   __u32 i_block[EXT2_N_BLOCKS];    /* Pointers to blocks, EXT2_N_BLOCKS = 15*/

   __u32 i_generation;              /* File version (for NFS) */

   __u32 i_file_acl;                /* File ACL */

   __u32 i_dir_acl;                 /* Directory ACL */

   __u32 i_faddr;                   /* Fragment address */

   union {

      struct {

         __u8 l_i_frag;             /* Fragment number */

         __u8 l_i_fsize;            /* Fragment size */

         __u16 i_pad1;

         __u16 l_i_uid_high;        /* these 2 fields   */

         __u16 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 */

         __u16 h_i_mode_high;

         __u16 h_i_uid_high;

         __u16 h_i_gid_high;

         __u32 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;                          /* OS dependent 2 */

};


从结构中可以看到,对于ext2文件系统而言,Inode的大小为128字节,i_mode记录了文件模式(文件/目录/符号链接/设备文件等),i_uidi_gid记录了用户ID和用户组IDi_size记录了文件大小,i_atime记录了文件最近访问时间,i_ctime记录了创建时间,i_mtime记录了修改时间,i_dtime代表文件删除时间,i_links_count记录了文件的连接数,i_flags记录了文件特性标志。


注意这里有一个字段i_blocks表示的并非是这个文件占用的数据块的数量,而是记录实际上分配给文件的有效物理块(以512字节为单位)数。这里需要简单引入一个概念,文件洞(file hole),它表示一些并未存放在磁盘中的“空字符”,引入文件洞是为了更为有效地利用磁盘空间,避免浪费,文件洞在ext2文件系统中是动态分配的,仅当一个进程需要向一个数据块写数据时,才会真正把这个块分配给文件。例如我们创建一个文件:echo -n "X" | dd of=/tmp/holebs=1024 seek 6,通过bsseek参数指定跳过6*1024字节开始拷贝文件,然后向文件中输出一个"X"字符。这样,这个文件对应的Inodei_size属性就是6*1024+1=6145字节,但是i_blocks却不是等于6145/512,假设我们的文件系统的Block大小为4096字节,则第一个Block4096字节)里面存放的就都是空字符(也就是存在一个文件洞),第二个Block中存放了“X”这个字符。真正存放数据的Block的数量是1,所以i_blocks=1*4096/512=8


i_block是一个数组,数组长度为EXT2_N_BLOCKS(15),这15个元组都为Block指针,其中前12个指针指向用于存放实际数据的Block,这些Block称为直接块(Direct Block),第13个指针指向的块为间接块(IndirectBlock),间接块中存放了指向直接块的指针。第14个指针指向二级间接块(DoubleIndirect Block),二级间接块中存放的指针指向间接块。第15个指针指向三级间接块(TripleIndirect Block),三级间接块中存放指向二级间接块的指针。


Inode大小需要满足三个条件:必须大于等于128字节,必须小于Block大小,必须是128字节的整数倍。


从上面的结构也可以看出,一个Inode对应一个文件,因此Inode的数量决定了整个文件系统最多可以存放的文件数量。Inode的数量则由Inode的划分密度(inode_ratio)决定,inode_ratio可以在/etc/mke2fs.conf中定义,也可以在创建文件系统的时候通过参数指定,表示每隔inode_ratio个字节创建一个Inode(虽然实际上Inode都是聚在一起的,这个说法仅仅是为了更形象地表示“密度”这个概念)。当然,inode_ratio的取值也并不是没有限制,对于ext2文件系统而言,inode_ratio的取值范围在EXT2_MIN_BLOCK_SIZEEXT2_MAX_BLOCK_SIZE*1024之间,即1024字节和4096*1024字节之间。


为了最大程度地利用磁盘空间,避免浪费,当然是每个文件的实际大小与Inode的密度越接近越好:最完美的情况是每个文件的大小都恰好等于Inode密度,这种情况下,假设磁盘总大小为G,每个文件大小F=inode_ratioinode数量N=G/inode_ratio,当Inode使用完的时候,磁盘存放的数据总量为N*F=(G/inode_ratio)*inode_ratio=G,即磁盘利用率为100%。当然这只是理想情况,实际情况中,由于文件系统在不重新格式化的情况下不能随意调整inode_ratio,所以在格式化之前一定要做好规划,如果磁盘中存放的都是小文件,则inode_ratio划分的小一些可以更好地利用磁盘避免浪费,如果磁盘中的文件相对都比较大,则inode_ratio划分的大一些会更好,原因是Inode本身也会占用一部分磁盘空间,更少的Inode可以省出空间划分更多的DataBlock


 


可以通过dumpe2fs查看一个文件系统的详细信息:


[root@DanCentOS65 daniel]# dumpe2fs /dev/sdd1 | more

dumpe2fs 1.41.12 (17-May-2010)

Filesystem volume name:  <none>

Last mounted on:         <not available>

Filesystem UUID:         5399c0d1-dcb3-4c00-b78a-7c8eb2731cd3

Filesystem magic number: 0xEF53

Filesystem revision #:   1 (dynamic)

Filesystem features:     ext_attr resize_inode dir_index filetype sparse_super large_file

Filesystem flags:        signed_directory_hash

Default mount options:   (none)

Filesystem state:        not clean

Errors behavior:         Continue

Filesystem OS type:      Linux

Inode count:             1047552

Block count:             268173037

Reserved block count:    13408651

Free blocks:             268071716

Free inodes:             1047541

First block:             0

Block size:              4096

Fragment size:           4096

Reserved GDT blocks:     960

Blocks per group:        32768

Fragments per group:     32768

Inodes per group:        128

Inode blocks per group:  8

Filesystem created:      Thu Apr 20 14:16:46 2017

Last mount time:         Thu Apr 20 14:20:16 2017

Last write time:         Thu Apr 20 14:24:37 2017

Mount count:             1

Maximum mount count:     31

Last checked:            Thu Apr 20 14:16:46 2017

Check interval:          15552000 (6 months)

Next check after:        Tue Oct 17 14:16:46 2017

Reserved blocks uid:     0 (user root)

Reserved blocks gid:     0 (group root)

First inode:             11

Inode size:                 256

Required extra isize:    28

Desired extra isize:     28

Default directory hash:  half_md4

Directory Hash Seed:     b0242b69-cb57-4b52-b448-89d5352db22c

 

 

Group 0: (Blocks 0-32767)

 Primary superblock at 0, Group descriptors at 1-64

 Reserved GDT blocks at 65-1024

 Block bitmap at 1025 (+1025), Inode bitmap at 1026 (+1026)

 Inode table at 1027-1034 (+1027)

 31726 free blocks, 116 free inodes, 2 directories

 Free blocks: 1041-1048, 1050-32767

 Free inodes: 12-13, 15-128

Group 1: (Blocks 32768-65535)

 Backup superblock at 32768, Group descriptors at 32769-32832

 Reserved GDT blocks at 32833-33792

 Block bitmap at 33793 (+1025), Inode bitmap at 33794 (+1026)

 Inode table at 33795-33802 (+1027)

 31733 free blocks, 128 free inodes, 0 directories

 Free blocks: 33803-65535

 Free inodes: 129-256

Group 2: (Blocks 65536-98303)

……


从输出结果中可以看到下面这些信息:


文件系统的UUID=5399c0d1-dcb3-4c00-b78a-7c8eb2731cd3


magic number=0xEF53


文件系统的一些特性ext_attr resize_inode dir_index filetype sparse_superlarge_file


Inode数量为1047552Block数量为268173037,空闲的Inode数量为1047541,空闲的Block数量为268071716


Block大小为4096字节


每个Block Group中的Block数量为32768,每个Block GroupInode的数量为128,每个Block GroupInode占用的Block数量为8


创建时间,上次挂载时间,上次写入数据的时间


最多挂载31次就会强制自检(fsck),上次自检时间,强制自检周期,下次强制自检的时间


Inode大小为256字节


在这些信息后面,紧接着会将每个Block Group的信息依次输出来:


Super Block的起始位置,GroupDescriptor的范围,Data Block Bitmap的位置,Inode Bitmap的位置,Inode Table的范围,空闲的Block数量,空闲的Inode数量,目录数量,空闲Block的范围,空闲Inode的范围。