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. The walk goes to the end of the extent tree, as fallocate() with FALLOC_FL_KEEP_SIZE can leave extents past i_size. Physically adjacent extents are merged into one discard, otherwise every cut at the unwritten extent length limit leaves a partly covered cluster of the image below. A merged run is discarded in chunks, with a check for fatal signals and freezing between them. The walk holds i_rwsem and the invalidate lock exclusively, after waiting for direct I/O and flushing the page cache of the balloon, as punch hole does. No block of the balloon can be freed and reused under the discard, and nothing is written to it meanwhile, so an extent that is still unwritten reads as zeroes before and after the discard. The balloon is hidden from lookup and opened only through EXT4_IOC_OPEN_BALLOON, which takes CAP_SYS_ADMIN, so the lock only waits for ploop inflating or shrinking it. Every FITRIM discards the balloon again, which costs little: dm-ploop completes the discard of an unmapped cluster at once. The balloon is not counted in the trimmed length reported to userspace, and a discard error fails FITRIM. 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 | 126 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index aaf2c24c514c..5fb87753975d 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -7009,6 +7009,126 @@ ext4_trim_all_free(struct super_block *sb, ext4_group_t group, return ret; } +#define EXT4_BALLOON_DISCARD_CHUNK (128U << 20) + +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) +{ + unsigned int gran = max_t(unsigned int, 1, + bdev_discard_granularity(sb->s_bdev) >> sb->s_blocksize_bits); + unsigned int chunk = roundup(EXT4_BALLOON_DISCARD_CHUNK >> + sb->s_blocksize_bits, gran); + ext4_fsblk_t last = pblk + len - 1, next; + int ret; + + if (!len) + return 0; + if (pblk < start) + pblk = start; + if (last > end) + last = end; + if (last < pblk || last - pblk + 1 < minblocks) + return 0; + + /* + * Chunks keep a long run interruptible. They end on multiples of the + * discard granularity, so no cluster below is split between two. + */ + while (pblk <= last) { + u64 rem = pblk; + + next = pblk + chunk - do_div(rem, chunk); + if (next > last + 1) + next = last + 1; + trace_ext4_discard_blocks(sb, pblk, next - pblk); + ret = sb_issue_discard(sb, pblk, next - pblk, GFP_NOFS, 0); + if (ret) + return ret; + pblk = next; + if (ext4_trim_interrupted()) + break; + cond_resched(); + } + + return 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) +{ + 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; + bool unwritten; + int ret, err; + + /* ext4_load_balloon() replaces s_balloon_ino under s_umount */ + down_read(&sb->s_umount); + if (sbi->s_balloon_ino) + inode = igrab(sbi->s_balloon_ino); + up_read(&sb->s_umount); + if (!inode) + return 0; + + /* + * Nobody may write the balloon meanwhile: data written into an + * unwritten extent would be discarded with it. + */ + inode_lock(inode); + inode_dio_wait(inode); + filemap_invalidate_lock(inode->i_mapping); + ret = filemap_write_and_wait(inode->i_mapping); + if (ret) + goto out; + + for (lblk = 0; lblk < EXT_MAX_BLOCKS; lblk += map.m_len) { + if (ext4_trim_interrupted()) + break; + cond_resched(); + + map.m_lblk = lblk; + map.m_len = EXT_MAX_BLOCKS - 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); + 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); +out: + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); + iput(inode); + + return ret; +} + /** * ext4_trim_fs() -- trim ioctl handle function * @sb: superblock for filesystem @@ -7028,6 +7148,7 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) ext4_group_t group, first_group, last_group; ext4_grpblk_t cnt = 0, first_cluster, last_cluster; uint64_t start, end, minlen, trimmed = 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 +7182,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 +7225,10 @@ 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)); + 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
