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字段存放实际的文件名长度(参见下图)。
![](https://img.haomeiwen.com/i10186629/c61149f21f7ed935.gif)
rec_len字段可以被解释为指定一个有效目录项的指针:它是偏移量,与目录项的起始地址相加就得到下一个有效目录的起始地址。
为了删除一个目录项,把它的inode字段置为0并适当地增加前一个有效目录项rec_len字段的值就足够了
仔细看一下上边图中的rec_len字段,你会发现oldfile项已被删除,因为usr的rec_len字段被置为 12+16(usr和oldfile目录项的长度)。
- 符号链接
如前所述,如果符号链接的路径名小于等于60个字符,就把它存放在索引节点的i_blocks字段,该字段是由15个4字节整数组成的数组,因此无需数据块。但是,如果路径名大于60个字符,就需要一个单独的数据块。
- 设备文件、管道和套接字
这些类型的文件不需要数据块。所有必要的信息都存放在索引节点中。
网友评论