From: Denis V. Lunev <[email protected]> The balloon holds file system space that never carries data, but its blocks are allocated, so ext4_trim_all_free() never discards them, and inflating the balloon is a plain fallocate() that does not discard either. Once the balloon is inflated over blocks that held data, e.g. on an online shrink after files were removed, the device below keeps them allocated: a ploop or qcow2 image keeps host disk space the container can never use, and compaction, which is FITRIM, does not give it back.
Walk the balloon after the free space in ext4_trim_fs() and discard its unwritten extents that fall into the requested range. Physically adjacent extents are merged into one discard, otherwise every cut at the unwritten extent length limit leaves a partly covered cluster of the device below. The shared i_rwsem keeps fallocate, truncate and EXT4_IOC_MOVE_EXT away from the balloon meanwhile, so none of its blocks can be freed and reused under the discard. An unwritten extent reads as zeroes whatever the device returns, so the discard changes nothing visible, and repeating it is harmless. A balloon inflated before this change is released by the next FITRIM. https://virtuozzo.atlassian.net/browse/VSTOR-153590 Feature: fs/ext4: fast online shrink support Signed-off-by: Denis V. Lunev <[email protected]> --- fs/ext4/mballoc.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index aaf2c24c514c..8d205efafac9 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -7009,6 +7009,94 @@ ext4_trim_all_free(struct super_block *sb, ext4_group_t group, return ret; } +static int ext4_trim_balloon_run(struct super_block *sb, ext4_fsblk_t pblk, + ext4_fsblk_t len, ext4_fsblk_t start, + ext4_fsblk_t end, ext4_fsblk_t minblocks, + u64 *trimmed) +{ + ext4_fsblk_t last = pblk + len - 1; + + if (!len) + return 0; + if (pblk < start) + pblk = start; + if (last > end) + last = end; + if (last < pblk || last - pblk + 1 < minblocks) + return 0; + + len = last - pblk + 1; + trace_ext4_discard_blocks(sb, pblk, len); + *trimmed += len; + return sb_issue_discard(sb, pblk, len, GFP_NOFS, 0); +} + +/* + * Blocks of the balloon never hold data, but they are allocated, so + * ext4_trim_all_free() never discards them. Discard its unwritten + * extents, merging physically adjacent ones, so that the device below + * can release the space as it does for free blocks. + */ +static int ext4_trim_balloon(struct super_block *sb, ext4_fsblk_t start, + ext4_fsblk_t end, ext4_fsblk_t minblocks, + u64 *trimmed) +{ + struct ext4_sb_info *sbi = EXT4_SB(sb); + ext4_fsblk_t run_pblk = 0, run_len = 0; + struct ext4_map_blocks map; + struct inode *inode = NULL; + ext4_lblk_t lblk, nr_lblks; + bool unwritten; + int ret = 0, err; + + /* ext4_load_balloon() replaces s_balloon_ino under s_umount */ + down_read(&sb->s_umount); + if (sbi->s_balloon_ino && !sb_rdonly(sb)) + inode = igrab(sbi->s_balloon_ino); + up_read(&sb->s_umount); + if (!inode) + return 0; + + inode_lock_shared(inode); + nr_lblks = min_t(u64, EXT_MAX_BLOCKS, + DIV_ROUND_UP(i_size_read(inode), sb->s_blocksize)); + for (lblk = 0; lblk < nr_lblks; lblk += map.m_len) { + if (ext4_trim_interrupted()) + break; + cond_resched(); + + map.m_lblk = lblk; + map.m_len = nr_lblks - lblk; + err = ext4_map_blocks(NULL, inode, &map, 0); + if (err < 0) { + ret = err; + break; + } + if (!map.m_len) + break; + + unwritten = err > 0 && (map.m_flags & EXT4_MAP_UNWRITTEN); + if (unwritten && run_len && run_pblk + run_len == map.m_pblk) { + run_len += map.m_len; + continue; + } + + ret = ext4_trim_balloon_run(sb, run_pblk, run_len, start, end, + minblocks, trimmed); + if (ret) + break; + run_pblk = map.m_pblk; + run_len = unwritten ? map.m_len : 0; + } + if (!ret && !ext4_trim_interrupted()) + ret = ext4_trim_balloon_run(sb, run_pblk, run_len, start, end, + minblocks, trimmed); + inode_unlock_shared(inode); + iput(inode); + + return ret; +} + /** * ext4_trim_fs() -- trim ioctl handle function * @sb: superblock for filesystem @@ -7027,7 +7115,8 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) struct ext4_group_info *grp; ext4_group_t group, first_group, last_group; ext4_grpblk_t cnt = 0, first_cluster, last_cluster; - uint64_t start, end, minlen, trimmed = 0; + uint64_t start, end, minlen, trimmed = 0, balloon_blks = 0; + ext4_fsblk_t last_blk; ext4_fsblk_t first_data_blk = le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block); ext4_fsblk_t max_blks = ext4_blocks_count(EXT4_SB(sb)->s_es); @@ -7061,6 +7150,7 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) &first_group, &first_cluster); ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) end, &last_group, &last_cluster); + last_blk = end; /* end now represents the last cluster to discard in this group */ end = EXT4_CLUSTERS_PER_GROUP(sb) - 1; @@ -7103,6 +7193,13 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) first_cluster = 0; } + if (!ret && !ext4_trim_interrupted()) { + ret = ext4_trim_balloon(sb, start, last_blk, + EXT4_C2B(EXT4_SB(sb), minlen), + &balloon_blks); + trimmed += EXT4_NUM_B2C(EXT4_SB(sb), balloon_blks); + } + if (!ret) EXT4_SB(sb)->s_last_trim_minblks = minlen; -- 2.53.0 _______________________________________________ Devel mailing list [email protected] https://lists.openvz.org/mailman/listinfo/devel
