diff options
| author | Park Ju Hyung <qkrwngud825@gmail.com> | 2017-09-26 16:37:08 +0900 |
|---|---|---|
| committer | Moyster <oysterized@gmail.com> | 2018-05-23 19:11:36 +0200 |
| commit | bd1785f9c33da7edc0e50606e13ccf18be860eb6 (patch) | |
| tree | 6b7ad1c9855d31bbcc9a89e71d15c029ed98608e | |
| parent | 57a20b86b1279bbeb7ad5b19af18ac0fcd2f839c (diff) | |
f2fs: rapidly GC all invalid blocks when screen goes off & plugged in
Rapidly GC'ing all invalid blocks only takes a few minute,
even under extreme scenario.
Wait until the screen goes off and detect if the power source is plugged in.
If it is, GC thread runs repeatedly with 1ms interval.
Since the device can go to sleep even with power source plugged in,
catch an additional wakelock.
When no more GC victim block is found, GC thread sleeps for
gc_no_gc_sleep_time(5m).
With the previous ioprio patch, the impact to system responsiveness
(e.g. waking up the screen) is minimal, if not zero.
This patch is intended to optimize background GC behavior specific to Android.
So, disable background_gc by default as well.
Signed-off-by: Park Ju Hyung <qkrwngud825@gmail.com>
| -rw-r--r-- | fs/f2fs/f2fs.h | 6 | ||||
| -rw-r--r-- | fs/f2fs/gc.c | 145 | ||||
| -rw-r--r-- | fs/f2fs/gc.h | 3 | ||||
| -rw-r--r-- | fs/f2fs/super.c | 5 |
4 files changed, 151 insertions, 8 deletions
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index a44a589ec..8d297b554 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1256,6 +1256,7 @@ struct f2fs_sb_info { char *s_qf_names[F2FS_MAXQUOTAS]; int s_jquota_fmt; /* Format of quota to use */ #endif + struct list_head list; }; #ifdef CONFIG_F2FS_FAULT_INJECTION @@ -2806,6 +2807,11 @@ int f2fs_migrate_page(struct address_space *mapping, struct page *newpage, */ int start_gc_thread(struct f2fs_sb_info *sbi); void stop_gc_thread(struct f2fs_sb_info *sbi); +void start_all_gc_threads(void); +void stop_all_gc_threads(void); +void f2fs_sbi_list_add(struct f2fs_sb_info *sbi); +void f2fs_sbi_list_del(struct f2fs_sb_info *sbi); + block_t start_bidx_of_node(unsigned int node_ofs, struct inode *inode); int f2fs_gc(struct f2fs_sb_info *sbi, bool sync, bool background, unsigned int segno); diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 2ad858dd6..917bff037 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -16,6 +16,9 @@ #include <linux/kthread.h> #include <linux/delay.h> #include <linux/freezer.h> +#include <linux/fb.h> +#include <linux/power_supply.h> +#include <linux/wakelock.h> #include "f2fs.h" #include "node.h" @@ -23,14 +26,33 @@ #include "gc.h" #include <trace/events/f2fs.h> +#define TRIGGER_SOFF (!screen_on && power_supply_is_system_supplied()) +static bool screen_on = true; +// Use 1 instead of 0 to allow thread interrupts +static unsigned int soff_wait_ms = 1; + +static inline void gc_set_wakelock(struct f2fs_gc_kthread *gc_th, bool val) +{ + if (val) { + if (!wake_lock_active(&gc_th->gc_wakelock)) { + pr_info("f2fs: catching wakelock for GC\n"); + wake_lock(&gc_th->gc_wakelock); + } + } else { + if (wake_lock_active(&gc_th->gc_wakelock)) { + pr_info("f2fs: unlocking wakelock for GC\n"); + wake_unlock(&gc_th->gc_wakelock); + } + } +} + static int gc_thread_func(void *data) { struct f2fs_sb_info *sbi = data; struct f2fs_gc_kthread *gc_th = sbi->gc_thread; wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head; - unsigned int wait_ms; - - wait_ms = gc_th->min_sleep_time; + unsigned int wait_ms = gc_th->min_sleep_time; + bool force_gc; set_freezable(); do { @@ -39,6 +61,17 @@ static int gc_thread_func(void *data) gc_th->gc_wake, msecs_to_jiffies(wait_ms)); + force_gc = TRIGGER_SOFF; + if (force_gc) { + gc_set_wakelock(gc_th, true); + wait_ms = soff_wait_ms; + gc_th->gc_urgent = 1; + } else { + gc_set_wakelock(gc_th, false); + wait_ms = gc_th->min_sleep_time; + gc_th->gc_urgent = 0; + } + /* give it a try one time */ if (gc_th->gc_wake) gc_th->gc_wake = 0; @@ -49,7 +82,8 @@ static int gc_thread_func(void *data) break; if (sbi->sb->s_writers.frozen >= SB_FREEZE_WRITE) { - increase_sleep_time(gc_th, &wait_ms); + if (!force_gc) + increase_sleep_time(gc_th, &wait_ms); continue; } @@ -79,6 +113,9 @@ static int gc_thread_func(void *data) if (!mutex_trylock(&sbi->gc_mutex)) goto next; + if (force_gc) + goto do_gc; + if (gc_th->gc_urgent) { wait_ms = gc_th->urgent_sleep_time; goto do_gc; @@ -98,8 +135,11 @@ do_gc: stat_inc_bggc_count(sbi); /* if return value is not zero, no victim was selected */ - if (f2fs_gc(sbi, test_opt(sbi, FORCE_FG_GC), true, NULL_SEGNO)) + if (f2fs_gc(sbi, test_opt(sbi, FORCE_FG_GC), true, NULL_SEGNO)) { wait_ms = gc_th->no_gc_sleep_time; + gc_set_wakelock(gc_th, false); + pr_info("f2fs: no more GC victim found, sleeping for %u ms\n", wait_ms); + } trace_f2fs_background_gc(sbi->sb, wait_ms, prefree_segments(sbi), free_segments(sbi)); @@ -118,6 +158,10 @@ int start_gc_thread(struct f2fs_sb_info *sbi) struct f2fs_gc_kthread *gc_th; dev_t dev = sbi->sb->s_bdev->bd_dev; int err = 0; + char buf[25]; + + if (sbi->gc_thread != NULL) + goto out; gc_th = f2fs_kmalloc(sbi, sizeof(struct f2fs_gc_kthread), GFP_KERNEL); if (!gc_th) { @@ -134,10 +178,13 @@ int start_gc_thread(struct f2fs_sb_info *sbi) gc_th->gc_urgent = 0; gc_th->gc_wake= 0; + snprintf(buf, sizeof(buf), "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev)); + + wake_lock_init(&gc_th->gc_wakelock, WAKE_LOCK_SUSPEND, buf); + sbi->gc_thread = gc_th; init_waitqueue_head(&sbi->gc_thread->gc_wait_queue_head); - sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi, - "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev)); + sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi, buf); if (IS_ERR(gc_th->f2fs_gc_task)) { err = PTR_ERR(gc_th->f2fs_gc_task); kfree(gc_th); @@ -155,10 +202,94 @@ void stop_gc_thread(struct f2fs_sb_info *sbi) if (!gc_th) return; kthread_stop(gc_th->f2fs_gc_task); + wake_lock_destroy(&gc_th->gc_wakelock); kfree(gc_th); sbi->gc_thread = NULL; } +static LIST_HEAD(f2fs_sbi_list); +static DEFINE_MUTEX(f2fs_sbi_mutex); + +void start_all_gc_threads(void) +{ + struct f2fs_sb_info *sbi; + + mutex_lock(&f2fs_sbi_mutex); + list_for_each_entry(sbi, &f2fs_sbi_list, list) { + start_gc_thread(sbi); + sbi->gc_thread->gc_wake = 1; + wake_up_interruptible_all(&sbi->gc_thread->gc_wait_queue_head); + } + mutex_unlock(&f2fs_sbi_mutex); +} + +void stop_all_gc_threads(void) +{ + struct f2fs_sb_info *sbi; + + mutex_lock(&f2fs_sbi_mutex); + list_for_each_entry(sbi, &f2fs_sbi_list, list) { + stop_gc_thread(sbi); + } + mutex_unlock(&f2fs_sbi_mutex); +} + +void f2fs_sbi_list_add(struct f2fs_sb_info *sbi) +{ + mutex_lock(&f2fs_sbi_mutex); + list_add_tail(&sbi->list, &f2fs_sbi_list); + mutex_unlock(&f2fs_sbi_mutex); +} + +void f2fs_sbi_list_del(struct f2fs_sb_info *sbi) +{ + mutex_lock(&f2fs_sbi_mutex); + list_del(&sbi->list); + mutex_unlock(&f2fs_sbi_mutex); +} + +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + + if ((event == FB_EVENT_BLANK) && evdata && evdata->data) { + blank = evdata->data; + + switch (*blank) { + case FB_BLANK_POWERDOWN: + screen_on = false; + /* + * Start all GC threads exclusively from here + * since the phone screen would turn on when + * a charger is connected + */ + if (TRIGGER_SOFF) + start_all_gc_threads(); + break; + case FB_BLANK_UNBLANK: + screen_on = true; + stop_all_gc_threads(); + break; + } + } + + return 0; +} + +static struct notifier_block fb_notifier_block = { + .notifier_call = fb_notifier_callback, +}; + +static int __init f2fs_gc_register_fb(void) +{ + fb_register_client(&fb_notifier_block); + + return 0; +} +late_initcall(f2fs_gc_register_fb); + static int select_gc_type(struct f2fs_gc_kthread *gc_th, int gc_type) { int gc_mode = (gc_type == BG_GC) ? GC_CB : GC_GREEDY; diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h index 9325191fa..adecfcd07 100644 --- a/fs/f2fs/gc.h +++ b/fs/f2fs/gc.h @@ -8,6 +8,8 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#include <linux/wakelock.h> + #define GC_THREAD_MIN_WB_PAGES 1 /* * a threshold to determine * whether IO subsystem is idle @@ -26,6 +28,7 @@ struct f2fs_gc_kthread { struct task_struct *f2fs_gc_task; wait_queue_head_t gc_wait_queue_head; + struct wake_lock gc_wakelock; /* for gc sleep time */ unsigned int urgent_sleep_time; diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index c6c3161d4..a310c85e2 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -826,6 +826,7 @@ static void f2fs_put_super(struct super_block *sb) /* write_checkpoint can update stat informaion */ f2fs_destroy_stats(sbi); + f2fs_sbi_list_del(sbi); /* * normally superblock is clean, so we need to release this. @@ -1124,7 +1125,6 @@ static void default_options(struct f2fs_sb_info *sbi) /* init some FS parameters */ sbi->active_logs = NR_CURSEG_TYPE; - set_opt(sbi, BG_GC); set_opt(sbi, INLINE_XATTR); set_opt(sbi, INLINE_DATA); set_opt(sbi, INLINE_DENTRY); @@ -2482,6 +2482,8 @@ try_onemore: if (err) goto free_nm; + f2fs_sbi_list_add(sbi); + /* read root inode and dentry */ root = f2fs_iget(sb, F2FS_ROOT_INO(sbi)); if (IS_ERR(root)) { @@ -2596,6 +2598,7 @@ free_node_inode: f2fs_leave_shrinker(sbi); iput(sbi->node_inode); mutex_unlock(&sbi->umount_mutex); + f2fs_sbi_list_del(sbi); f2fs_destroy_stats(sbi); free_nm: destroy_node_manager(sbi); |
