From 5ac048bb7ea6e87b06504b999017cfa1f38f4092 Mon Sep 17 00:00:00 2001 From: Steven Whitehouse Date: Wed, 30 Mar 2011 16:25:51 +0100 Subject: GFS2: Use filemap_fdatawrite() to write back the AIL In order to ensure that the mapping stats (and thus the bdi) are correctly updated, this patch changes the AIL writeback to use the filemap_datawrite function. This helps prevent stalls in balance_dirty_pages() due to large amounts of dirty metadata when there is little or no dirty data around. Signed-off-by: Steven Whitehouse --- fs/gfs2/log.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'fs/gfs2/log.c') diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index 5b102c1887fd..3ebafa1efad0 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -91,6 +91,7 @@ static void gfs2_ail1_start_one(struct gfs2_sbd *sdp, struct gfs2_ail *ai) __releases(&sdp->sd_ail_lock) __acquires(&sdp->sd_ail_lock) { + struct gfs2_glock *gl = NULL; struct gfs2_bufdata *bd, *s; struct buffer_head *bh; int retry; @@ -113,19 +114,13 @@ __acquires(&sdp->sd_ail_lock) if (!buffer_dirty(bh)) continue; - + if (gl == bd->bd_gl) + continue; + gl = bd->bd_gl; list_move(&bd->bd_ail_st_list, &ai->ai_ail1_list); - get_bh(bh); spin_unlock(&sdp->sd_ail_lock); - lock_buffer(bh); - if (test_clear_buffer_dirty(bh)) { - bh->b_end_io = end_buffer_write_sync; - submit_bh(WRITE_SYNC, bh); - } else { - unlock_buffer(bh); - brelse(bh); - } + filemap_fdatawrite(gfs2_glock2aspace(gl)); spin_lock(&sdp->sd_ail_lock); retry = 1; -- cgit v1.2.3 From 4667a0ec32867865fd4deccf834594b3ea831baf Mon Sep 17 00:00:00 2001 From: Steven Whitehouse Date: Mon, 18 Apr 2011 14:18:09 +0100 Subject: GFS2: Make writeback more responsive to system conditions This patch adds writeback_control to writing back the AIL list. This means that we can then take advantage of the information we get in ->write_inode() in order to set off some pre-emptive writeback. In addition, the AIL code is cleaned up a bit to make it a bit simpler to understand. There is still more which can usefully be done in this area, but this is a good start at least. Signed-off-by: Steven Whitehouse --- fs/gfs2/log.c | 165 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 85 insertions(+), 80 deletions(-) (limited to 'fs/gfs2/log.c') diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index 3ebafa1efad0..03e00417061b 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "gfs2.h" #include "incore.h" @@ -83,50 +84,90 @@ void gfs2_remove_from_ail(struct gfs2_bufdata *bd) /** * gfs2_ail1_start_one - Start I/O on a part of the AIL * @sdp: the filesystem - * @tr: the part of the AIL + * @wbc: The writeback control structure + * @ai: The ail structure * */ -static void gfs2_ail1_start_one(struct gfs2_sbd *sdp, struct gfs2_ail *ai) +static void gfs2_ail1_start_one(struct gfs2_sbd *sdp, + struct writeback_control *wbc, + struct gfs2_ail *ai) __releases(&sdp->sd_ail_lock) __acquires(&sdp->sd_ail_lock) { struct gfs2_glock *gl = NULL; + struct address_space *mapping; struct gfs2_bufdata *bd, *s; struct buffer_head *bh; - int retry; - do { - retry = 0; +restart: + list_for_each_entry_safe_reverse(bd, s, &ai->ai_ail1_list, bd_ail_st_list) { + bh = bd->bd_bh; - list_for_each_entry_safe_reverse(bd, s, &ai->ai_ail1_list, - bd_ail_st_list) { - bh = bd->bd_bh; + gfs2_assert(sdp, bd->bd_ail == ai); - gfs2_assert(sdp, bd->bd_ail == ai); + if (!buffer_busy(bh)) { + if (!buffer_uptodate(bh)) + gfs2_io_error_bh(sdp, bh); + list_move(&bd->bd_ail_st_list, &ai->ai_ail2_list); + continue; + } + + if (!buffer_dirty(bh)) + continue; + if (gl == bd->bd_gl) + continue; + gl = bd->bd_gl; + list_move(&bd->bd_ail_st_list, &ai->ai_ail1_list); + mapping = bh->b_page->mapping; + spin_unlock(&sdp->sd_ail_lock); + generic_writepages(mapping, wbc); + spin_lock(&sdp->sd_ail_lock); + if (wbc->nr_to_write <= 0) + break; + goto restart; + } +} - if (!buffer_busy(bh)) { - if (!buffer_uptodate(bh)) - gfs2_io_error_bh(sdp, bh); - list_move(&bd->bd_ail_st_list, &ai->ai_ail2_list); - continue; - } - if (!buffer_dirty(bh)) - continue; - if (gl == bd->bd_gl) - continue; - gl = bd->bd_gl; - list_move(&bd->bd_ail_st_list, &ai->ai_ail1_list); +/** + * gfs2_ail1_flush - start writeback of some ail1 entries + * @sdp: The super block + * @wbc: The writeback control structure + * + * Writes back some ail1 entries, according to the limits in the + * writeback control structure + */ - spin_unlock(&sdp->sd_ail_lock); - filemap_fdatawrite(gfs2_glock2aspace(gl)); - spin_lock(&sdp->sd_ail_lock); +void gfs2_ail1_flush(struct gfs2_sbd *sdp, struct writeback_control *wbc) +{ + struct list_head *head = &sdp->sd_ail1_list; + struct gfs2_ail *ai; - retry = 1; + spin_lock(&sdp->sd_ail_lock); + list_for_each_entry_reverse(ai, head, ai_list) { + if (wbc->nr_to_write <= 0) break; - } - } while (retry); + gfs2_ail1_start_one(sdp, wbc, ai); /* This may drop ail lock */ + } + spin_unlock(&sdp->sd_ail_lock); +} + +/** + * gfs2_ail1_start - start writeback of all ail1 entries + * @sdp: The superblock + */ + +static void gfs2_ail1_start(struct gfs2_sbd *sdp) +{ + struct writeback_control wbc = { + .sync_mode = WB_SYNC_NONE, + .nr_to_write = LONG_MAX, + .range_start = 0, + .range_end = LLONG_MAX, + }; + + return gfs2_ail1_flush(sdp, &wbc); } /** @@ -136,7 +177,7 @@ __acquires(&sdp->sd_ail_lock) * */ -static int gfs2_ail1_empty_one(struct gfs2_sbd *sdp, struct gfs2_ail *ai, int flags) +static void gfs2_ail1_empty_one(struct gfs2_sbd *sdp, struct gfs2_ail *ai) { struct gfs2_bufdata *bd, *s; struct buffer_head *bh; @@ -144,71 +185,37 @@ static int gfs2_ail1_empty_one(struct gfs2_sbd *sdp, struct gfs2_ail *ai, int fl list_for_each_entry_safe_reverse(bd, s, &ai->ai_ail1_list, bd_ail_st_list) { bh = bd->bd_bh; - gfs2_assert(sdp, bd->bd_ail == ai); - - if (buffer_busy(bh)) { - if (flags & DIO_ALL) - continue; - else - break; - } - + if (buffer_busy(bh)) + continue; if (!buffer_uptodate(bh)) gfs2_io_error_bh(sdp, bh); - list_move(&bd->bd_ail_st_list, &ai->ai_ail2_list); } - return list_empty(&ai->ai_ail1_list); } -static void gfs2_ail1_start(struct gfs2_sbd *sdp) -{ - struct list_head *head; - u64 sync_gen; - struct gfs2_ail *ai; - int done = 0; - - spin_lock(&sdp->sd_ail_lock); - head = &sdp->sd_ail1_list; - if (list_empty(head)) { - spin_unlock(&sdp->sd_ail_lock); - return; - } - sync_gen = sdp->sd_ail_sync_gen++; - - while(!done) { - done = 1; - list_for_each_entry_reverse(ai, head, ai_list) { - if (ai->ai_sync_gen >= sync_gen) - continue; - ai->ai_sync_gen = sync_gen; - gfs2_ail1_start_one(sdp, ai); /* This may drop ail lock */ - done = 0; - break; - } - } - - spin_unlock(&sdp->sd_ail_lock); -} +/** + * gfs2_ail1_empty - Try to empty the ail1 lists + * @sdp: The superblock + * + * Tries to empty the ail1 lists, starting with the oldest first + */ -static int gfs2_ail1_empty(struct gfs2_sbd *sdp, int flags) +static int gfs2_ail1_empty(struct gfs2_sbd *sdp) { struct gfs2_ail *ai, *s; int ret; spin_lock(&sdp->sd_ail_lock); - list_for_each_entry_safe_reverse(ai, s, &sdp->sd_ail1_list, ai_list) { - if (gfs2_ail1_empty_one(sdp, ai, flags)) + gfs2_ail1_empty_one(sdp, ai); + if (list_empty(&ai->ai_ail1_list)) list_move(&ai->ai_list, &sdp->sd_ail2_list); - else if (!(flags & DIO_ALL)) + else break; } - ret = list_empty(&sdp->sd_ail1_list); - spin_unlock(&sdp->sd_ail_lock); return ret; @@ -569,7 +576,7 @@ static void log_write_header(struct gfs2_sbd *sdp, u32 flags, int pull) set_buffer_uptodate(bh); clear_buffer_dirty(bh); - gfs2_ail1_empty(sdp, 0); + gfs2_ail1_empty(sdp); tail = current_tail(sdp); lh = (struct gfs2_log_header *)bh->b_data; @@ -864,7 +871,7 @@ void gfs2_meta_syncfs(struct gfs2_sbd *sdp) gfs2_log_flush(sdp, NULL); for (;;) { gfs2_ail1_start(sdp); - if (gfs2_ail1_empty(sdp, DIO_ALL)) + if (gfs2_ail1_empty(sdp)) break; msleep(10); } @@ -900,17 +907,15 @@ int gfs2_logd(void *data) preflush = atomic_read(&sdp->sd_log_pinned); if (gfs2_jrnl_flush_reqd(sdp) || t == 0) { - gfs2_ail1_empty(sdp, DIO_ALL); + gfs2_ail1_empty(sdp); gfs2_log_flush(sdp, NULL); - gfs2_ail1_empty(sdp, DIO_ALL); } if (gfs2_ail_flush_reqd(sdp)) { gfs2_ail1_start(sdp); io_schedule(); - gfs2_ail1_empty(sdp, 0); + gfs2_ail1_empty(sdp); gfs2_log_flush(sdp, NULL); - gfs2_ail1_empty(sdp, DIO_ALL); } wake_up(&sdp->sd_log_waitq); -- cgit v1.2.3 From c83ae9cad8776bab153a05cc466be39f14011091 Mon Sep 17 00:00:00 2001 From: Steven Whitehouse Date: Mon, 18 Apr 2011 14:18:38 +0100 Subject: GFS2: Add an AIL writeback tracepoint Add a tracepoint for monitoring writeback of the AIL. Signed-off-by: Steven Whitehouse --- fs/gfs2/log.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'fs/gfs2/log.c') diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index 03e00417061b..ad1f1887633f 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -144,6 +144,7 @@ void gfs2_ail1_flush(struct gfs2_sbd *sdp, struct writeback_control *wbc) struct list_head *head = &sdp->sd_ail1_list; struct gfs2_ail *ai; + trace_gfs2_ail_flush(sdp, wbc, 1); spin_lock(&sdp->sd_ail_lock); list_for_each_entry_reverse(ai, head, ai_list) { if (wbc->nr_to_write <= 0) @@ -151,6 +152,7 @@ void gfs2_ail1_flush(struct gfs2_sbd *sdp, struct writeback_control *wbc) gfs2_ail1_start_one(sdp, wbc, ai); /* This may drop ail lock */ } spin_unlock(&sdp->sd_ail_lock); + trace_gfs2_ail_flush(sdp, wbc, 0); } /** -- cgit v1.2.3 From 4f1de018215fb56940ce5793e11becd1e8cd6e44 Mon Sep 17 00:00:00 2001 From: Steven Whitehouse Date: Tue, 26 Apr 2011 10:23:56 +0100 Subject: GFS2: Fix ail list traversal In the recent patches to update the AIL list code, I managed to forget that the ail list lock got dropped, even though I added a comment specifically to remind myself :( Reported-by: Barry Marson Signed-off-by: Steven Whitehouse --- fs/gfs2/log.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'fs/gfs2/log.c') diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index ad1f1887633f..cec26c00b50d 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -89,9 +89,9 @@ void gfs2_remove_from_ail(struct gfs2_bufdata *bd) * */ -static void gfs2_ail1_start_one(struct gfs2_sbd *sdp, - struct writeback_control *wbc, - struct gfs2_ail *ai) +static int gfs2_ail1_start_one(struct gfs2_sbd *sdp, + struct writeback_control *wbc, + struct gfs2_ail *ai) __releases(&sdp->sd_ail_lock) __acquires(&sdp->sd_ail_lock) { @@ -100,7 +100,6 @@ __acquires(&sdp->sd_ail_lock) struct gfs2_bufdata *bd, *s; struct buffer_head *bh; -restart: list_for_each_entry_safe_reverse(bd, s, &ai->ai_ail1_list, bd_ail_st_list) { bh = bd->bd_bh; @@ -120,13 +119,17 @@ restart: gl = bd->bd_gl; list_move(&bd->bd_ail_st_list, &ai->ai_ail1_list); mapping = bh->b_page->mapping; + if (!mapping) + continue; spin_unlock(&sdp->sd_ail_lock); generic_writepages(mapping, wbc); spin_lock(&sdp->sd_ail_lock); if (wbc->nr_to_write <= 0) break; - goto restart; + return 1; } + + return 0; } @@ -146,10 +149,12 @@ void gfs2_ail1_flush(struct gfs2_sbd *sdp, struct writeback_control *wbc) trace_gfs2_ail_flush(sdp, wbc, 1); spin_lock(&sdp->sd_ail_lock); +restart: list_for_each_entry_reverse(ai, head, ai_list) { if (wbc->nr_to_write <= 0) break; - gfs2_ail1_start_one(sdp, wbc, ai); /* This may drop ail lock */ + if (gfs2_ail1_start_one(sdp, wbc, ai)) + goto restart; } spin_unlock(&sdp->sd_ail_lock); trace_gfs2_ail_flush(sdp, wbc, 0); -- cgit v1.2.3 From 26b06a6958df0f12f1a654db8598433eb89cc024 Mon Sep 17 00:00:00 2001 From: Steven Whitehouse Date: Sat, 21 May 2011 19:21:07 +0100 Subject: GFS2: Wait properly when flushing the ail list The ail flush code has always relied upon log flushing to prevent it from spinning needlessly. This fixes it to wait on the last I/O request submitted (we don't need to wait for all of it) instead of either spinning with io_schedule or sleeping. As a result cpu usage of gfs2_logd is much reduced with certain workloads. Reported-by: Abhijith Das Tested-by: Abhijith Das Signed-off-by: Steven Whitehouse --- fs/gfs2/log.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'fs/gfs2/log.c') diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index cec26c00b50d..903115f2bb34 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -228,6 +228,27 @@ static int gfs2_ail1_empty(struct gfs2_sbd *sdp) return ret; } +static void gfs2_ail1_wait(struct gfs2_sbd *sdp) +{ + struct gfs2_ail *ai; + struct gfs2_bufdata *bd; + struct buffer_head *bh; + + spin_lock(&sdp->sd_ail_lock); + list_for_each_entry_reverse(ai, &sdp->sd_ail1_list, ai_list) { + list_for_each_entry(bd, &ai->ai_ail1_list, bd_ail_st_list) { + bh = bd->bd_bh; + if (!buffer_locked(bh)) + continue; + get_bh(bh); + spin_unlock(&sdp->sd_ail_lock); + wait_on_buffer(bh); + brelse(bh); + return; + } + } + spin_unlock(&sdp->sd_ail_lock); +} /** * gfs2_ail2_empty_one - Check whether or not a trans in the AIL has been synced @@ -878,9 +899,9 @@ void gfs2_meta_syncfs(struct gfs2_sbd *sdp) gfs2_log_flush(sdp, NULL); for (;;) { gfs2_ail1_start(sdp); + gfs2_ail1_wait(sdp); if (gfs2_ail1_empty(sdp)) break; - msleep(10); } } @@ -920,12 +941,14 @@ int gfs2_logd(void *data) if (gfs2_ail_flush_reqd(sdp)) { gfs2_ail1_start(sdp); - io_schedule(); + gfs2_ail1_wait(sdp); gfs2_ail1_empty(sdp); gfs2_log_flush(sdp, NULL); } - wake_up(&sdp->sd_log_waitq); + if (!gfs2_ail_flush_reqd(sdp)) + wake_up(&sdp->sd_log_waitq); + t = gfs2_tune_get(sdp, gt_logd_secs) * HZ; if (freezing(current)) refrigerator(); -- cgit v1.2.3