Commit 215ddd66 authored by Mel Gorman's avatar Mel Gorman Committed by Linus Torvalds

mm: vmscan: only read new_classzone_idx from pgdat when reclaiming successfully

During allocator-intensive workloads, kswapd will be woken frequently
causing free memory to oscillate between the high and min watermark.  This
is expected behaviour.  Unfortunately, if the highest zone is small, a
problem occurs.

When balance_pgdat() returns, it may be at a lower classzone_idx than it
started because the highest zone was unreclaimable.  Before checking if it
should go to sleep though, it checks pgdat->classzone_idx which when there
is no other activity will be MAX_NR_ZONES-1.  It interprets this as it has
been woken up while reclaiming, skips scheduling and reclaims again.  As
there is no useful reclaim work to do, it enters into a loop of shrinking
slab consuming loads of CPU until the highest zone becomes reclaimable for
a long period of time.

There are two problems here.  1) If the returned classzone or order is
lower, it'll continue reclaiming without scheduling.  2) if the highest
zone was marked unreclaimable but balance_pgdat() returns immediately at
DEF_PRIORITY, the new lower classzone is not communicated back to kswapd()
for sleeping.

This patch does two things that are related.  If the end_zone is
unreclaimable, this information is communicated back.  Second, if the
classzone or order was reduced due to failing to reclaim, new information
is not read from pgdat and instead an attempt is made to go to sleep.  Due
to this, it is also necessary that pgdat->classzone_idx be initialised
each time to pgdat->nr_zones - 1 to avoid re-reads being interpreted as
wakeups.
Signed-off-by: default avatarMel Gorman <mgorman@suse.de>
Reported-by: default avatarPádraig Brady <P@draigBrady.com>
Tested-by: default avatarPádraig Brady <P@draigBrady.com>
Tested-by: default avatarAndrew Lutomirski <luto@mit.edu>
Acked-by: default avatarRik van Riel <riel@redhat.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
Cc: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: <stable@kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent da175d06
...@@ -2451,7 +2451,6 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, int order, ...@@ -2451,7 +2451,6 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, int order,
if (!zone_watermark_ok_safe(zone, order, if (!zone_watermark_ok_safe(zone, order,
high_wmark_pages(zone), 0, 0)) { high_wmark_pages(zone), 0, 0)) {
end_zone = i; end_zone = i;
*classzone_idx = i;
break; break;
} }
} }
...@@ -2531,8 +2530,11 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, int order, ...@@ -2531,8 +2530,11 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, int order,
total_scanned > sc.nr_reclaimed + sc.nr_reclaimed / 2) total_scanned > sc.nr_reclaimed + sc.nr_reclaimed / 2)
sc.may_writepage = 1; sc.may_writepage = 1;
if (zone->all_unreclaimable) if (zone->all_unreclaimable) {
if (end_zone && end_zone == i)
end_zone--;
continue; continue;
}
if (!zone_watermark_ok_safe(zone, order, if (!zone_watermark_ok_safe(zone, order,
high_wmark_pages(zone), end_zone, 0)) { high_wmark_pages(zone), end_zone, 0)) {
...@@ -2712,8 +2714,8 @@ static void kswapd_try_to_sleep(pg_data_t *pgdat, int order, int classzone_idx) ...@@ -2712,8 +2714,8 @@ static void kswapd_try_to_sleep(pg_data_t *pgdat, int order, int classzone_idx)
*/ */
static int kswapd(void *p) static int kswapd(void *p)
{ {
unsigned long order; unsigned long order, new_order;
int classzone_idx; int classzone_idx, new_classzone_idx;
pg_data_t *pgdat = (pg_data_t*)p; pg_data_t *pgdat = (pg_data_t*)p;
struct task_struct *tsk = current; struct task_struct *tsk = current;
...@@ -2743,17 +2745,23 @@ static int kswapd(void *p) ...@@ -2743,17 +2745,23 @@ static int kswapd(void *p)
tsk->flags |= PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD; tsk->flags |= PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD;
set_freezable(); set_freezable();
order = 0; order = new_order = 0;
classzone_idx = MAX_NR_ZONES - 1; classzone_idx = new_classzone_idx = pgdat->nr_zones - 1;
for ( ; ; ) { for ( ; ; ) {
unsigned long new_order;
int new_classzone_idx;
int ret; int ret;
new_order = pgdat->kswapd_max_order; /*
new_classzone_idx = pgdat->classzone_idx; * If the last balance_pgdat was unsuccessful it's unlikely a
pgdat->kswapd_max_order = 0; * new request of a similar or harder type will succeed soon
pgdat->classzone_idx = MAX_NR_ZONES - 1; * so consider going to sleep on the basis we reclaimed at
*/
if (classzone_idx >= new_classzone_idx && order == new_order) {
new_order = pgdat->kswapd_max_order;
new_classzone_idx = pgdat->classzone_idx;
pgdat->kswapd_max_order = 0;
pgdat->classzone_idx = pgdat->nr_zones - 1;
}
if (order < new_order || classzone_idx > new_classzone_idx) { if (order < new_order || classzone_idx > new_classzone_idx) {
/* /*
* Don't sleep if someone wants a larger 'order' * Don't sleep if someone wants a larger 'order'
...@@ -2766,7 +2774,7 @@ static int kswapd(void *p) ...@@ -2766,7 +2774,7 @@ static int kswapd(void *p)
order = pgdat->kswapd_max_order; order = pgdat->kswapd_max_order;
classzone_idx = pgdat->classzone_idx; classzone_idx = pgdat->classzone_idx;
pgdat->kswapd_max_order = 0; pgdat->kswapd_max_order = 0;
pgdat->classzone_idx = MAX_NR_ZONES - 1; pgdat->classzone_idx = pgdat->nr_zones - 1;
} }
ret = try_to_freeze(); ret = try_to_freeze();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment