bigalloc机制

bigalloc机制

简介

概述: 用在初始化文件系统时,以cluster为分配单位而不是块,从而可以指定文件系统的最小分配单位,例如:

1
mkfs.ext4 -O bigalloc -C 16384 /dev/nvme1n1

优点: 大幅减少元数据量(位图大小将至1/4),提升大文件分配效率

缺点: 小文件可能浪费cluster内空间(如inode所在块组)相邻的cluster,减少寻址开销

示例

4KB大小的bitmap大小:(此时block bitmap每一位表示一个block的状态) \[ 4 * 1024 * 8 * 4KB/bit / 1024 = 128MB \] 对于2TB文件系统,块组数量: \[ 2 * 1024 * 1024 / 128 = 16,384 \] 如果指定cluster大小为1MB,每个块组仍使用1个块(4KB)存储快位图,但每个bit管理1MB的cluster,则bitmap大小:(此时block bitmap每一位表示一个cluster的状态) \[ 4 * 1024 * 8 * 1MB/bit = 32GB \] 对于2TB文件系统,块组数量: \[ 2 * 1024 / 32 = 64个 \]

原理

概述:

传统ext4以4KB块为最小分配单元,每个块在块位图(block bitmap)中占用1bit。

启用bigalloc后,多个物理块组成一个块cluster,block bitmap不再对应一个块,而是一个cluster,分配单位以cluster为最小分配单元

用户态部分

mkfs.ext4源码: https://github.com/tytso/e2fsprogs.git

首先可以知道,mkfs.ext4通过以下选项可以开启bigalloc并指定cluster大小:

1
mkfs.ext4 -O bigalloc -C 16384 /dev/nvme1n1

通过-O可以指定变量fs_features为bigalloc,从而使能bigalloc特性

通过-C选项可以赋值变量cluster_size,然后通过以下代码传递给struct ext4_super_block字段s_log_cluster_size

1
2
3
4
5
6
7
8
9
10
11
// main()
// EXT2_MIN_CLUSTER_LOG_SIZE = 1 << 10 = 1024
// ext2fs_log2_u32 就是 log2()
// 所以cluster_size >> EXT2_MIN_CLUSTER_LOG_SIZE = 16
// ext2fs_log2_u32(16) = 4
fs_param.s_log_cluster_size = ext2fs_log2_u32(cluster_size >> EXT2_MIN_CLUSTER_LOG_SIZE);

// ext2fs_initialize
#define set_field(field, default) (super->field = param->field ? \
param->field : (default))
set_field(s_log_cluster_size, super->s_log_block_size+4);

然后在ext2fs_initialize()中将fs的超级块标记为dirty:

1
ext2fs_mark_super_dirty(fs);

这样在ext2fs_close2()的时候,就可以通过ext2fs_flush2()更新磁盘中的超级块信息,接下来在挂载文件系统的时候就是内核态做的事情了

内核态部分

设置cluster大小

与cluster相关结构体字段说明:

  • struct ext4_super_block:这个结构体只用于磁盘读取和写入,不能影响运行时状态,属于”磁盘上的superblock“
    • s_log_cluster_size:superblock的cluster大小(log2后的),计算方式为clustersize >> BLOCK_SIZE,当要使用clustersize转换回来BLOCK_SIZE << s_log_cluster_size(BLOCK_SIZE一般为10)
    • s_clusters_per_group:每个块组的cluster数量
    • s_blocks_per_group:每个块组的block数量
  • struct ext4_sb_info:这个结构体能够通过修改字段影响运行时状态,属于”内存上的superblock“
    • s_cluster_ratio:表示每个cluster包含多少个block,计算方式为clustersize / blocksize
    • s_cluster_bits:表示s_cluster_ratio是2的几次方,可以理解为s_cluster_bits = log2(s_cluster_ratio),用于位运算优化
    • s_clusters_per_group:每个块组的cluster数量
    • s_blocks_per_group:每个块组的block数量

通过上面的用户态代码,已经将磁盘块中的superblock更新。

挂载的时候,在sys_mount()中,会通过ext4_fill_super()ext4_load_super()加载磁盘中的超级块,具体调用链如下:

1
sys_mount() -> do_mount() -> path_mount() -> do_new_mount() -> vfs_get_tree() -> ext4_get_tree() -> get_tree_bdev() -> ext4_fill_super() -> __ext4_fill_super() -> ext4_load_super() -> ext4_sb_bread_unmovable()

ext4_load_super()将磁盘中的超级块读取后存储到struct ext4_super_block

ext4_fill_super()中调用ext4_handle_clustersize()来赋值s_clusters_per_groups_cluster_ratios_cluster_bits,如下:

1
2
3
4
5
6
7
8
9
10
11
12
/* 使能bigalloc */
// es->s_log_cluster_size在用户态代码赋值的,由用户定义
clustersize = BLOCK_SIZE << le32_to_cpu(es->s_log_cluster_size);
sbi->s_cluster_bits = le32_to_cpu(es->s_log_cluster_size) - le32_to_cpu(es->s_log_block_size);
// es->s_clusters_per_group在用户态代码中赋值,大小等于最开始的s_blocks_per_group,然后s_blocks_per_group会被重新赋值,即等于s_clusters_per_group*每个cluster的block数量
sbi->s_clusters_per_group = le32_to_cpu(es->s_clusters_per_group);
sbi->s_cluster_ratio = clustersize / sb->s_blocksize;

/* 没有使能bigalloc */
sbi->s_cluster_bits = 0;
sbi->s_clusters_per_group = sbi->s_blocks_per_group;
sbi->s_cluster_ratio = 0;

用户态中重新赋值s_blocks_per_group的代码:

1
2
3
4
5
6
7
// s_block_per_group = s_clusters_per_group << s_cluster_bits,就是每组的cluster数乘以一个cluster包含的block数量
bpg = EXT2FS_C2B(fs, (unsigned long long) super->s_clusters_per_group);
if (bpg >= (((unsigned long long) 1) << 32)) {
retval = EXT2_ET_INVALID_ARGUMENT;
goto cleanup;
}
super->s_blocks_per_group = bpg;

至此,在挂载文件系统后,与cluster相关结构体的字段已经成功初始化,接下来就是使用阶段,包括分配、回收、统计、bitmap操作时,代码是如何以cluster为分配单位进行执行的。

使用阶段

在使能bigalloc后:

  • 逻辑块号在磁盘元数据中依然是以block号表示,即逻辑寻址仍使用block号
  • 涉及分配、回收、统计、bitmap操作时,粒度就从block变成cluster(一般来说,会在入口处把block转换为cluster,在出口处把cluster转换为block)

需要先了解一下常用到的宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// EXT4_B2C:用于将block号转换为cluster号
#define EXT4_B2C(sbi, blk) ((blk) >> (sbi)->s_cluster_bits)

// EXT4_C2B:用于将cluster号转换为block号
#define EXT4_C2B(sbi, cluster) ((cluster) << (sbi)->s_cluster_bits)

// EXT4_NUM_B2C:用于将block数量转换为cluster数量,常用于向上取整的转换,等价于ceil(blks / s_cluster_ratio)
#define EXT4_NUM_B2C(sbi, blks) (((blks) + (sbi)->s_cluster_ratio - 1) >> \
(sbi)->s_cluster_bits)

// EXT4_PBLK_COFF:一个物理块在其所在的cluster中的偏移量,这是一个位掩码操作,等价于pblk % s_cluster_ratio
#define EXT4_PBLK_COFF(s, pblk) ((pblk) & ((ext4_fsblk_t) (s)->s_cluster_ratio - 1))

// EXT4_LBLK_COFF:一个逻辑块在其所在的cluster中的偏移量,这是一个位掩码操作,等价于lblk % s_cluster_ratio
#define EXT4_LBLK_COFF(s, lblk) ((lblk) & ((ext4_lblk_t) (s)->s_cluster_ratio - 1))

可以注意到,当没有使能bigalloc时,s_cluster_bitss_cluster_ratio均为0,使用这几个宏并不会造成影响,所以只需要在用到block号的地方都用这些宏,这样使能bigalloc自然就以cluster为分配单位,而不使能bigalloc自然就以block为分配单位。

下面以ext4_init_block_bitmap()作为一个示例,这个函数用于初始未初始化的block bitmap,每个block group都有一个block bitmap,它用于标记该组中的每个块是已用还是空闲:

  • 这个函数会调用ext4_num_base_meta_clusters()来计算一个block group中元数据所占的cluster数,如果没有使能bigalloc,这里返回的是block数,函数中用到了EXT4_NUM_B2C宏。
  • 这个函数会调用num_clusters_in_group()来计算某个block group中的cluster数,同样使用了EXT4_NUM_B2C宏,所以没有使能bigalloc这里返回的是block数

再比如说ext4_ext_map_blocks(),这个函数负责处理文件逻辑块到物理块的映射关系,在文件读写操作中被调用,用于查找或分配磁盘块。在分配新块时,会用到EXT4_LBLK_COFF宏、EXT4_NUM_B2C宏和EXT4_C2B宏。

一些常见的函数

  • block group初始化
    • ext4_init_block_bitmap()
    • ext4_mb_init_group()
    • ext4_mb_generate_buddy()
    • ext4_mb_mark_free_simple() / ext4_mb_mark_used_simple()
    • ext4_group_first_cluster() / ext4_group_last_cluster()
  • mballoc相关
    • ext4_mb_new_blocks()
    • ext4_mb_normalize_request()
    • ext4_mb_use_preallocated()
    • ext4_mb_good_group()
    • ext4_mb_clear_bb()
    • ext4_mb_discard_preallocations()
  • 释放与调整相关
    • ext4_free_blocks()
    • ext4_remove_space()
    • ext4_ext_remove_space()
    • ext4_ext_rm_leaf()
    • ext4_remove_blocks()
  • extent建立/修改
    • ext4_ext_map_blocks()
    • ext4_ext_insert_extent()
    • ext4_punch_hole()
    • ext4_zero_range()
    • ext4_collapse_range()
  • 统计、配额与statfs
    • ext4_count_free_clusters()
    • ext4_get_free_clusters()
    • ext4_statfs()
    • ext4_calculate_overhead() / ext4_update_overhead()
  • 在线resize
    • ext4_resize_fs()
    • ext4_group_add_blocks()
    • ext4_flex_group_add()
  • 其他
    • ext4_inode_block_valid() / ext4_inode_cluster_valid()
    • ext4_mb_discard_group_preallocations()
    • ext4_mb_release_group_pa()
    • ext4_mb_free_metadata()

bigalloc机制
http://example.com/2025/06/27/bigalloc机制/
作者
凌云行者
发布于
2025年6月27日
许可协议