目录
1 关于Ext4系统的一些概念
1.1 Inode与Data Block
Superblock:超级块,包含有关文件系统的信息。
Group Descriptors :组描述符,包含数据块位图,inode位图(inode Bitmap)和inode表(inode Table)在块组中的位置。
Data Block Bitmap:指示正在使用的数据块。
Inode Bitmap:指示正在使用inode表(inode Table)条目。
Inode Table:Inode表示每个文件和目录的数据结构。它是一个或多个块。它的位置不固定,在组描述符中指出。
Data blocks:文件的实际内容,它是一个或多个块。基本上是块组中的所有其他块。
1.2 关于上述概念的规律
总结来说,和Inode是文件的数据结构和目录,而Data Block相关的内容是真实的文件内容。带Bitmap是指示正在使用。
如图所示,大体可以看出他们的关系:一个Ext4系统内有若干个Block Group,每个Block Group里面有若干个Block。
大致每个Block Group放置的次序都是差不多的:先放置Bitmap(Data Block和Inode的Bitmap),再放置Inode和Data Block。除第一个Block会放置Superblock和Group Descriptors。
2 查找Inode在磁盘的位置
2.1 关于Inode操作的数据结构
在 /fs/ext4/namei.c 中可以找到关于Inode的操作,从结构体命名上也可以看出来Inode是ext4文件目录的数据结构。
其中有一条是 ext4_create,这应该就是ext4文件目录的创建主入口。
/*
* directories can handle most operations...
*/
const struct inode_operations ext4_dir_inode_operations = {
.create = ext4_create,
.lookup = ext4_lookup,
.link = ext4_link,
.unlink = ext4_unlink,
.symlink = ext4_symlink,
.mkdir = ext4_mkdir,
.rmdir = ext4_rmdir,
.mknod = ext4_mknod,
.tmpfile = ext4_tmpfile,
.rename2 = ext4_rename2,
.setattr = ext4_setattr,
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.listxattr = ext4_listxattr,
.removexattr = generic_removexattr,
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
.fiemap = ext4_fiemap,
};
关键性的函数在 /fs/ext4/ialloc.c 中。由 ext4_create 调用的 __ext4_new_inode函数。
2.2 Ext4在磁盘上Inode分配的策略分析
EXT4文件系统的inode是非常多的,按照Inodes Per Block Group = 4096而言,1T空间能提供出32M个inode左右,当我们创建一个目录,或者创建一个文件时,EXT4会怎么选择inode呢?
上述关键性函数 __ext4_new_inode 确实起到了关键性作用。
这个__ext4_new_inode是EXT4非常重要的一个函数,占据了fs/ext4/ialloc.c的四分之一以上的篇幅。这里介绍选择 inode 分配给新的文件或目录的这个流程。
根据 1.2 中的可视化表示,可以总结出一些规律:
Ext4在磁盘上分配空间的话,无非就要经历以下三步:
1.which flex block group
2.which block group
3.which inode
这些步骤中,第一步决定了目录是分散还是聚集在一起。文件和目录的规则是不同的:
if (S_ISDIR(mode))
ret2 = find_group_orlov(sb, dir, &group, mode, qstr);
else
ret2 = find_group_other(sb, dir, &group, mode);
对于目录和文件,选择group的规则是不同的。对于目录而言,使用find_group_orlov函数来寻找合适的 group,该函数使用了orlov allocator算法,而对于文件,软链接等则 使用了find_group_other来寻找合适的group。
至此,问题又被细化为了两部分:目录的inode分配和文件的inode分配。
2.3.1 目录的Inode分配策略
1. 采用spread out 策略让目录散开
2. 根据局部性原理,让兄弟目录,父子目录尽可能的靠近
当然这两种搜索都可能失败,那么就各个块组地毯式搜索。
首先如果文件系统使用了flex_bg就以战斗小组为单位分配,率先需要确定inode所在的战斗小组, 即ngroup的值为文件系统中包含的所有逻辑块组的个数,当然了,如果并没有采用flex_bg,就退化成普通的块组。 接下来,对文件系统的总体使用情况做一个评估,我们如果是伸展开的策略,那么我们希望我们的inode分布在一个试用情况低于平均水平的逻辑块组。这样各个逻辑块组使用才能均衡。
freei = percpu_counter_read_positive(&sbi->s_freeinodes_counter);
avefreei = freei / ngroups;
freeb = EXT4_C2B(sbi,
percpu_counter_read_positive(&sbi->s_freeclusters_counter));
avefreec = freeb;
do_div(avefreec, ngroups);
ndirs = percpu_counter_read_positive(&sbi->s_dirs_counter);
接下来的情况是何时采用伸展开的策略,何时采用靠近的策略。
if (S_ISDIR(mode) &&
((parent == sb->s_root->d_inode) ||
(ext4_test_inode_flag(parent, EXT4_INODE_TOPDIR)))) {
int best_ndir = inodes_per_group;
int ret = -1;
if (qstr) {
hinfo.hash_version = DX_HASH_HALF_MD4;
hinfo.seed = sbi->s_hash_seed;
ext4fs_dirhash(qstr->name, qstr->len, &hinfo);
grp = hinfo.hash;
} else
grp = prandom_u32();
parent_group = (unsigned)grp % ngroups;
for (i = 0; i < ngroups; i++) {
g = (parent_group + i) % ngroups;
get_orlov_stats(sb, g, flex_size, &stats);
if (!stats.free_inodes)
continue;
if (stats.used_dirs >= best_ndir)
continue;
if (stats.free_inodes < avefreei)
continue;