ext2文件系统整体布局
每个块组内部都有相关的元数据对该块组进行管理。如图所示,第一个块组中的元数据包括引导块、超级块、块组描述符、预留GDT块、数据块位图、inode位图、inode表和其它数据块。后续块组中有些是对超级块的备份,有些则没有第一个块组这么完整的元数据信息,而只有数据块位图、inode位图和inode表等元数据信息。也就是说块组其实分为两种,一种是有超级块的,比较复杂的块组(如图3下面淡棕色所示),另外一种是没有超级块的,比较简单的块组(如图3上面淡绿色所示)。
本次实验用1G大小文件模拟磁盘,对齐进行格式化ext2文件系统,然后查看其物理存储结构信息。
dd if=/dev/zero of=/tmp/bk bs=1024k count=1024
mkfs.ext2 /tmp/bk
dumpe2fs /dev/bk
hexdump -s 1024 -n 4096 /tmp/bk -C # 查看超级块信息,磁盘偏移量为1024,存储在第1个4k块中
1. 超级块super block
每个ext2文件系统都必须包含一个超级块,其中存储了该文件系统的大量基本信息,包括块的大小、每块组中包含的块数等。同时,系统会对超级块进行备份,备份被存放在其他块组的第一个块中,备份数量不确定,也就是其他块组可能没有超级块,也可能有。超级块的起始位置为其所在分区的第1024个字节,占用1KB的空间,如果初始化指定块大小1k,那么超级块在第二个块内,如果块大小为4k,超级块则存储在第一个块内,偏移量1024字节,内容占用长度1024字节。原因是:
超级块结构如下:
下图展示了超级块信息:
2. 块组描述符表BGT
磁盘分区格式化为ext2时,会将分区划分为多个块组block group,一个块组描述符用以描述一个块组的属性。块组描述符组由若干块组描述符组成,描述了文件系统中所有块组的属性,存放于超级块所在块的下一个块中。一个块组描述符的结构如下,单个描述符占用32字节,本次实验1G空间共划分8个bg:
核心属性介绍:
__le32 bg_block_bitmap; // 块位图所在的第一个块的块ID(地址信息)
__le32 bg_inode_bitmap; // inode位图所在的第一个块的块ID(地址信息)
__le32 bg_inode_table; // inode表所在的第一个块的块ID(地址信息)
__le16 bg_free_blocks_count; // 块组中未使用的块数
__le16 bg_free_inodes_count; // 块组中未使用的inode数
__le16 bg_used_dirs_count; // 块组分配的目录的inode数
下图展示了块组描述符部分GDT信息:
3. 块位图block bitmap和索引节点位图inode bitmap
块位图和inode位图的每一位分别指出块组中对应的那个块或inode是否被使用,在位图中,0位表示对应项处于FREE状态,1位表示对应项处于IN_USE状态。块位图是一个位图,一个二进制位代表一个块,记录块组中每个块的使用情况,标记哪些块已被分配,哪些是空闲的,帮助文件系统管理块的分配和释放,维护块的空闲状态。Inode位图是一个位图,记录块组中每个inode的使用情况,标记已分配和空闲的inode。 帮助文件系统管理inode的分配和释放,维护inode的空闲状态。
根据块组描述符找到对应的block位图:
inode位图:
4. inode表
inode表一列表的形式保存了文件的元数据信息,包括文件大小、扩展属性和时间等内容。由于inode结构的大小根据格式化文件系统的属性而有差异,因此该表占用的磁盘空间不定,大概若干个逻辑块的大小。关于文件名称与inode数据结构的关系是通过inode的id确定的,在文件夹中的文件存储包含文件名和inode的id信息,而通过该id可以计算出inode数据结构位于的块组位置和inode表位置。
/** Structure of an inode on the disk*/
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 */__le16 i_gid; /* Low 16 bits of Group Id */__le16 i_links_count; /* Links count */__le32 i_blocks; /* Blocks count */__le32 i_flags; /* File flags */union {struct {__le32 l_i_reserved1;} linux1;struct {__le32 h_i_translator;} hurd1;struct {__le32 m_i_reserved1;} masix1;} osd1; /* OS dependent 1 */__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */__le32 i_generation; /* File version (for NFS) */__le32 i_file_acl; /* File ACL */__le32 i_dir_acl; /* Directory ACL */__le32 i_faddr; /* Fragment address */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; /* OS dependent 2 */
};
5. 数据块
对于普通文件,数据块中存放文件的内容。对于目录文件,数据块存放目录项。
测试文件写入:
mkdir /t
mount /tmp/bk /t
找到对应磁盘位置,二进制打印13号的inode信息:
根据对应字段关系,找到inode里面存储文件对应数据块的数组,由于文件较小,分配了一个块,找到第一个4字节整数,解析得到文件块号为1598.然后二进制打印1598块内容,得到该文件存储内容与写入一致:
关于目录
在ext2文件系统中,目录是作为文件存储的。根目录总是在inode表的第二项,而其子目录则在根目录文件的内容中定义.目录不过是一种特殊的文件。每个目录也有一个inode,会对其分配数据块。数据块中存储的是用于描述目录下文件的目录项,目录下每个文件或目录对应一个目录项,记录了该文件的文件名和inode对应关系,文件类型。rec_len域是目录项的长度,把它与目录项的起始地址相加就得到下一个目录项的起始地址,因此说,rec_len可以被解释为指向下一个有效目录项的指针。为了删除一个目录项,把ext2_dir_entry_2的inode域置为0并适当增加前一个有效目录项rec_len域的值就可以了.
struct ext2_dir_entry_2 {
__le32 inode; // 文件入口的inode号,0表示该项未使用
__le16 rec_len; // 目录项长度,文件名是字符数组,不定长
__u8 name_len; // 文件名包含的字符数
__u8 file_type; // 文件类型
char name[255]; // 文件名
};
请注意:由上可知,以下情形可以改变目录内容:新增文件,删除文件,修改文件名,修改文件类型,修改目录下文件大小是不会修改目录的内容,也就是系统看到目录的修改时间不会变。修改目录内部目录里面的内容,也不会导致外层目录发生变化