Subject: writeback: dirty ratelimit - think time compensation Date: Sat Jun 11 19:25:42 CST 2011 Compensate the task's think time when computing the final pause time, so that ->dirty_ratelimit can be executed accurately. In the rare case that the task slept longer than the period time (result in negative pause time), the extra sleep time will be compensated in next period if it's not too big (<500ms). Accumulated errors are carefully avoided as long as the max pause area is not hitted. Pseudo code: period = pages_dirtied / bw; think = jiffies - dirty_paused_when; pause = period - think; case 1: period > think pause = period - think dirty_paused_when += pause period time |======================================>| think time |===============>| ------|----------------|----------------------|----------- dirty_paused_when jiffies case 2: period <= think don't pause; reduce future pause time by: dirty_paused_when += period period time |=========================>| think time |======================================>| ------|--------------------------+------------|----------- dirty_paused_when jiffies Signed-off-by: Wu Fengguang --- include/linux/sched.h | 1 + kernel/fork.c | 1 + mm/page-writeback.c | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) --- linux-next.orig/include/linux/sched.h 2011-08-09 07:53:31.000000000 +0800 +++ linux-next/include/linux/sched.h 2011-08-09 07:54:12.000000000 +0800 @@ -1531,6 +1531,7 @@ struct task_struct { */ int nr_dirtied; int nr_dirtied_pause; + unsigned long dirty_paused_when; /* start of a write-and-pause period */ #ifdef CONFIG_LATENCYTOP int latency_record_count; --- linux-next.orig/mm/page-writeback.c 2011-08-09 07:53:31.000000000 +0800 +++ linux-next/mm/page-writeback.c 2011-08-09 08:08:11.000000000 +0800 @@ -817,6 +817,7 @@ static void balance_dirty_pages(struct a unsigned long background_thresh; unsigned long dirty_thresh; unsigned long bdi_thresh; + long period; long pause = 0; bool dirty_exceeded = false; unsigned long bw; @@ -825,6 +826,8 @@ static void balance_dirty_pages(struct a unsigned long start_time = jiffies; for (;;) { + unsigned long now = jiffies; + /* * Unstable writes are a feature of certain networked * filesystems (i.e. NFS) in which data may have been @@ -842,8 +845,11 @@ static void balance_dirty_pages(struct a * catch-up. This avoids (excessively) small writeouts * when the bdi limits are ramping up. */ - if (nr_dirty <= (background_thresh + dirty_thresh) / 2) + if (nr_dirty <= (background_thresh + dirty_thresh) / 2) { + current->dirty_paused_when = now; + current->nr_dirtied = 0; break; + } bdi_thresh = bdi_dirty_limit(bdi, dirty_thresh); @@ -879,17 +885,40 @@ static void balance_dirty_pages(struct a bw = bdi_position_ratio(bdi, dirty_thresh, nr_dirty, bdi_thresh, bdi_dirty); if (unlikely(bw == 0)) { + period = MAX_PAUSE; pause = MAX_PAUSE; goto pause; } bw = (u64)base_bw * bw >> BANDWIDTH_CALC_SHIFT; - pause = (HZ * pages_dirtied + bw / 2) / (bw | 1); + period = (HZ * pages_dirtied + bw / 2) / (bw | 1); + pause = current->dirty_paused_when + period - now; + /* + * For less than 1s think time (ext3/4 may block the dirtier + * for up to 800ms from time to time on 1-HDD; so does xfs, + * however at much less frequency), try to compensate it in + * future periods by updating the virtual time; otherwise just + * do a reset, as it may be a light dirtier. + */ + if (unlikely(pause <= 0)) { + if (pause < -HZ) { + current->dirty_paused_when = now; + current->nr_dirtied = 0; + } else if (period) { + current->dirty_paused_when += period; + current->nr_dirtied = 0; + } + pause = 1; /* avoid resetting nr_dirtied_pause below */ + break; + } pause = min(pause, MAX_PAUSE); pause: __set_current_state(TASK_UNINTERRUPTIBLE); io_schedule_timeout(pause); + current->dirty_paused_when = now + pause; + current->nr_dirtied = 0; + dirty_thresh = hard_dirty_limit(dirty_thresh); /* * max-pause area. If dirty exceeded but still within this @@ -916,7 +945,6 @@ pause: if (!dirty_exceeded && bdi->dirty_exceeded) bdi->dirty_exceeded = 0; - current->nr_dirtied = 0; current->nr_dirtied_pause = ratelimit_pages(nr_dirty, dirty_thresh); if (writeback_in_progress(bdi)) --- linux-next.orig/kernel/fork.c 2011-08-09 07:53:31.000000000 +0800 +++ linux-next/kernel/fork.c 2011-08-09 07:54:12.000000000 +0800 @@ -1303,6 +1303,7 @@ static struct task_struct *copy_process( p->nr_dirtied = 0; p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10); + p->dirty_paused_when = 0; /* * Ok, make it visible to the rest of the system.