ext2文件系统格式TheSecondExtendedFileSystem(ext2)文件系统是Linux系统中的标准文件系统,是通过对Minix的文件系统进行扩展而得到的,其存取文件的性能极好。在ext2文件系统中,文件由inode(包含有文件的所有信息)进行唯一标识。一个文件可能对应多个文件名,只有在所有文件名都被删除后,该文件才会被删除。此外,同一文件在磁盘中存放和被打开时所对应的inode是不同的,并由内核负责同步。ext2文件系统采用三级间接块来存储数据块指针,并以块(block,默认为1KB)为单位分配空间。其磁盘分配策略是尽可能将逻辑相邻的文件分配到磁盘上物理相邻的块中,并尽可能将碎片分配给尽量少的文件,以从全局上提高性能。ext2文件系统将同一目录下的文件(包括目录)尽可能的放在同一个块组中,但目录则分布在各个块组中以实现负载均衡。在扩展文件时,会尽量一次性扩展8个连续块给文件(以预留空间的形式实现)。2.1.总体存储布局请点评我们知道,一个磁盘可以划分成多个分区,每个分区必须先用格式化工具(例如某种mkfs命令)格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局的信息。下图是一个磁盘分区格式化成ext2文件系统后的存储布局。图1.ext2文件系统的总体存储布局文件系统中存储的最小单位是块(Block),一个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、2048或4096字节。而上图中启动块(BootBlock)的大小是确定的,就是1KB,启动块是由PC标准规定的,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划成若干个同样大小的块组(BlockGroup),每个块组都由以下部分组成。超级块(SuperBlock)描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。超级块位于每个块组的最前面,每个块组包含超级块的内容是相同的(超级块在每个块组的开头都有一份拷贝);系统运行期间,把超级块复制到系统缓冲区内,只需把块组0的超级块读入内存,其它块组的超级块做为备份。超级块的起始位置为其所在分区的第1024个字节,占用1KB的空间,其结构如下:structext2_super_block{__le32s_inodes_count;//文件系统中inode的总数__le32s_blocks_count;//文件系统中块的总数__le32s_r_blocks_count;//保留块的总数__le32s_free_blocks_count;//未使用的块的总数(包括保留块)__le32s_free_inodes_count;//未使用的inode的总数__le32s_first_data_block;//块ID,在小于1KB的文件系统中为0,大于1KB的文件系统中为1__le32s_log_block_size;//用以计算块的大小(1024算术左移该值即为块大小)__le32s_log_frag_size;//用以计算段大小(为正则1024算术左移该值,否则右移)__le32s_blocks_per_group;//每个块组中块的总数__le32s_frags_per_group;//每个块组中段的总数__le32s_inodes_per_group;//每个块组中inode的总数__le32s_mtime;//POSIX中定义的文件系统装载时间__le32s_wtime;//POSIX中定义的文件系统最近被写入的时间__le16s_mnt_count;//最近一次完整校验后被装载的次数__le16s_max_mnt_count;//在进行完整校验前还能被装载的次数__le16s_magic;//文件系统标志,ext2中为0xEF53__le16s_state;//文件系统的状态__le16s_errors;//文件系统发生错误时驱动程序应该执行的操作__le16s_minor_rev_level;//局部修订级别__le32s_lastcheck;//POSIX中定义的文件系统最近一次检查的时间__le32s_checkinterval;//POSIX中定义的文件系统最近检查的最大时间间隔__le32s_creator_os;//生成该文件系统的操作系统__le32s_rev_level;//修订级别__le16s_def_resuid;//报留块的默认用户ID__le16s_def_resgid;//保留块的默认组ID//仅用于使用动态inode大小的修订版(EXT2_DYNAMIC_REV)__le32s_first_ino;//标准文件的第一个可用inode的索引(非动态为11)__le16s_inode_size;//inode结构的大小(非动态为128)__le16s_block_group_nr;//保存此超级块的块组号__le32s_feature_compat;//兼容特性掩码__le32s_feature_incompat;//不兼容特性掩码__le32s_feature_ro_compat;//只读特性掩码__u8s_uuid[16];//卷ID,应尽可能使每个文件系统的格式唯一chars_volume_name[16];//卷名(只能为ISO-Latin-1字符集,以'\0'结束)chars_last_mounted[64];//最近被安装的目录__le32s_algorithm_usage_bitmap;//文件系统采用的压缩算法//仅在EXT2_COMPAT_PREALLOC标志被设置时有效__u8s_prealloc_blocks;//预分配的块数__u8s_prealloc_dir_blocks;//给目录预分配的块数__u16s_padding1;//仅在EXT3_FEATURE_COMPAT_HAS_JOURNAL标志被设置时有效,用以支持日志__u8s_journal_uuid[16];//日志超级块的卷ID__u32s_journal_inum;//日志文件的inode数目__u32s_journal_dev;//日志文件的设备数__u32s_last_orphan;//要删除的inode列表的起始位置__u32s_hash_seed[4];//HTREE散列种子__u8s_def_hash_version;//默认使用的散列函数__u8s_reserved_char_pad;__u16s_reserved_word_pad;__le32s_default_mount_opts;__le32s_first_meta_bg;//块组的第一个元块__u32s_reserved[190];};块组描述符表(GDT,GroupDescriptorTable)由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(GroupDescriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。存放于超级块所在块的下一个块中。一个块组描述符的结构如下:structext2_group_desc{__le32bg_block_bitmap;//块位图所在的第一个块的块ID__le32bg_inode_bitmap;//inode位图所在的第一个块的块ID__le32bg_inode_table;//inode表所在的第一个块的块ID__le16bg_free_blocks_count;//块组中未使用的块数__le16bg_free_inodes_count;//块组中未使用的inode数__le16bg_used_dirs_count;//块组分配的目录的inode数__le16bg_pad;__le32bg_reserved[3];};块位图(BlockBitmap)一个块组中的块是这样利用的:数据块存储所有文件的数据,比如某个分区的块大小是1024字节,某个文件是2049字节,那么就需要三个数据块来存,即使第三个块只存了一个字节也需要占用一个整块;超级块、块组描述符表、块位图、inode位图、inode表这几部分存储该块组的描述信息。那么如何知道哪些块已经用来存储文件数据或其它描述信息,哪些块仍然空闲可用呢?块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。为什么用df命令统计整个磁盘的已用空间非常快呢?因为只需要查看每个块组的块位图即可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因为不可避免地要搜遍整个目录的所有文件。与此相联系的另一个问题是:在格式化一个分区时究竟会划出多少个块组呢?主要的限制在于块位图本身必须只占一个块。用mke2fs格式化时默认块大小是1024字节,可以用-b参数指定块大小,现在设块大小指定为b字节,那么一个块可以有8b个bit,这样大小的一个块位图就可以表示8b个块的占用情况,因此一个块组最多可以有8b个块,如果整个分区有s个块,那么就可以有s/(8b)个块组。格式化时可以用-g参数指定一个块组有多少个块,但是通常不需要手动指定,mke2fs工具会计算出最优的数值。inode位图(inodeBitmap)和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。inode表(inodeTable)我们知道,一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls-l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。inode表占多少个块在格式化时就要决定并写入块组描述符中,mke2fs格式化工具的默认策略是一个块组有多少个8KB就分配多少个inode(每个inode占用多少字节?128字节)。由于数据块占了整个块组的绝大部分,也可以近似认为数据块有多少个8KB就分配多少个inode,换句话说,如果平均每个文件的大小是8KB,当分区存满的时候inode表会得到比较充分的利用,数据块也不浪费。如果这个分区存的都是很大的文件(比如电影),则数据块用完的时候inode会有一些浪费,如果这个分区存的都是很小的文件(比如源代码),则有可能数据块还没用完inode就已经用完了,数据块可能有很大的浪费。如果用户在格式化时能够对这个分区以后要存储的文件大小做一个预测,也可以用mke2fs的-i参数手动指定每多少个字节分配一个inode。inode表用于跟踪定位每个文件,包括位置、大小等(但不包括文件名,文件名包含于dentry结构中),一个块组只有一个inode表。一个inode的结构如下:structext2_inode{__le16i_mode;//文件格式和访问权限__le16i_uid;//文件所有者ID的低16位__le32i_size;//文件字节数__le32i_atime;//文件上次被访问的时间__le32i_ctime;//文件创建时间__le32i_mtime;//文件被修改的时间__le32i_dtime;//文件被删除的时间(如果存在则为0)__