ext2 文件系统结构分析:
首先来看一下ext2文件系统的结构示意图:
Block:
对于ext2文件系统来说,硬盘分区首先被分割为一个一个的“Block”,每个Block就是实际用来存储数据的单元,大小相同,Block按照0,1,2,3的顺序进行编号,第一个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的备份。(可以参考前面的示意图)
ext2中Super 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中记录了Inode和Block的数量,预留的Block数量,空闲Block和Inode的数量,Block的大小,Inode大小,每个Block Group中有多少Block和Inode,文件系统挂在时间,最近一次写入数据时间,最近一次磁盘检查(fsck)时间,挂载标记为等等。
除了这些比较有意义的信息外,还在s_magic属性中记录的Magic签名,对于ext2和ext3文件系统来说,s_magic的值等于 0xEF53,如果不等于这个值的话,则这个磁盘分区一定不是一个有效的ext2或ext3文件系统。
s_log_block_size中的记录需要通过计算转换为实际的Block大小,公式为:BlockSize=1<<(s_log_block_size+10)Byte,即当s_log_block_size为0时,通过将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 Block和SuperBlock仅占用了Block 0的前2048字节,所以第一个Group Descriptor与Super 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 Bitmap,Inode Bitmap,Inode Table的起始位置。此外,还包含了这个Block Group的空闲Block和Inode的数量等信息。
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*8个Block。
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*8个Inode。
Inode Table:
Inode Table从与InodeBitmap相邻的Block开始存放,但是Inode Table并不一定只占据1个Block,Inode Table中实际就存放着一个一个Inode,这些Inode与这个Block Group相对应,映射到这个Block Group中的Data Block进行文件的存储和读取。Inode Table占用的空间与单个Inode大小以及Inode密度有关。
Inode :
Inode,即Index Node,顾名思义,用于文件索引,和Block一样,Inode也有自己的编号,但是Inode编号是从1开始,依次增长(1,2,3,4等等)。看一下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_uid和i_gid记录了用户ID和用户组ID,i_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,通过bs和seek参数指定跳过6*1024字节开始拷贝文件,然后向文件中输出一个"X"字符。这样,这个文件对应的Inode的i_size属性就是6*1024+1=6145字节,但是i_blocks却不是等于6145/512,假设我们的文件系统的Block大小为4096字节,则第一个Block(4096字节)里面存放的就都是空字符(也就是存在一个文件洞),第二个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_SIZE和EXT2_MAX_BLOCK_SIZE*1024之间,即1024字节和4096*1024字节之间。
为了最大程度地利用磁盘空间,避免浪费,当然是每个文件的实际大小与Inode的密度越接近越好:最完美的情况是每个文件的大小都恰好等于Inode密度,这种情况下,假设磁盘总大小为G,每个文件大小F=inode_ratio,inode数量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数量为1047552,Block数量为268173037,空闲的Inode数量为1047541,空闲的Block数量为268071716
Block大小为4096字节
每个Block Group中的Block数量为32768,每个Block Group中Inode的数量为128,每个Block Group中Inode占用的Block数量为8
创建时间,上次挂载时间,上次写入数据的时间
最多挂载31次就会强制自检(fsck),上次自检时间,强制自检周期,下次强制自检的时间
Inode大小为256字节
在这些信息后面,紧接着会将每个Block Group的信息依次输出来:
Super Block的起始位置,GroupDescriptor的范围,Data Block Bitmap的位置,Inode Bitmap的位置,Inode Table的范围,空闲的Block数量,空闲的Inode数量,目录数量,空闲Block的范围,空闲Inode的范围。