f2fs: support async discard based on v4.9

This patch is based on commit 275b66b09e85 (f2fs: support async discard).

Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
This commit is contained in:
Jaegeuk Kim 2017-01-11 16:41:25 -08:00
parent 7146292938
commit 373bb0247a
3 changed files with 181 additions and 12 deletions

View file

@ -1255,6 +1255,7 @@ int write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
f2fs_bug_on(sbi, prefree_segments(sbi)); f2fs_bug_on(sbi, prefree_segments(sbi));
flush_sit_entries(sbi, cpc); flush_sit_entries(sbi, cpc);
clear_prefree_segments(sbi, cpc); clear_prefree_segments(sbi, cpc);
f2fs_wait_all_discard_bio(sbi);
unblock_operations(sbi); unblock_operations(sbi);
goto out; goto out;
} }
@ -1273,10 +1274,12 @@ int write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
/* unlock all the fs_lock[] in do_checkpoint() */ /* unlock all the fs_lock[] in do_checkpoint() */
err = do_checkpoint(sbi, cpc); err = do_checkpoint(sbi, cpc);
if (err) if (err) {
release_discard_addrs(sbi); release_discard_addrs(sbi);
else } else {
clear_prefree_segments(sbi, cpc); clear_prefree_segments(sbi, cpc);
f2fs_wait_all_discard_bio(sbi);
}
unblock_operations(sbi); unblock_operations(sbi);
stat_inc_cp_count(sbi->stat_info); stat_inc_cp_count(sbi->stat_info);

View file

@ -127,7 +127,7 @@ static inline void bio_set_op_attrs(struct bio *bio, unsigned op,
static inline int wbc_to_write_flags(struct writeback_control *wbc) static inline int wbc_to_write_flags(struct writeback_control *wbc)
{ {
if (wbc->sync_mode == WB_SYNC_ALL) if (wbc->sync_mode == WB_SYNC_ALL)
return REQ_SYNC; return REQ_SYNC | REQ_NOIDLE;
return 0; return 0;
} }
@ -2174,6 +2174,7 @@ void destroy_flush_cmd_control(struct f2fs_sb_info *, bool);
void invalidate_blocks(struct f2fs_sb_info *, block_t); void invalidate_blocks(struct f2fs_sb_info *, block_t);
bool is_checkpointed_data(struct f2fs_sb_info *, block_t); bool is_checkpointed_data(struct f2fs_sb_info *, block_t);
void refresh_sit_entry(struct f2fs_sb_info *, block_t, block_t); void refresh_sit_entry(struct f2fs_sb_info *, block_t, block_t);
void f2fs_wait_all_discard_bio(struct f2fs_sb_info *);
void clear_prefree_segments(struct f2fs_sb_info *, struct cp_control *); void clear_prefree_segments(struct f2fs_sb_info *, struct cp_control *);
void release_discard_addrs(struct f2fs_sb_info *); void release_discard_addrs(struct f2fs_sb_info *);
int npages_for_summary_flush(struct f2fs_sb_info *, bool); int npages_for_summary_flush(struct f2fs_sb_info *, bool);

View file

@ -26,6 +26,7 @@
#define __reverse_ffz(x) __reverse_ffs(~(x)) #define __reverse_ffz(x) __reverse_ffs(~(x))
static struct kmem_cache *discard_entry_slab; static struct kmem_cache *discard_entry_slab;
static struct kmem_cache *bio_entry_slab;
static struct kmem_cache *sit_entry_set_slab; static struct kmem_cache *sit_entry_set_slab;
static struct kmem_cache *inmem_entry_slab; static struct kmem_cache *inmem_entry_slab;
@ -622,6 +623,162 @@ static void locate_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno)
mutex_unlock(&dirty_i->seglist_lock); mutex_unlock(&dirty_i->seglist_lock);
} }
static struct bio_entry *__add_bio_entry(struct f2fs_sb_info *sbi,
struct bio *bio)
{
struct list_head *wait_list = &(SM_I(sbi)->wait_list);
struct bio_entry *be = f2fs_kmem_cache_alloc(bio_entry_slab, GFP_NOFS);
INIT_LIST_HEAD(&be->list);
be->bio = bio;
init_completion(&be->event);
list_add_tail(&be->list, wait_list);
return be;
}
void f2fs_wait_all_discard_bio(struct f2fs_sb_info *sbi)
{
struct list_head *wait_list = &(SM_I(sbi)->wait_list);
struct bio_entry *be, *tmp;
list_for_each_entry_safe(be, tmp, wait_list, list) {
struct bio *bio = be->bio;
int err;
wait_for_completion_io(&be->event);
err = be->error;
if (err == -EOPNOTSUPP)
err = 0;
if (err)
f2fs_msg(sbi->sb, KERN_INFO,
"Issue discard failed, ret: %d", err);
bio_put(bio);
list_del(&be->list);
kmem_cache_free(bio_entry_slab, be);
}
}
static void f2fs_submit_bio_wait_endio(struct bio *bio)
{
struct bio_entry *be = (struct bio_entry *)bio->bi_private;
be->error = bio->bi_error;
complete(&be->event);
}
/* copied from block/blk-lib.c in 4.10-rc1 */
static int __blkdev_issue_discard(struct block_device *bdev, sector_t sector,
sector_t nr_sects, gfp_t gfp_mask, int flags,
struct bio **biop)
{
struct request_queue *q = bdev_get_queue(bdev);
struct bio *bio = *biop;
unsigned int granularity;
int op = REQ_WRITE | REQ_DISCARD;
int alignment;
sector_t bs_mask;
if (!q)
return -ENXIO;
if (!blk_queue_discard(q))
return -EOPNOTSUPP;
if (flags & BLKDEV_DISCARD_SECURE) {
if (!blk_queue_secdiscard(q))
return -EOPNOTSUPP;
op |= REQ_SECURE;
}
bs_mask = (bdev_logical_block_size(bdev) >> 9) - 1;
if ((sector | nr_sects) & bs_mask)
return -EINVAL;
/* Zero-sector (unknown) and one-sector granularities are the same. */
granularity = max(q->limits.discard_granularity >> 9, 1U);
alignment = (bdev_discard_alignment(bdev) >> 9) % granularity;
while (nr_sects) {
unsigned int req_sects;
sector_t end_sect, tmp;
/* Make sure bi_size doesn't overflow */
req_sects = min_t(sector_t, nr_sects, UINT_MAX >> 9);
/**
* If splitting a request, and the next starting sector would be
* misaligned, stop the discard at the previous aligned sector.
*/
end_sect = sector + req_sects;
tmp = end_sect;
if (req_sects < nr_sects &&
sector_div(tmp, granularity) != alignment) {
end_sect = end_sect - alignment;
sector_div(end_sect, granularity);
end_sect = end_sect * granularity + alignment;
req_sects = end_sect - sector;
}
if (bio) {
int ret = submit_bio_wait(0, bio);
bio_put(bio);
if (ret)
return ret;
}
bio = f2fs_bio_alloc(0);
bio->bi_iter.bi_sector = sector;
bio->bi_bdev = bdev;
bio_set_op_attrs(bio, op, 0);
bio->bi_iter.bi_size = req_sects << 9;
nr_sects -= req_sects;
sector = end_sect;
/*
* We can loop for a long time in here, if someone does
* full device discards (like mkfs). Be nice and allow
* us to schedule out to avoid softlocking if preempt
* is disabled.
*/
cond_resched();
}
*biop = bio;
return 0;
}
/* this function is copied from blkdev_issue_discard from block/blk-lib.c */
static int __f2fs_issue_discard_async(struct f2fs_sb_info *sbi,
struct block_device *bdev, block_t blkstart, block_t blklen)
{
struct bio *bio = NULL;
int err;
trace_f2fs_issue_discard(sbi->sb, blkstart, blklen);
if (sbi->s_ndevs) {
int devi = f2fs_target_device_index(sbi, blkstart);
blkstart -= FDEV(devi).start_blk;
}
err = __blkdev_issue_discard(bdev,
SECTOR_FROM_BLOCK(blkstart),
SECTOR_FROM_BLOCK(blklen),
GFP_NOFS, 0, &bio);
if (!err && bio) {
struct bio_entry *be = __add_bio_entry(sbi, bio);
bio->bi_private = be;
bio->bi_end_io = f2fs_submit_bio_wait_endio;
submit_bio(REQ_SYNC, bio);
}
return err;
}
#ifdef CONFIG_BLK_DEV_ZONED #ifdef CONFIG_BLK_DEV_ZONED
static int __f2fs_issue_discard_zone(struct f2fs_sb_info *sbi, static int __f2fs_issue_discard_zone(struct f2fs_sb_info *sbi,
struct block_device *bdev, block_t blkstart, block_t blklen) struct block_device *bdev, block_t blkstart, block_t blklen)
@ -655,8 +812,7 @@ static int __f2fs_issue_discard_zone(struct f2fs_sb_info *sbi,
case BLK_ZONE_TYPE_CONVENTIONAL: case BLK_ZONE_TYPE_CONVENTIONAL:
if (!blk_queue_discard(bdev_get_queue(bdev))) if (!blk_queue_discard(bdev_get_queue(bdev)))
return 0; return 0;
return blkdev_issue_discard(bdev, sector, nr_sects, return __f2fs_issue_discard_async(sbi, bdev, blkstart, blklen);
GFP_NOFS, 0);
case BLK_ZONE_TYPE_SEQWRITE_REQ: case BLK_ZONE_TYPE_SEQWRITE_REQ:
case BLK_ZONE_TYPE_SEQWRITE_PREF: case BLK_ZONE_TYPE_SEQWRITE_PREF:
trace_f2fs_issue_reset_zone(sbi->sb, blkstart); trace_f2fs_issue_reset_zone(sbi->sb, blkstart);
@ -672,15 +828,12 @@ static int __f2fs_issue_discard_zone(struct f2fs_sb_info *sbi,
static int __issue_discard_async(struct f2fs_sb_info *sbi, static int __issue_discard_async(struct f2fs_sb_info *sbi,
struct block_device *bdev, block_t blkstart, block_t blklen) struct block_device *bdev, block_t blkstart, block_t blklen)
{ {
sector_t start = SECTOR_FROM_BLOCK(blkstart);
sector_t len = SECTOR_FROM_BLOCK(blklen);
#ifdef CONFIG_BLK_DEV_ZONED #ifdef CONFIG_BLK_DEV_ZONED
if (f2fs_sb_mounted_blkzoned(sbi->sb) && if (f2fs_sb_mounted_blkzoned(sbi->sb) &&
bdev_zoned_model(bdev) != BLK_ZONED_NONE) bdev_zoned_model(bdev) != BLK_ZONED_NONE)
return __f2fs_issue_discard_zone(sbi, bdev, blkstart, blklen); return __f2fs_issue_discard_zone(sbi, bdev, blkstart, blklen);
#endif #endif
return blkdev_issue_discard(bdev, start, len, GFP_NOFS, 0); return __f2fs_issue_discard_async(sbi, bdev, blkstart, blklen);
} }
static int f2fs_issue_discard(struct f2fs_sb_info *sbi, static int f2fs_issue_discard(struct f2fs_sb_info *sbi,
@ -720,8 +873,6 @@ static int f2fs_issue_discard(struct f2fs_sb_info *sbi,
if (len) if (len)
err = __issue_discard_async(sbi, bdev, start, len); err = __issue_discard_async(sbi, bdev, start, len);
trace_f2fs_issue_discard(sbi->sb, blkstart, blklen);
return err; return err;
} }
@ -822,11 +973,14 @@ void clear_prefree_segments(struct f2fs_sb_info *sbi, struct cp_control *cpc)
struct list_head *head = &(SM_I(sbi)->discard_list); struct list_head *head = &(SM_I(sbi)->discard_list);
struct discard_entry *entry, *this; struct discard_entry *entry, *this;
struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
struct blk_plug plug;
unsigned long *prefree_map = dirty_i->dirty_segmap[PRE]; unsigned long *prefree_map = dirty_i->dirty_segmap[PRE];
unsigned int start = 0, end = -1; unsigned int start = 0, end = -1;
unsigned int secno, start_segno; unsigned int secno, start_segno;
bool force = (cpc->reason == CP_DISCARD); bool force = (cpc->reason == CP_DISCARD);
blk_start_plug(&plug);
mutex_lock(&dirty_i->seglist_lock); mutex_lock(&dirty_i->seglist_lock);
while (1) { while (1) {
@ -875,6 +1029,8 @@ skip:
SM_I(sbi)->nr_discards -= entry->len; SM_I(sbi)->nr_discards -= entry->len;
kmem_cache_free(discard_entry_slab, entry); kmem_cache_free(discard_entry_slab, entry);
} }
blk_finish_plug(&plug);
} }
static bool __mark_sit_entry_dirty(struct f2fs_sb_info *sbi, unsigned int segno) static bool __mark_sit_entry_dirty(struct f2fs_sb_info *sbi, unsigned int segno)
@ -2551,6 +2707,7 @@ int build_segment_manager(struct f2fs_sb_info *sbi)
sm_info->min_fsync_blocks = DEF_MIN_FSYNC_BLOCKS; sm_info->min_fsync_blocks = DEF_MIN_FSYNC_BLOCKS;
INIT_LIST_HEAD(&sm_info->discard_list); INIT_LIST_HEAD(&sm_info->discard_list);
INIT_LIST_HEAD(&sm_info->wait_list);
sm_info->nr_discards = 0; sm_info->nr_discards = 0;
sm_info->max_discards = 0; sm_info->max_discards = 0;
@ -2694,10 +2851,15 @@ int __init create_segment_manager_caches(void)
if (!discard_entry_slab) if (!discard_entry_slab)
goto fail; goto fail;
bio_entry_slab = f2fs_kmem_cache_create("bio_entry",
sizeof(struct bio_entry));
if (!bio_entry_slab)
goto destroy_discard_entry;
sit_entry_set_slab = f2fs_kmem_cache_create("sit_entry_set", sit_entry_set_slab = f2fs_kmem_cache_create("sit_entry_set",
sizeof(struct sit_entry_set)); sizeof(struct sit_entry_set));
if (!sit_entry_set_slab) if (!sit_entry_set_slab)
goto destroy_discard_entry; goto destroy_bio_entry;
inmem_entry_slab = f2fs_kmem_cache_create("inmem_page_entry", inmem_entry_slab = f2fs_kmem_cache_create("inmem_page_entry",
sizeof(struct inmem_pages)); sizeof(struct inmem_pages));
@ -2707,6 +2869,8 @@ int __init create_segment_manager_caches(void)
destroy_sit_entry_set: destroy_sit_entry_set:
kmem_cache_destroy(sit_entry_set_slab); kmem_cache_destroy(sit_entry_set_slab);
destroy_bio_entry:
kmem_cache_destroy(bio_entry_slab);
destroy_discard_entry: destroy_discard_entry:
kmem_cache_destroy(discard_entry_slab); kmem_cache_destroy(discard_entry_slab);
fail: fail:
@ -2716,6 +2880,7 @@ fail:
void destroy_segment_manager_caches(void) void destroy_segment_manager_caches(void)
{ {
kmem_cache_destroy(sit_entry_set_slab); kmem_cache_destroy(sit_entry_set_slab);
kmem_cache_destroy(bio_entry_slab);
kmem_cache_destroy(discard_entry_slab); kmem_cache_destroy(discard_entry_slab);
kmem_cache_destroy(inmem_entry_slab); kmem_cache_destroy(inmem_entry_slab);
} }