linux/fs/ext4/mballoc-test.c
Baokun Li f7eaacbb4e ext4: convert free groups order lists to xarrays
While traversing the list, holding a spin_lock prevents load_buddy, making
direct use of ext4_try_lock_group impossible. This can lead to a bouncing
scenario where spin_is_locked(grp_A) succeeds, but ext4_try_lock_group()
fails, forcing the list traversal to repeatedly restart from grp_A.

In contrast, linear traversal directly uses ext4_try_lock_group(),
avoiding this bouncing. Therefore, we need a lockless, ordered traversal
to achieve linear-like efficiency.

Therefore, this commit converts both average fragment size lists and
largest free order lists into ordered xarrays.

In an xarray, the index represents the block group number and the value
holds the block group information; a non-empty value indicates the block
group's presence.

While insertion and deletion complexity remain O(1), lookup complexity
changes from O(1) to O(nlogn), which may slightly reduce single-threaded
performance.

Additionally, xarray insertions might fail, potentially due to memory
allocation issues. However, since we have linear traversal as a fallback,
this isn't a major problem. Therefore, we've only added a warning message
for insertion failures here.

A helper function ext4_mb_find_good_group_xarray() is added to find good
groups in the specified xarray starting at the specified position start,
and when it reaches ngroups-1, it wraps around to 0 and then to start-1.
This ensures an ordered traversal within the xarray.

Performance test results are as follows: Single-process operations
on an empty disk show negligible impact, while multi-process workloads
demonstrate a noticeable performance gain.

|CPU: Kunpeng 920   |          P80           |            P1           |
|Memory: 512GB      |------------------------|-------------------------|
|960GB SSD (0.5GB/s)| base  |    patched     | base   |    patched     |
|-------------------|-------|----------------|--------|----------------|
|mb_optimize_scan=0 | 20097 | 19555 (-2.6%)  | 316141 | 315636 (-0.2%) |
|mb_optimize_scan=1 | 13318 | 15496 (+16.3%) | 325273 | 323569 (-0.5%) |

|CPU: AMD 9654 * 2  |          P96           |             P1          |
|Memory: 1536GB     |------------------------|-------------------------|
|960GB SSD (1GB/s)  | base  |    patched     | base   |    patched     |
|-------------------|-------|----------------|--------|----------------|
|mb_optimize_scan=0 | 53603 | 53192 (-0.7%)  | 214243 | 212678 (-0.7%) |
|mb_optimize_scan=1 | 20887 | 37636 (+80.1%) | 213632 | 214189 (+0.2%) |

[ Applied spelling fixes per discussion on the ext4-list see thread
  referened in the Link tag. --tytso]

Signed-off-by: Baokun Li <libaokun1@huawei.com>
Reviewed-by: Zhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20250714130327.1830534-16-libaokun1@huawei.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
2025-07-25 09:14:17 -04:00

999 lines
27 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* KUnit test of ext4 multiblocks allocation.
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
#include <linux/random.h>
#include "ext4.h"
struct mbt_grp_ctx {
struct buffer_head bitmap_bh;
/* desc and gd_bh are just the place holders for now */
struct ext4_group_desc desc;
struct buffer_head gd_bh;
};
struct mbt_ctx {
struct mbt_grp_ctx *grp_ctx;
};
struct mbt_ext4_super_block {
struct ext4_super_block es;
struct ext4_sb_info sbi;
struct mbt_ctx mbt_ctx;
};
#define MBT_SB(_sb) (container_of((_sb)->s_fs_info, struct mbt_ext4_super_block, sbi))
#define MBT_CTX(_sb) (&MBT_SB(_sb)->mbt_ctx)
#define MBT_GRP_CTX(_sb, _group) (&MBT_CTX(_sb)->grp_ctx[_group])
static struct inode *mbt_alloc_inode(struct super_block *sb)
{
struct ext4_inode_info *ei;
ei = kmalloc(sizeof(struct ext4_inode_info), GFP_KERNEL);
if (!ei)
return NULL;
INIT_LIST_HEAD(&ei->i_orphan);
init_rwsem(&ei->xattr_sem);
init_rwsem(&ei->i_data_sem);
inode_init_once(&ei->vfs_inode);
ext4_fc_init_inode(&ei->vfs_inode);
return &ei->vfs_inode;
}
static void mbt_free_inode(struct inode *inode)
{
kfree(EXT4_I(inode));
}
static const struct super_operations mbt_sops = {
.alloc_inode = mbt_alloc_inode,
.free_inode = mbt_free_inode,
};
static void mbt_kill_sb(struct super_block *sb)
{
generic_shutdown_super(sb);
}
static struct file_system_type mbt_fs_type = {
.name = "mballoc test",
.kill_sb = mbt_kill_sb,
};
static int mbt_mb_init(struct super_block *sb)
{
ext4_fsblk_t block;
int ret;
/* needed by ext4_mb_init->bdev_nonrot(sb->s_bdev) */
sb->s_bdev = kzalloc(sizeof(*sb->s_bdev), GFP_KERNEL);
if (sb->s_bdev == NULL)
return -ENOMEM;
sb->s_bdev->bd_queue = kzalloc(sizeof(struct request_queue), GFP_KERNEL);
if (sb->s_bdev->bd_queue == NULL) {
kfree(sb->s_bdev);
return -ENOMEM;
}
/*
* needed by ext4_mb_init->ext4_mb_init_backend-> sbi->s_buddy_cache =
* new_inode(sb);
*/
INIT_LIST_HEAD(&sb->s_inodes);
sb->s_op = &mbt_sops;
ret = ext4_mb_init(sb);
if (ret != 0)
goto err_out;
block = ext4_count_free_clusters(sb);
ret = percpu_counter_init(&EXT4_SB(sb)->s_freeclusters_counter, block,
GFP_KERNEL);
if (ret != 0)
goto err_mb_release;
ret = percpu_counter_init(&EXT4_SB(sb)->s_dirtyclusters_counter, 0,
GFP_KERNEL);
if (ret != 0)
goto err_freeclusters;
return 0;
err_freeclusters:
percpu_counter_destroy(&EXT4_SB(sb)->s_freeclusters_counter);
err_mb_release:
ext4_mb_release(sb);
err_out:
kfree(sb->s_bdev->bd_queue);
kfree(sb->s_bdev);
return ret;
}
static void mbt_mb_release(struct super_block *sb)
{
percpu_counter_destroy(&EXT4_SB(sb)->s_dirtyclusters_counter);
percpu_counter_destroy(&EXT4_SB(sb)->s_freeclusters_counter);
ext4_mb_release(sb);
kfree(sb->s_bdev->bd_queue);
kfree(sb->s_bdev);
}
static int mbt_set(struct super_block *sb, void *data)
{
return 0;
}
static struct super_block *mbt_ext4_alloc_super_block(void)
{
struct mbt_ext4_super_block *fsb;
struct super_block *sb;
struct ext4_sb_info *sbi;
fsb = kzalloc(sizeof(*fsb), GFP_KERNEL);
if (fsb == NULL)
return NULL;
sb = sget(&mbt_fs_type, NULL, mbt_set, 0, NULL);
if (IS_ERR(sb))
goto out;
sbi = &fsb->sbi;
sbi->s_blockgroup_lock =
kzalloc(sizeof(struct blockgroup_lock), GFP_KERNEL);
if (!sbi->s_blockgroup_lock)
goto out_deactivate;
bgl_lock_init(sbi->s_blockgroup_lock);
sbi->s_es = &fsb->es;
sbi->s_sb = sb;
sb->s_fs_info = sbi;
up_write(&sb->s_umount);
return sb;
out_deactivate:
deactivate_locked_super(sb);
out:
kfree(fsb);
return NULL;
}
static void mbt_ext4_free_super_block(struct super_block *sb)
{
struct mbt_ext4_super_block *fsb = MBT_SB(sb);
struct ext4_sb_info *sbi = EXT4_SB(sb);
kfree(sbi->s_blockgroup_lock);
deactivate_super(sb);
kfree(fsb);
}
struct mbt_ext4_block_layout {
unsigned char blocksize_bits;
unsigned int cluster_bits;
uint32_t blocks_per_group;
ext4_group_t group_count;
uint16_t desc_size;
};
static void mbt_init_sb_layout(struct super_block *sb,
struct mbt_ext4_block_layout *layout)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_super_block *es = sbi->s_es;
sb->s_blocksize = 1UL << layout->blocksize_bits;
sb->s_blocksize_bits = layout->blocksize_bits;
sbi->s_groups_count = layout->group_count;
sbi->s_blocks_per_group = layout->blocks_per_group;
sbi->s_cluster_bits = layout->cluster_bits;
sbi->s_cluster_ratio = 1U << layout->cluster_bits;
sbi->s_clusters_per_group = layout->blocks_per_group >>
layout->cluster_bits;
sbi->s_desc_size = layout->desc_size;
sbi->s_desc_per_block_bits =
sb->s_blocksize_bits - (fls(layout->desc_size) - 1);
sbi->s_desc_per_block = 1 << sbi->s_desc_per_block_bits;
es->s_first_data_block = cpu_to_le32(0);
es->s_blocks_count_lo = cpu_to_le32(layout->blocks_per_group *
layout->group_count);
}
static int mbt_grp_ctx_init(struct super_block *sb,
struct mbt_grp_ctx *grp_ctx)
{
ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
grp_ctx->bitmap_bh.b_data = kzalloc(EXT4_BLOCK_SIZE(sb), GFP_KERNEL);
if (grp_ctx->bitmap_bh.b_data == NULL)
return -ENOMEM;
mb_set_bits(grp_ctx->bitmap_bh.b_data, max, sb->s_blocksize * 8 - max);
ext4_free_group_clusters_set(sb, &grp_ctx->desc, max);
return 0;
}
static void mbt_grp_ctx_release(struct mbt_grp_ctx *grp_ctx)
{
kfree(grp_ctx->bitmap_bh.b_data);
grp_ctx->bitmap_bh.b_data = NULL;
}
static void mbt_ctx_mark_used(struct super_block *sb, ext4_group_t group,
unsigned int start, unsigned int len)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
mb_set_bits(grp_ctx->bitmap_bh.b_data, start, len);
}
static void *mbt_ctx_bitmap(struct super_block *sb, ext4_group_t group)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
return grp_ctx->bitmap_bh.b_data;
}
/* called after mbt_init_sb_layout */
static int mbt_ctx_init(struct super_block *sb)
{
struct mbt_ctx *ctx = MBT_CTX(sb);
ext4_group_t i, ngroups = ext4_get_groups_count(sb);
ctx->grp_ctx = kcalloc(ngroups, sizeof(struct mbt_grp_ctx),
GFP_KERNEL);
if (ctx->grp_ctx == NULL)
return -ENOMEM;
for (i = 0; i < ngroups; i++)
if (mbt_grp_ctx_init(sb, &ctx->grp_ctx[i]))
goto out;
/*
* first data block(first cluster in first group) is used by
* metadata, mark it used to avoid to alloc data block at first
* block which will fail ext4_sb_block_valid check.
*/
mb_set_bits(ctx->grp_ctx[0].bitmap_bh.b_data, 0, 1);
ext4_free_group_clusters_set(sb, &ctx->grp_ctx[0].desc,
EXT4_CLUSTERS_PER_GROUP(sb) - 1);
return 0;
out:
while (i-- > 0)
mbt_grp_ctx_release(&ctx->grp_ctx[i]);
kfree(ctx->grp_ctx);
return -ENOMEM;
}
static void mbt_ctx_release(struct super_block *sb)
{
struct mbt_ctx *ctx = MBT_CTX(sb);
ext4_group_t i, ngroups = ext4_get_groups_count(sb);
for (i = 0; i < ngroups; i++)
mbt_grp_ctx_release(&ctx->grp_ctx[i]);
kfree(ctx->grp_ctx);
}
static struct buffer_head *
ext4_read_block_bitmap_nowait_stub(struct super_block *sb, ext4_group_t block_group,
bool ignore_locked)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group);
/* paired with brelse from caller of ext4_read_block_bitmap_nowait */
get_bh(&grp_ctx->bitmap_bh);
return &grp_ctx->bitmap_bh;
}
static int ext4_wait_block_bitmap_stub(struct super_block *sb,
ext4_group_t block_group,
struct buffer_head *bh)
{
/*
* real ext4_wait_block_bitmap will set these flags and
* functions like ext4_mb_init_cache will verify the flags.
*/
set_buffer_uptodate(bh);
set_bitmap_uptodate(bh);
set_buffer_verified(bh);
return 0;
}
static struct ext4_group_desc *
ext4_get_group_desc_stub(struct super_block *sb, ext4_group_t block_group,
struct buffer_head **bh)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group);
if (bh != NULL)
*bh = &grp_ctx->gd_bh;
return &grp_ctx->desc;
}
static int
ext4_mb_mark_context_stub(handle_t *handle, struct super_block *sb, bool state,
ext4_group_t group, ext4_grpblk_t blkoff,
ext4_grpblk_t len, int flags,
ext4_grpblk_t *ret_changed)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
struct buffer_head *bitmap_bh = &grp_ctx->bitmap_bh;
if (state)
mb_set_bits(bitmap_bh->b_data, blkoff, len);
else
mb_clear_bits(bitmap_bh->b_data, blkoff, len);
return 0;
}
#define TEST_GOAL_GROUP 1
static int mbt_kunit_init(struct kunit *test)
{
struct mbt_ext4_block_layout *layout =
(struct mbt_ext4_block_layout *)(test->param_value);
struct super_block *sb;
int ret;
sb = mbt_ext4_alloc_super_block();
if (sb == NULL)
return -ENOMEM;
mbt_init_sb_layout(sb, layout);
ret = mbt_ctx_init(sb);
if (ret != 0) {
mbt_ext4_free_super_block(sb);
return ret;
}
test->priv = sb;
kunit_activate_static_stub(test,
ext4_read_block_bitmap_nowait,
ext4_read_block_bitmap_nowait_stub);
kunit_activate_static_stub(test,
ext4_wait_block_bitmap,
ext4_wait_block_bitmap_stub);
kunit_activate_static_stub(test,
ext4_get_group_desc,
ext4_get_group_desc_stub);
kunit_activate_static_stub(test,
ext4_mb_mark_context,
ext4_mb_mark_context_stub);
/* stub function will be called in mbt_mb_init->ext4_mb_init */
if (mbt_mb_init(sb) != 0) {
mbt_ctx_release(sb);
mbt_ext4_free_super_block(sb);
return -ENOMEM;
}
return 0;
}
static void mbt_kunit_exit(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
mbt_mb_release(sb);
mbt_ctx_release(sb);
mbt_ext4_free_super_block(sb);
}
static void test_new_blocks_simple(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
struct inode *inode;
struct ext4_allocation_request ar;
ext4_group_t i, goal_group = TEST_GOAL_GROUP;
int err = 0;
ext4_fsblk_t found;
struct ext4_sb_info *sbi = EXT4_SB(sb);
inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
if (!inode)
return;
inode->i_sb = sb;
ar.inode = inode;
/* get block at goal */
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test, ar.goal, found,
"failed to alloc block at goal, expected %llu found %llu",
ar.goal, found);
/* get block after goal in goal group */
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test, ar.goal + EXT4_C2B(sbi, 1), found,
"failed to alloc block after goal in goal group, expected %llu found %llu",
ar.goal + 1, found);
/* get block after goal group */
mbt_ctx_mark_used(sb, goal_group, 0, EXT4_CLUSTERS_PER_GROUP(sb));
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test,
ext4_group_first_block_no(sb, goal_group + 1), found,
"failed to alloc block after goal group, expected %llu found %llu",
ext4_group_first_block_no(sb, goal_group + 1), found);
/* get block before goal group */
for (i = goal_group; i < ext4_get_groups_count(sb); i++)
mbt_ctx_mark_used(sb, i, 0, EXT4_CLUSTERS_PER_GROUP(sb));
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test,
ext4_group_first_block_no(sb, 0) + EXT4_C2B(sbi, 1), found,
"failed to alloc block before goal group, expected %llu found %llu",
ext4_group_first_block_no(sb, 0 + EXT4_C2B(sbi, 1)), found);
/* no block available, fail to allocate block */
for (i = 0; i < ext4_get_groups_count(sb); i++)
mbt_ctx_mark_used(sb, i, 0, EXT4_CLUSTERS_PER_GROUP(sb));
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_NE_MSG(test, err, 0,
"unexpectedly get block when no block is available");
}
#define TEST_RANGE_COUNT 8
struct test_range {
ext4_grpblk_t start;
ext4_grpblk_t len;
};
static void
mbt_generate_test_ranges(struct super_block *sb, struct test_range *ranges,
int count)
{
ext4_grpblk_t start, len, max;
int i;
max = EXT4_CLUSTERS_PER_GROUP(sb) / count;
for (i = 0; i < count; i++) {
start = get_random_u32() % max;
len = get_random_u32() % max;
len = min(len, max - start);
ranges[i].start = start + i * max;
ranges[i].len = len;
}
}
static void
validate_free_blocks_simple(struct kunit *test, struct super_block *sb,
ext4_group_t goal_group, ext4_grpblk_t start,
ext4_grpblk_t len)
{
void *bitmap;
ext4_grpblk_t bit, max = EXT4_CLUSTERS_PER_GROUP(sb);
ext4_group_t i;
for (i = 0; i < ext4_get_groups_count(sb); i++) {
if (i == goal_group)
continue;
bitmap = mbt_ctx_bitmap(sb, i);
bit = mb_find_next_zero_bit(bitmap, max, 0);
KUNIT_ASSERT_EQ_MSG(test, bit, max,
"free block on unexpected group %d", i);
}
bitmap = mbt_ctx_bitmap(sb, goal_group);
bit = mb_find_next_zero_bit(bitmap, max, 0);
KUNIT_ASSERT_EQ(test, bit, start);
bit = mb_find_next_bit(bitmap, max, bit + 1);
KUNIT_ASSERT_EQ(test, bit, start + len);
}
static void
test_free_blocks_simple_range(struct kunit *test, ext4_group_t goal_group,
ext4_grpblk_t start, ext4_grpblk_t len)
{
struct super_block *sb = (struct super_block *)test->priv;
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct inode *inode;
ext4_fsblk_t block;
inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
if (!inode)
return;
inode->i_sb = sb;
if (len == 0)
return;
block = ext4_group_first_block_no(sb, goal_group) +
EXT4_C2B(sbi, start);
ext4_free_blocks_simple(inode, block, len);
validate_free_blocks_simple(test, sb, goal_group, start, len);
mbt_ctx_mark_used(sb, goal_group, 0, EXT4_CLUSTERS_PER_GROUP(sb));
}
static void test_free_blocks_simple(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
ext4_group_t i;
struct test_range ranges[TEST_RANGE_COUNT];
for (i = 0; i < ext4_get_groups_count(sb); i++)
mbt_ctx_mark_used(sb, i, 0, max);
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
for (i = 0; i < TEST_RANGE_COUNT; i++)
test_free_blocks_simple_range(test, TEST_GOAL_GROUP,
ranges[i].start, ranges[i].len);
}
static void
test_mark_diskspace_used_range(struct kunit *test,
struct ext4_allocation_context *ac,
ext4_grpblk_t start,
ext4_grpblk_t len)
{
struct super_block *sb = (struct super_block *)test->priv;
int ret;
void *bitmap;
ext4_grpblk_t i, max;
/* ext4_mb_mark_diskspace_used will BUG if len is 0 */
if (len == 0)
return;
ac->ac_b_ex.fe_group = TEST_GOAL_GROUP;
ac->ac_b_ex.fe_start = start;
ac->ac_b_ex.fe_len = len;
bitmap = mbt_ctx_bitmap(sb, TEST_GOAL_GROUP);
memset(bitmap, 0, sb->s_blocksize);
ret = ext4_mb_mark_diskspace_used(ac, NULL, 0);
KUNIT_ASSERT_EQ(test, ret, 0);
max = EXT4_CLUSTERS_PER_GROUP(sb);
i = mb_find_next_bit(bitmap, max, 0);
KUNIT_ASSERT_EQ(test, i, start);
i = mb_find_next_zero_bit(bitmap, max, i + 1);
KUNIT_ASSERT_EQ(test, i, start + len);
i = mb_find_next_bit(bitmap, max, i + 1);
KUNIT_ASSERT_EQ(test, max, i);
}
static void test_mark_diskspace_used(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
struct inode *inode;
struct ext4_allocation_context ac;
struct test_range ranges[TEST_RANGE_COUNT];
int i;
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
inode = kunit_kzalloc(test, sizeof(*inode), GFP_KERNEL);
if (!inode)
return;
inode->i_sb = sb;
ac.ac_status = AC_STATUS_FOUND;
ac.ac_sb = sb;
ac.ac_inode = inode;
for (i = 0; i < TEST_RANGE_COUNT; i++)
test_mark_diskspace_used_range(test, &ac, ranges[i].start,
ranges[i].len);
}
static void mbt_generate_buddy(struct super_block *sb, void *buddy,
void *bitmap, struct ext4_group_info *grp)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
uint32_t order, off;
void *bb, *bb_h;
int max;
memset(buddy, 0xff, sb->s_blocksize);
memset(grp, 0, offsetof(struct ext4_group_info,
bb_counters[MB_NUM_ORDERS(sb)]));
bb = bitmap;
max = EXT4_CLUSTERS_PER_GROUP(sb);
bb_h = buddy + sbi->s_mb_offsets[1];
off = mb_find_next_zero_bit(bb, max, 0);
grp->bb_first_free = off;
while (off < max) {
grp->bb_counters[0]++;
grp->bb_free++;
if (!(off & 1) && !mb_test_bit(off + 1, bb)) {
grp->bb_free++;
grp->bb_counters[0]--;
mb_clear_bit(off >> 1, bb_h);
grp->bb_counters[1]++;
grp->bb_largest_free_order = 1;
off++;
}
off = mb_find_next_zero_bit(bb, max, off + 1);
}
for (order = 1; order < MB_NUM_ORDERS(sb) - 1; order++) {
bb = buddy + sbi->s_mb_offsets[order];
bb_h = buddy + sbi->s_mb_offsets[order + 1];
max = max >> 1;
off = mb_find_next_zero_bit(bb, max, 0);
while (off < max) {
if (!(off & 1) && !mb_test_bit(off + 1, bb)) {
mb_set_bits(bb, off, 2);
grp->bb_counters[order] -= 2;
mb_clear_bit(off >> 1, bb_h);
grp->bb_counters[order + 1]++;
grp->bb_largest_free_order = order + 1;
off++;
}
off = mb_find_next_zero_bit(bb, max, off + 1);
}
}
max = EXT4_CLUSTERS_PER_GROUP(sb);
off = mb_find_next_zero_bit(bitmap, max, 0);
while (off < max) {
grp->bb_fragments++;
off = mb_find_next_bit(bitmap, max, off + 1);
if (off + 1 >= max)
break;
off = mb_find_next_zero_bit(bitmap, max, off + 1);
}
}
static void
mbt_validate_group_info(struct kunit *test, struct ext4_group_info *grp1,
struct ext4_group_info *grp2)
{
struct super_block *sb = (struct super_block *)test->priv;
int i;
KUNIT_ASSERT_EQ(test, grp1->bb_first_free,
grp2->bb_first_free);
KUNIT_ASSERT_EQ(test, grp1->bb_fragments,
grp2->bb_fragments);
KUNIT_ASSERT_EQ(test, grp1->bb_free, grp2->bb_free);
KUNIT_ASSERT_EQ(test, grp1->bb_largest_free_order,
grp2->bb_largest_free_order);
for (i = 1; i < MB_NUM_ORDERS(sb); i++) {
KUNIT_ASSERT_EQ_MSG(test, grp1->bb_counters[i],
grp2->bb_counters[i],
"bb_counters[%d] diffs, expected %d, generated %d",
i, grp1->bb_counters[i],
grp2->bb_counters[i]);
}
}
static void
do_test_generate_buddy(struct kunit *test, struct super_block *sb, void *bitmap,
void *mbt_buddy, struct ext4_group_info *mbt_grp,
void *ext4_buddy, struct ext4_group_info *ext4_grp)
{
int i;
mbt_generate_buddy(sb, mbt_buddy, bitmap, mbt_grp);
for (i = 0; i < MB_NUM_ORDERS(sb); i++)
ext4_grp->bb_counters[i] = 0;
/* needed by validation in ext4_mb_generate_buddy */
ext4_grp->bb_free = mbt_grp->bb_free;
memset(ext4_buddy, 0xff, sb->s_blocksize);
ext4_mb_generate_buddy(sb, ext4_buddy, bitmap, TEST_GOAL_GROUP,
ext4_grp);
KUNIT_ASSERT_EQ(test, memcmp(mbt_buddy, ext4_buddy, sb->s_blocksize),
0);
mbt_validate_group_info(test, mbt_grp, ext4_grp);
}
static void test_mb_generate_buddy(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
void *bitmap, *expected_bb, *generate_bb;
struct ext4_group_info *expected_grp, *generate_grp;
struct test_range ranges[TEST_RANGE_COUNT];
int i;
bitmap = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bitmap);
expected_bb = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, expected_bb);
generate_bb = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, generate_bb);
expected_grp = kunit_kzalloc(test, offsetof(struct ext4_group_info,
bb_counters[MB_NUM_ORDERS(sb)]), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, expected_grp);
generate_grp = ext4_get_group_info(sb, TEST_GOAL_GROUP);
KUNIT_ASSERT_NOT_NULL(test, generate_grp);
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
for (i = 0; i < TEST_RANGE_COUNT; i++) {
mb_set_bits(bitmap, ranges[i].start, ranges[i].len);
do_test_generate_buddy(test, sb, bitmap, expected_bb,
expected_grp, generate_bb, generate_grp);
}
}
static void
test_mb_mark_used_range(struct kunit *test, struct ext4_buddy *e4b,
ext4_grpblk_t start, ext4_grpblk_t len, void *bitmap,
void *buddy, struct ext4_group_info *grp)
{
struct super_block *sb = (struct super_block *)test->priv;
struct ext4_free_extent ex;
int i;
/* mb_mark_used only accepts non-zero len */
if (len == 0)
return;
ex.fe_start = start;
ex.fe_len = len;
ex.fe_group = TEST_GOAL_GROUP;
ext4_lock_group(sb, TEST_GOAL_GROUP);
mb_mark_used(e4b, &ex);
ext4_unlock_group(sb, TEST_GOAL_GROUP);
mb_set_bits(bitmap, start, len);
/* bypass bb_free validatoin in ext4_mb_generate_buddy */
grp->bb_free -= len;
memset(buddy, 0xff, sb->s_blocksize);
for (i = 0; i < MB_NUM_ORDERS(sb); i++)
grp->bb_counters[i] = 0;
ext4_mb_generate_buddy(sb, buddy, bitmap, 0, grp);
KUNIT_ASSERT_EQ(test, memcmp(buddy, e4b->bd_buddy, sb->s_blocksize),
0);
mbt_validate_group_info(test, grp, e4b->bd_info);
}
static void test_mb_mark_used(struct kunit *test)
{
struct ext4_buddy e4b;
struct super_block *sb = (struct super_block *)test->priv;
void *bitmap, *buddy;
struct ext4_group_info *grp;
int ret;
struct test_range ranges[TEST_RANGE_COUNT];
int i;
/* buddy cache assumes that each page contains at least one block */
if (sb->s_blocksize > PAGE_SIZE)
kunit_skip(test, "blocksize exceeds pagesize");
bitmap = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bitmap);
buddy = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buddy);
grp = kunit_kzalloc(test, offsetof(struct ext4_group_info,
bb_counters[MB_NUM_ORDERS(sb)]), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, grp);
ret = ext4_mb_load_buddy(sb, TEST_GOAL_GROUP, &e4b);
KUNIT_ASSERT_EQ(test, ret, 0);
grp->bb_free = EXT4_CLUSTERS_PER_GROUP(sb);
grp->bb_largest_free_order = -1;
grp->bb_avg_fragment_size_order = -1;
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
for (i = 0; i < TEST_RANGE_COUNT; i++)
test_mb_mark_used_range(test, &e4b, ranges[i].start,
ranges[i].len, bitmap, buddy, grp);
ext4_mb_unload_buddy(&e4b);
}
static void
test_mb_free_blocks_range(struct kunit *test, struct ext4_buddy *e4b,
ext4_grpblk_t start, ext4_grpblk_t len, void *bitmap,
void *buddy, struct ext4_group_info *grp)
{
struct super_block *sb = (struct super_block *)test->priv;
int i;
/* mb_free_blocks will WARN if len is 0 */
if (len == 0)
return;
ext4_lock_group(sb, e4b->bd_group);
mb_free_blocks(NULL, e4b, start, len);
ext4_unlock_group(sb, e4b->bd_group);
mb_clear_bits(bitmap, start, len);
/* bypass bb_free validatoin in ext4_mb_generate_buddy */
grp->bb_free += len;
memset(buddy, 0xff, sb->s_blocksize);
for (i = 0; i < MB_NUM_ORDERS(sb); i++)
grp->bb_counters[i] = 0;
ext4_mb_generate_buddy(sb, buddy, bitmap, 0, grp);
KUNIT_ASSERT_EQ(test, memcmp(buddy, e4b->bd_buddy, sb->s_blocksize),
0);
mbt_validate_group_info(test, grp, e4b->bd_info);
}
static void test_mb_free_blocks(struct kunit *test)
{
struct ext4_buddy e4b;
struct super_block *sb = (struct super_block *)test->priv;
void *bitmap, *buddy;
struct ext4_group_info *grp;
struct ext4_free_extent ex;
int ret;
int i;
struct test_range ranges[TEST_RANGE_COUNT];
/* buddy cache assumes that each page contains at least one block */
if (sb->s_blocksize > PAGE_SIZE)
kunit_skip(test, "blocksize exceeds pagesize");
bitmap = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bitmap);
buddy = kunit_kzalloc(test, sb->s_blocksize, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buddy);
grp = kunit_kzalloc(test, offsetof(struct ext4_group_info,
bb_counters[MB_NUM_ORDERS(sb)]), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, grp);
ret = ext4_mb_load_buddy(sb, TEST_GOAL_GROUP, &e4b);
KUNIT_ASSERT_EQ(test, ret, 0);
ex.fe_start = 0;
ex.fe_len = EXT4_CLUSTERS_PER_GROUP(sb);
ex.fe_group = TEST_GOAL_GROUP;
ext4_lock_group(sb, TEST_GOAL_GROUP);
mb_mark_used(&e4b, &ex);
ext4_unlock_group(sb, TEST_GOAL_GROUP);
grp->bb_free = 0;
grp->bb_largest_free_order = -1;
grp->bb_avg_fragment_size_order = -1;
memset(bitmap, 0xff, sb->s_blocksize);
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
for (i = 0; i < TEST_RANGE_COUNT; i++)
test_mb_free_blocks_range(test, &e4b, ranges[i].start,
ranges[i].len, bitmap, buddy, grp);
ext4_mb_unload_buddy(&e4b);
}
#define COUNT_FOR_ESTIMATE 100000
static void test_mb_mark_used_cost(struct kunit *test)
{
struct ext4_buddy e4b;
struct super_block *sb = (struct super_block *)test->priv;
struct ext4_free_extent ex;
int ret;
struct test_range ranges[TEST_RANGE_COUNT];
int i, j;
unsigned long start, end, all = 0;
/* buddy cache assumes that each page contains at least one block */
if (sb->s_blocksize > PAGE_SIZE)
kunit_skip(test, "blocksize exceeds pagesize");
ret = ext4_mb_load_buddy(sb, TEST_GOAL_GROUP, &e4b);
KUNIT_ASSERT_EQ(test, ret, 0);
ex.fe_group = TEST_GOAL_GROUP;
for (j = 0; j < COUNT_FOR_ESTIMATE; j++) {
mbt_generate_test_ranges(sb, ranges, TEST_RANGE_COUNT);
start = jiffies;
for (i = 0; i < TEST_RANGE_COUNT; i++) {
if (ranges[i].len == 0)
continue;
ex.fe_start = ranges[i].start;
ex.fe_len = ranges[i].len;
ext4_lock_group(sb, TEST_GOAL_GROUP);
mb_mark_used(&e4b, &ex);
ext4_unlock_group(sb, TEST_GOAL_GROUP);
}
end = jiffies;
all += (end - start);
for (i = 0; i < TEST_RANGE_COUNT; i++) {
if (ranges[i].len == 0)
continue;
ext4_lock_group(sb, TEST_GOAL_GROUP);
mb_free_blocks(NULL, &e4b, ranges[i].start,
ranges[i].len);
ext4_unlock_group(sb, TEST_GOAL_GROUP);
}
}
kunit_info(test, "costed jiffies %lu\n", all);
ext4_mb_unload_buddy(&e4b);
}
static const struct mbt_ext4_block_layout mbt_test_layouts[] = {
{
.blocksize_bits = 10,
.cluster_bits = 3,
.blocks_per_group = 8192,
.group_count = 4,
.desc_size = 64,
},
{
.blocksize_bits = 12,
.cluster_bits = 3,
.blocks_per_group = 8192,
.group_count = 4,
.desc_size = 64,
},
{
.blocksize_bits = 16,
.cluster_bits = 3,
.blocks_per_group = 8192,
.group_count = 4,
.desc_size = 64,
},
};
static void mbt_show_layout(const struct mbt_ext4_block_layout *layout,
char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "block_bits=%d cluster_bits=%d "
"blocks_per_group=%d group_count=%d desc_size=%d\n",
layout->blocksize_bits, layout->cluster_bits,
layout->blocks_per_group, layout->group_count,
layout->desc_size);
}
KUNIT_ARRAY_PARAM(mbt_layouts, mbt_test_layouts, mbt_show_layout);
static struct kunit_case mbt_test_cases[] = {
KUNIT_CASE_PARAM(test_new_blocks_simple, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_free_blocks_simple, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_mb_generate_buddy, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_mb_mark_used, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_mb_free_blocks, mbt_layouts_gen_params),
KUNIT_CASE_PARAM(test_mark_diskspace_used, mbt_layouts_gen_params),
KUNIT_CASE_PARAM_ATTR(test_mb_mark_used_cost, mbt_layouts_gen_params,
{ .speed = KUNIT_SPEED_SLOW }),
{}
};
static struct kunit_suite mbt_test_suite = {
.name = "ext4_mballoc_test",
.init = mbt_kunit_init,
.exit = mbt_kunit_exit,
.test_cases = mbt_test_cases,
};
kunit_test_suites(&mbt_test_suite);
MODULE_LICENSE("GPL");