目录
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
9.2 Ext2文件系统
9.2.2 数据结构
1. 超级块
ext2_fill_super() 函数:
读取超级块信息。
struct ext2_super_block {
__le32 s_inodes_count; // inode总数。
__le32 s_blocks_count; //块的总数。
__le32 s_r_blocks_count; //为 s_def_resuid 用户预留的块数量。
__le32 s_free_blocks_count; //空闲块数。
__le32 s_free_inodes_count; // 空闲inodes数。
__le32 s_log_block_size;
//块大小,值为0 1 2,分别表示块长度为1024,2048,4096字节 ,mkfs时指定。
__le32 s_blocks_per_group, //每个块组中数据块数
__le32 s_inodes_per_group //每个块组中inode数
__le16 s_mnt_count; //上一次检查后的装载次数。
__le16 s_max_mnt_count;
//若 s_max_mnt_count > s_mnt_count, 则会检查。
__le16 s_magic; //标识文件系统版本,Ext2为0xEF53。
__le16 s_state; //装载成功与否。
__le32 s_lastcheck;
//上次检查日期,若 now - s_lastcheck > s_checkinterval,则会检查。
__le32 s_checkinterval; //检测间隔时间。
__le16 s_def_resuid;
//默认为0, 即 root 用户。可确保root 用户紧急情况使用磁盘空间。如ssh时。
__le16 s_def_resgid; //组ID,同上。
__le32 s_feature_compat; //兼容特性集,增加特性使用,如ext3日志功能。
__le32 s_feature_incompat; //不兼容特性集。
__le32 s_feature_ro_compat; //只读兼容特性集。
__u32 s_reserved[190]; //填充到1024字节块大小。
};
struct ext2_sb_info = struct super_block *sb -> s_fs_info;
struct ext2_sb_info {
struct ext2_super_block *s_es;
...
}
struct ext2_sb_info 和 struct ext2_super_block 区别:
1. struct ext2_super_block:
从磁盘读取的文件系统静态原始信息。
2. struct ext2_sb_info:
基于超级块信息,维护的文件系统运行时的状态和辅助数据。
上述成员类型为__le32,__le16等,作用:
将一个移动硬盘更换到不同位长的计算机时,超级块信息以相同字节和大小端存储。
s_feature_compat:兼容特性,掩码。
举例:
EXT2_FEATURE_COMPAT_IMAGIC_INODES
EXT3_FEATURE_COMPAT_HAS_JOURNAL
s_feature_ro_compat:只读兼容特性,掩码。
举例:
EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER,稀疏超级块
EXT2_FEATURE_RO_COMPAT_LARGE_FILE
s_feature_incompat:不兼容特性,掩码。
举例:
EXT3_FEATURE_INCOMPAT_JOURNAL_DEV
上述三个掩码作用:
了解文件系统特性,进行相应处理。
使用举例:
if (osb-> s_feature_incompat & OCFS2_FEATURE_INCOMPAT_INLINE_DATA )
{
}
2. GDT组描述符
每个块组都有一个组描述符,反应当前块组的信息。
如何得到指定块组的组描述符?
struct ext2_group_desc * ext2_get_group_desc(struct super_block *sb,
unsigned int block_group,
struct buffer_head **bh)
struct ext2_group_desc {
__le32 bg_block_bitmap; // 块位图的块号。
__le32 bg_inode_bitmap; // inode 位图的块号。
__le32 bg_inode_table; // inode 表的块号。
__le16 bg_free_blocks_count; // 空闲块数目。
__le16 bg_free_inodes_count; // 空闲 inode 数目。
__le16 bg_used_dirs_count; // 目录数目。
__le16 bg_pad;
__le32 bg_reserved[3];
};
试图将一个文件都数据存在同一个块组中,可减小寻道代价。
3. inode
每个块组有:
1. 一个 inode 位图:表示当前块组的 inode 是否已使用。
2. 一个本地 inode 表:当前块组的文件的 inode 信息。每个 inode 如下表示:
struct ext2_inode {
__le16 i_mode; //访问权限,和文件类型。
__le16 i_uid; //文件的uid的低16位。
__le16 i_gid; //文件的组id的低16位。
__le16 l_i_uid_high; //uid 高16位。
__le16 l_i_gid_high; //gid 高16位。
__le32 i_size; //文件长度。单位:字节。
__le32 i_atime; //最后访问时间。
__le32 i_ctime; //文件创建时间。
__le32 i_mtime; //最后修改时间。
__le32 i_dtime; //文件被删除时间。
__le16 i_links_count; //硬链接数目。
__le32 i_blocks; //文件占用块数量。
__le32 i_flags; //具体看ext2实现。
__le32 l_i_reserved1;
__le32 i_block[EXT2_N_BLOCKS];
//指向数据块块号 EXT2_N_BLOCKS=12,前12个直接寻址,后3个间接寻址。
__le32 i_file_acl; //文件ACL,细粒度的访问权限控制。
__le32 i_dir_acl; //目录ACL。
__le32 i_faddr; //碎片地址。
__u8 l_i_frag; //碎片号。
__u8 l_i_fsize; //碎片大小。
__u16 i_pad1;
__u32 l_i_reserved2;
};
上述以 l_ 开头的成员表示用于Linux 系统。
struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
作用:根据inode编号,获取对应 struct inode。
static int __ext2_write_inode(struct inode *inode, int do_sync)
作用:将内存中 inode 写回到磁盘 inode 表中。
struct super_operations ext2_sops = {
.alloc_inode = ext2_alloc_inode,
.destroy_inode = ext2_destroy_inode,
.write_inode = ext2_write_inode,
.put_super = ext2_put_super,
.sync_fs = ext2_sync_fs,
};
文件洞(File Hole):即稀疏文件(Sparse File)。
问:如何创建文件洞?
答:只改变文件指针位置。不分配实际数据块,但文件系统仍记录该数据块位置和大小。
好处:节省磁盘空间,读文件时,自动填充零。
注意:当文件传输或备份时,可能无法正确处理文件洞,导致与原始文件大小不一致。
s_inodes_per_group:
每个块组中 inode数目,默认为128。即 128个文件。
4. 目录和文件
目录是一种特殊文件,每个目录有一个inode,并分配对应数据块。
数据块中存储着目录项结构(即表示目录中文件或子目录),即如下结构体。
一个目录项的结构:
struct ext2_dir_entry_2 {
//该成员来自inode指向的数据块,代表该目录下的一个文件。多个文件就多个结构体
__le32 inode; //目录文件的 inode编号。
__le16 rec_len; //目录项占用磁盘大小,删除目录时,跳过。
__u8 name_len; //目录名称的长度。
__u8 file_type; //文件类型。
char name[ ]; //目录名称,小于255。
} ext2_dirent;
注意:
文件名须为4 的整数倍,否则用 \0 填充。
rec_len 长度计算方法:
当前目录项的 rec_len 末尾到下一个目录项 rec_len 开头的偏移量字节。
举例:
一个目录下有文件:
该目录的 数据块存储 struct ext2_dir_entry_2 结构体,如下:
第一个文件. 的rec_len为12字节,计算方法:
1(name_len) + 1(file_type) + 4(name) + 下一个目录项的 4(inode) + 2(rec_len)
注意:
当删除一个目录项时使用,不用移动文件,将rec_len增加偏移,跳过即可。
该目录中文件 deldir 已被删除,则第四个文件 linux 中的 rec_len 需要偏移跳过 deldir 文件,所以rec_len 为32。
只有目录和普通文件占用硬盘的数据块。其他文件不用,如:
1. 符号链接文件:
目标路径小于60字节,则存在 inode中。
大于60字节时,分配数据块来存储。
2. 设备文件,命名管道,socket文件:
只需要一个inode即可,无需数据块。
5. 内存中的数据结构
struct super_block {
void *s_fs_info;
//特定文件系统实现,ext2 中指向 struct ext2_sb_info,只存在于内存。
}
struct inode {
void *i_private;
//特定文件系统实现,ext2 中指向 struct ext2_inode_info,只存在于内存。
}
块的预分配
文件请求分配数据块时,多分配些连续的数据块,秘密标记。
优点:节省时间,防止碎片。
预留窗口:定义了预留的区域。
struct ext2_reserve_window {
ext2_fsblk_t _rsv_start; //预留的起始字节。
ext2_fsblk_t _rsv_end; //预留的结束字节。
}