aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4
diff options
context:
space:
mode:
authorLukas Czerner <lczerner@redhat.com>2010-10-27 21:30:12 -0400
committerTheodore Ts'o <tytso@mit.edu>2010-10-27 21:30:12 -0400
commit7360d1731e5dc78aec867e65e55f9fb58782b5fe (patch)
tree2cc0d139ec129c19150ef481d6adb9b0fd947c89 /fs/ext4
parent367a51a339020ba4d9edb0ce0f21d65bd50b00c9 (diff)
ext4: Add batched discard support for ext4
Walk through allocation groups and trim all free extents. It can be invoked through FITRIM ioctl on the file system. The main idea is to provide a way to trim the whole file system if needed, since some SSD's may suffer from performance loss after the whole device was filled (it does not mean that fs is full!). It search for free extents in allocation groups specified by Byte range start -> start+len. When the free extent is within this range, blocks are marked as used and then trimmed. Afterwards these blocks are marked as free in per-group bitmap. Since fstrim is a long operation it is good to have an ability to interrupt it by a signal. This was added by Dmitry Monakhov. Thanks Dimitry. Signed-off-by: Lukas Czerner <lczerner@redhat.com> Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Dmitry Monakhov <dmonakhov@openvz.org> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4')
-rw-r--r--fs/ext4/ext4.h2
-rw-r--r--fs/ext4/mballoc.c185
-rw-r--r--fs/ext4/super.c1
3 files changed, 188 insertions, 0 deletions
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index ca9fda64dd4f..98dde2b7dd6f 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1694,6 +1694,8 @@ extern int ext4_mb_add_groupinfo(struct super_block *sb,
1694extern int ext4_mb_get_buddy_cache_lock(struct super_block *, ext4_group_t); 1694extern int ext4_mb_get_buddy_cache_lock(struct super_block *, ext4_group_t);
1695extern void ext4_mb_put_buddy_cache_lock(struct super_block *, 1695extern void ext4_mb_put_buddy_cache_lock(struct super_block *,
1696 ext4_group_t, int); 1696 ext4_group_t, int);
1697extern int ext4_trim_fs(struct super_block *, struct fstrim_range *);
1698
1697/* inode.c */ 1699/* inode.c */
1698struct buffer_head *ext4_getblk(handle_t *, struct inode *, 1700struct buffer_head *ext4_getblk(handle_t *, struct inode *,
1699 ext4_lblk_t, int, int *); 1701 ext4_lblk_t, int, int *);
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index 11c2eec386ef..e3bcc06b4906 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4685,3 +4685,188 @@ error_return:
4685 ext4_std_error(sb, err); 4685 ext4_std_error(sb, err);
4686 return; 4686 return;
4687} 4687}
4688
4689/**
4690 * ext4_trim_extent -- function to TRIM one single free extent in the group
4691 * @sb: super block for the file system
4692 * @start: starting block of the free extent in the alloc. group
4693 * @count: number of blocks to TRIM
4694 * @group: alloc. group we are working with
4695 * @e4b: ext4 buddy for the group
4696 *
4697 * Trim "count" blocks starting at "start" in the "group". To assure that no
4698 * one will allocate those blocks, mark it as used in buddy bitmap. This must
4699 * be called with under the group lock.
4700 */
4701static int ext4_trim_extent(struct super_block *sb, int start, int count,
4702 ext4_group_t group, struct ext4_buddy *e4b)
4703{
4704 struct ext4_free_extent ex;
4705 int ret = 0;
4706
4707 assert_spin_locked(ext4_group_lock_ptr(sb, group));
4708
4709 ex.fe_start = start;
4710 ex.fe_group = group;
4711 ex.fe_len = count;
4712
4713 /*
4714 * Mark blocks used, so no one can reuse them while
4715 * being trimmed.
4716 */
4717 mb_mark_used(e4b, &ex);
4718 ext4_unlock_group(sb, group);
4719
4720 ret = ext4_issue_discard(sb, group, start, count);
4721 if (ret)
4722 ext4_std_error(sb, ret);
4723
4724 ext4_lock_group(sb, group);
4725 mb_free_blocks(NULL, e4b, start, ex.fe_len);
4726 return ret;
4727}
4728
4729/**
4730 * ext4_trim_all_free -- function to trim all free space in alloc. group
4731 * @sb: super block for file system
4732 * @e4b: ext4 buddy
4733 * @start: first group block to examine
4734 * @max: last group block to examine
4735 * @minblocks: minimum extent block count
4736 *
4737 * ext4_trim_all_free walks through group's buddy bitmap searching for free
4738 * extents. When the free block is found, ext4_trim_extent is called to TRIM
4739 * the extent.
4740 *
4741 *
4742 * ext4_trim_all_free walks through group's block bitmap searching for free
4743 * extents. When the free extent is found, mark it as used in group buddy
4744 * bitmap. Then issue a TRIM command on this extent and free the extent in
4745 * the group buddy bitmap. This is done until whole group is scanned.
4746 */
4747ext4_grpblk_t ext4_trim_all_free(struct super_block *sb, struct ext4_buddy *e4b,
4748 ext4_grpblk_t start, ext4_grpblk_t max, ext4_grpblk_t minblocks)
4749{
4750 void *bitmap;
4751 ext4_grpblk_t next, count = 0;
4752 ext4_group_t group;
4753 int ret = 0;
4754
4755 BUG_ON(e4b == NULL);
4756
4757 bitmap = e4b->bd_bitmap;
4758 group = e4b->bd_group;
4759 start = (e4b->bd_info->bb_first_free > start) ?
4760 e4b->bd_info->bb_first_free : start;
4761 ext4_lock_group(sb, group);
4762
4763 while (start < max) {
4764 start = mb_find_next_zero_bit(bitmap, max, start);
4765 if (start >= max)
4766 break;
4767 next = mb_find_next_bit(bitmap, max, start);
4768
4769 if ((next - start) >= minblocks) {
4770 ret = ext4_trim_extent(sb, start,
4771 next - start, group, e4b);
4772 if (ret < 0)
4773 break;
4774 count += next - start;
4775 }
4776 start = next + 1;
4777
4778 if (fatal_signal_pending(current)) {
4779 count = -ERESTARTSYS;
4780 break;
4781 }
4782
4783 if (need_resched()) {
4784 ext4_unlock_group(sb, group);
4785 cond_resched();
4786 ext4_lock_group(sb, group);
4787 }
4788
4789 if ((e4b->bd_info->bb_free - count) < minblocks)
4790 break;
4791 }
4792 ext4_unlock_group(sb, group);
4793
4794 ext4_debug("trimmed %d blocks in the group %d\n",
4795 count, group);
4796
4797 if (ret < 0)
4798 count = ret;
4799
4800 return count;
4801}
4802
4803/**
4804 * ext4_trim_fs() -- trim ioctl handle function
4805 * @sb: superblock for filesystem
4806 * @range: fstrim_range structure
4807 *
4808 * start: First Byte to trim
4809 * len: number of Bytes to trim from start
4810 * minlen: minimum extent length in Bytes
4811 * ext4_trim_fs goes through all allocation groups containing Bytes from
4812 * start to start+len. For each such a group ext4_trim_all_free function
4813 * is invoked to trim all free space.
4814 */
4815int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range)
4816{
4817 struct ext4_buddy e4b;
4818 ext4_group_t first_group, last_group;
4819 ext4_group_t group, ngroups = ext4_get_groups_count(sb);
4820 ext4_grpblk_t cnt = 0, first_block, last_block;
4821 uint64_t start, len, minlen, trimmed;
4822 int ret = 0;
4823
4824 start = range->start >> sb->s_blocksize_bits;
4825 len = range->len >> sb->s_blocksize_bits;
4826 minlen = range->minlen >> sb->s_blocksize_bits;
4827 trimmed = 0;
4828
4829 if (unlikely(minlen > EXT4_BLOCKS_PER_GROUP(sb)))
4830 return -EINVAL;
4831
4832 /* Determine first and last group to examine based on start and len */
4833 ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) start,
4834 &first_group, &first_block);
4835 ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) (start + len),
4836 &last_group, &last_block);
4837 last_group = (last_group > ngroups - 1) ? ngroups - 1 : last_group;
4838 last_block = EXT4_BLOCKS_PER_GROUP(sb);
4839
4840 if (first_group > last_group)
4841 return -EINVAL;
4842
4843 for (group = first_group; group <= last_group; group++) {
4844 ret = ext4_mb_load_buddy(sb, group, &e4b);
4845 if (ret) {
4846 ext4_error(sb, "Error in loading buddy "
4847 "information for %u", group);
4848 break;
4849 }
4850
4851 if (len >= EXT4_BLOCKS_PER_GROUP(sb))
4852 len -= (EXT4_BLOCKS_PER_GROUP(sb) - first_block);
4853 else
4854 last_block = len;
4855
4856 if (e4b.bd_info->bb_free >= minlen) {
4857 cnt = ext4_trim_all_free(sb, &e4b, first_block,
4858 last_block, minlen);
4859 if (cnt < 0) {
4860 ret = cnt;
4861 ext4_mb_unload_buddy(&e4b);
4862 break;
4863 }
4864 }
4865 ext4_mb_unload_buddy(&e4b);
4866 trimmed += cnt;
4867 first_block = 0;
4868 }
4869 range->len = trimmed * sb->s_blocksize;
4870
4871 return ret;
4872}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index e13b3c3534d7..01e60aa6c478 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1189,6 +1189,7 @@ static const struct super_operations ext4_sops = {
1189 .quota_write = ext4_quota_write, 1189 .quota_write = ext4_quota_write,
1190#endif 1190#endif
1191 .bdev_try_to_free_page = bdev_try_to_free_page, 1191 .bdev_try_to_free_page = bdev_try_to_free_page,
1192 .trim_fs = ext4_trim_fs
1192}; 1193};
1193 1194
1194static const struct super_operations ext4_nojournal_sops = { 1195static const struct super_operations ext4_nojournal_sops = {