Commit d6277db4 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki Committed by Linus Torvalds

[PATCH] swsusp: rework memory shrinker

Rework the swsusp's memory shrinker in the following way:

- Simplify balance_pgdat() by removing all of the swsusp-related code
  from it.

- Make shrink_all_memory() use shrink_slab() and a new function
  shrink_all_zones() which calls shrink_active_list() and
  shrink_inactive_list() directly for each zone in a way that's optimized
  for suspend.

In shrink_all_memory() we try to free exactly as many pages as the caller
asks for, preferably in one shot, starting from easier targets.   If slab
caches are huge, they are most likely to have enough pages to reclaim.
 The inactive lists are next (the zones with more inactive pages go first)
etc.

Each time shrink_all_memory() attempts to shrink the active and inactive
lists for each zone in 5 passes.   In the first pass, only the inactive
lists are taken into consideration.   In the next two passes the active
lists are also shrunk, but mapped pages are not reclaimed.   In the last
two passes the active and inactive lists are shrunk and mapped pages are
reclaimed as well.  The aim of this is to alter the reclaim logic to choose
the best pages to keep on resume and improve the responsiveness of the
resumed system.
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: default avatarCon Kolivas <kernel@kolivas.org>
Signed-off-by: default avatarAdrian Bunk <bunk@stusta.de>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 7a7c381d
...@@ -175,6 +175,12 @@ void free_all_swap_pages(int swap, struct bitmap_page *bitmap) ...@@ -175,6 +175,12 @@ void free_all_swap_pages(int swap, struct bitmap_page *bitmap)
*/ */
#define SHRINK_BITE 10000 #define SHRINK_BITE 10000
static inline unsigned long __shrink_memory(long tmp)
{
if (tmp > SHRINK_BITE)
tmp = SHRINK_BITE;
return shrink_all_memory(tmp);
}
int swsusp_shrink_memory(void) int swsusp_shrink_memory(void)
{ {
...@@ -195,12 +201,12 @@ int swsusp_shrink_memory(void) ...@@ -195,12 +201,12 @@ int swsusp_shrink_memory(void)
if (!is_highmem(zone)) if (!is_highmem(zone))
tmp -= zone->free_pages; tmp -= zone->free_pages;
if (tmp > 0) { if (tmp > 0) {
tmp = shrink_all_memory(SHRINK_BITE); tmp = __shrink_memory(tmp);
if (!tmp) if (!tmp)
return -ENOMEM; return -ENOMEM;
pages += tmp; pages += tmp;
} else if (size > image_size / PAGE_SIZE) { } else if (size > image_size / PAGE_SIZE) {
tmp = shrink_all_memory(SHRINK_BITE); tmp = __shrink_memory(size - (image_size / PAGE_SIZE));
pages += tmp; pages += tmp;
} }
printk("\b%c", p[i++%4]); printk("\b%c", p[i++%4]);
......
...@@ -61,6 +61,8 @@ struct scan_control { ...@@ -61,6 +61,8 @@ struct scan_control {
* In this context, it doesn't matter that we scan the * In this context, it doesn't matter that we scan the
* whole list at once. */ * whole list at once. */
int swap_cluster_max; int swap_cluster_max;
int swappiness;
}; };
/* /*
...@@ -741,7 +743,7 @@ static void shrink_active_list(unsigned long nr_pages, struct zone *zone, ...@@ -741,7 +743,7 @@ static void shrink_active_list(unsigned long nr_pages, struct zone *zone,
* A 100% value of vm_swappiness overrides this algorithm * A 100% value of vm_swappiness overrides this algorithm
* altogether. * altogether.
*/ */
swap_tendency = mapped_ratio / 2 + distress + vm_swappiness; swap_tendency = mapped_ratio / 2 + distress + sc->swappiness;
/* /*
* Now use this metric to decide whether to start moving mapped * Now use this metric to decide whether to start moving mapped
...@@ -957,6 +959,7 @@ unsigned long try_to_free_pages(struct zone **zones, gfp_t gfp_mask) ...@@ -957,6 +959,7 @@ unsigned long try_to_free_pages(struct zone **zones, gfp_t gfp_mask)
.may_writepage = !laptop_mode, .may_writepage = !laptop_mode,
.swap_cluster_max = SWAP_CLUSTER_MAX, .swap_cluster_max = SWAP_CLUSTER_MAX,
.may_swap = 1, .may_swap = 1,
.swappiness = vm_swappiness,
}; };
inc_page_state(allocstall); inc_page_state(allocstall);
...@@ -1021,10 +1024,6 @@ unsigned long try_to_free_pages(struct zone **zones, gfp_t gfp_mask) ...@@ -1021,10 +1024,6 @@ unsigned long try_to_free_pages(struct zone **zones, gfp_t gfp_mask)
* For kswapd, balance_pgdat() will work across all this node's zones until * For kswapd, balance_pgdat() will work across all this node's zones until
* they are all at pages_high. * they are all at pages_high.
* *
* If `nr_pages' is non-zero then it is the number of pages which are to be
* reclaimed, regardless of the zone occupancies. This is a software suspend
* special.
*
* Returns the number of pages which were actually freed. * Returns the number of pages which were actually freed.
* *
* There is special handling here for zones which are full of pinned pages. * There is special handling here for zones which are full of pinned pages.
...@@ -1042,10 +1041,8 @@ unsigned long try_to_free_pages(struct zone **zones, gfp_t gfp_mask) ...@@ -1042,10 +1041,8 @@ unsigned long try_to_free_pages(struct zone **zones, gfp_t gfp_mask)
* the page allocator fallback scheme to ensure that aging of pages is balanced * the page allocator fallback scheme to ensure that aging of pages is balanced
* across the zones. * across the zones.
*/ */
static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages, static unsigned long balance_pgdat(pg_data_t *pgdat, int order)
int order)
{ {
unsigned long to_free = nr_pages;
int all_zones_ok; int all_zones_ok;
int priority; int priority;
int i; int i;
...@@ -1055,7 +1052,8 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages, ...@@ -1055,7 +1052,8 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages,
struct scan_control sc = { struct scan_control sc = {
.gfp_mask = GFP_KERNEL, .gfp_mask = GFP_KERNEL,
.may_swap = 1, .may_swap = 1,
.swap_cluster_max = nr_pages ? nr_pages : SWAP_CLUSTER_MAX, .swap_cluster_max = SWAP_CLUSTER_MAX,
.swappiness = vm_swappiness,
}; };
loop_again: loop_again:
...@@ -1082,31 +1080,26 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages, ...@@ -1082,31 +1080,26 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages,
all_zones_ok = 1; all_zones_ok = 1;
if (nr_pages == 0) { /*
/* * Scan in the highmem->dma direction for the highest
* Scan in the highmem->dma direction for the highest * zone which needs scanning
* zone which needs scanning */
*/ for (i = pgdat->nr_zones - 1; i >= 0; i--) {
for (i = pgdat->nr_zones - 1; i >= 0; i--) { struct zone *zone = pgdat->node_zones + i;
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone)) if (!populated_zone(zone))
continue; continue;
if (zone->all_unreclaimable && if (zone->all_unreclaimable && priority != DEF_PRIORITY)
priority != DEF_PRIORITY) continue;
continue;
if (!zone_watermark_ok(zone, order, if (!zone_watermark_ok(zone, order, zone->pages_high,
zone->pages_high, 0, 0)) { 0, 0)) {
end_zone = i; end_zone = i;
goto scan; goto scan;
}
} }
goto out;
} else {
end_zone = pgdat->nr_zones - 1;
} }
goto out;
scan: scan:
for (i = 0; i <= end_zone; i++) { for (i = 0; i <= end_zone; i++) {
struct zone *zone = pgdat->node_zones + i; struct zone *zone = pgdat->node_zones + i;
...@@ -1133,11 +1126,9 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages, ...@@ -1133,11 +1126,9 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages,
if (zone->all_unreclaimable && priority != DEF_PRIORITY) if (zone->all_unreclaimable && priority != DEF_PRIORITY)
continue; continue;
if (nr_pages == 0) { /* Not software suspend */ if (!zone_watermark_ok(zone, order, zone->pages_high,
if (!zone_watermark_ok(zone, order, end_zone, 0))
zone->pages_high, end_zone, 0)) all_zones_ok = 0;
all_zones_ok = 0;
}
zone->temp_priority = priority; zone->temp_priority = priority;
if (zone->prev_priority > priority) if (zone->prev_priority > priority)
zone->prev_priority = priority; zone->prev_priority = priority;
...@@ -1162,8 +1153,6 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages, ...@@ -1162,8 +1153,6 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages,
total_scanned > nr_reclaimed + nr_reclaimed / 2) total_scanned > nr_reclaimed + nr_reclaimed / 2)
sc.may_writepage = 1; sc.may_writepage = 1;
} }
if (nr_pages && to_free > nr_reclaimed)
continue; /* swsusp: need to do more work */
if (all_zones_ok) if (all_zones_ok)
break; /* kswapd: all done */ break; /* kswapd: all done */
/* /*
...@@ -1179,7 +1168,7 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages, ...@@ -1179,7 +1168,7 @@ static unsigned long balance_pgdat(pg_data_t *pgdat, unsigned long nr_pages,
* matches the direct reclaim path behaviour in terms of impact * matches the direct reclaim path behaviour in terms of impact
* on zone->*_priority. * on zone->*_priority.
*/ */
if ((nr_reclaimed >= SWAP_CLUSTER_MAX) && !nr_pages) if (nr_reclaimed >= SWAP_CLUSTER_MAX)
break; break;
} }
out: out:
...@@ -1261,7 +1250,7 @@ static int kswapd(void *p) ...@@ -1261,7 +1250,7 @@ static int kswapd(void *p)
} }
finish_wait(&pgdat->kswapd_wait, &wait); finish_wait(&pgdat->kswapd_wait, &wait);
balance_pgdat(pgdat, 0, order); balance_pgdat(pgdat, order);
} }
return 0; return 0;
} }
...@@ -1290,35 +1279,154 @@ void wakeup_kswapd(struct zone *zone, int order) ...@@ -1290,35 +1279,154 @@ void wakeup_kswapd(struct zone *zone, int order)
#ifdef CONFIG_PM #ifdef CONFIG_PM
/* /*
* Try to free `nr_pages' of memory, system-wide. Returns the number of freed * Helper function for shrink_all_memory(). Tries to reclaim 'nr_pages' pages
* pages. * from LRU lists system-wide, for given pass and priority, and returns the
* number of reclaimed pages
*
* For pass > 3 we also try to shrink the LRU lists that contain a few pages
*/
static unsigned long shrink_all_zones(unsigned long nr_pages, int pass,
int prio, struct scan_control *sc)
{
struct zone *zone;
unsigned long nr_to_scan, ret = 0;
for_each_zone(zone) {
if (!populated_zone(zone))
continue;
if (zone->all_unreclaimable && prio != DEF_PRIORITY)
continue;
/* For pass = 0 we don't shrink the active list */
if (pass > 0) {
zone->nr_scan_active += (zone->nr_active >> prio) + 1;
if (zone->nr_scan_active >= nr_pages || pass > 3) {
zone->nr_scan_active = 0;
nr_to_scan = min(nr_pages, zone->nr_active);
shrink_active_list(nr_to_scan, zone, sc);
}
}
zone->nr_scan_inactive += (zone->nr_inactive >> prio) + 1;
if (zone->nr_scan_inactive >= nr_pages || pass > 3) {
zone->nr_scan_inactive = 0;
nr_to_scan = min(nr_pages, zone->nr_inactive);
ret += shrink_inactive_list(nr_to_scan, zone, sc);
if (ret >= nr_pages)
return ret;
}
}
return ret;
}
/*
* Try to free `nr_pages' of memory, system-wide, and return the number of
* freed pages.
*
* Rather than trying to age LRUs the aim is to preserve the overall
* LRU order by reclaiming preferentially
* inactive > active > active referenced > active mapped
*/ */
unsigned long shrink_all_memory(unsigned long nr_pages) unsigned long shrink_all_memory(unsigned long nr_pages)
{ {
pg_data_t *pgdat; unsigned long lru_pages, nr_slab;
unsigned long nr_to_free = nr_pages;
unsigned long ret = 0; unsigned long ret = 0;
unsigned retry = 2; int pass;
struct reclaim_state reclaim_state = { struct reclaim_state reclaim_state;
.reclaimed_slab = 0, struct zone *zone;
struct scan_control sc = {
.gfp_mask = GFP_KERNEL,
.may_swap = 0,
.swap_cluster_max = nr_pages,
.may_writepage = 1,
.swappiness = vm_swappiness,
}; };
current->reclaim_state = &reclaim_state; current->reclaim_state = &reclaim_state;
repeat:
for_each_online_pgdat(pgdat) {
unsigned long freed;
freed = balance_pgdat(pgdat, nr_to_free, 0); lru_pages = 0;
ret += freed; for_each_zone(zone)
nr_to_free -= freed; lru_pages += zone->nr_active + zone->nr_inactive;
if ((long)nr_to_free <= 0)
nr_slab = read_page_state(nr_slab);
/* If slab caches are huge, it's better to hit them first */
while (nr_slab >= lru_pages) {
reclaim_state.reclaimed_slab = 0;
shrink_slab(nr_pages, sc.gfp_mask, lru_pages);
if (!reclaim_state.reclaimed_slab)
break; break;
ret += reclaim_state.reclaimed_slab;
if (ret >= nr_pages)
goto out;
nr_slab -= reclaim_state.reclaimed_slab;
} }
if (retry-- && ret < nr_pages) {
blk_congestion_wait(WRITE, HZ/5); /*
goto repeat; * We try to shrink LRUs in 5 passes:
* 0 = Reclaim from inactive_list only
* 1 = Reclaim from active list but don't reclaim mapped
* 2 = 2nd pass of type 1
* 3 = Reclaim mapped (normal reclaim)
* 4 = 2nd pass of type 3
*/
for (pass = 0; pass < 5; pass++) {
int prio;
/* Needed for shrinking slab caches later on */
if (!lru_pages)
for_each_zone(zone) {
lru_pages += zone->nr_active;
lru_pages += zone->nr_inactive;
}
/* Force reclaiming mapped pages in the passes #3 and #4 */
if (pass > 2) {
sc.may_swap = 1;
sc.swappiness = 100;
}
for (prio = DEF_PRIORITY; prio >= 0; prio--) {
unsigned long nr_to_scan = nr_pages - ret;
sc.nr_mapped = read_page_state(nr_mapped);
sc.nr_scanned = 0;
ret += shrink_all_zones(nr_to_scan, prio, pass, &sc);
if (ret >= nr_pages)
goto out;
reclaim_state.reclaimed_slab = 0;
shrink_slab(sc.nr_scanned, sc.gfp_mask, lru_pages);
ret += reclaim_state.reclaimed_slab;
if (ret >= nr_pages)
goto out;
if (sc.nr_scanned && prio < DEF_PRIORITY - 2)
blk_congestion_wait(WRITE, HZ / 10);
}
lru_pages = 0;
} }
/*
* If ret = 0, we could not shrink LRUs, but there may be something
* in slab caches
*/
if (!ret)
do {
reclaim_state.reclaimed_slab = 0;
shrink_slab(nr_pages, sc.gfp_mask, lru_pages);
ret += reclaim_state.reclaimed_slab;
} while (ret < nr_pages && reclaim_state.reclaimed_slab > 0);
out:
current->reclaim_state = NULL; current->reclaim_state = NULL;
return ret; return ret;
} }
#endif #endif
...@@ -1416,6 +1524,7 @@ static int __zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order) ...@@ -1416,6 +1524,7 @@ static int __zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order)
.swap_cluster_max = max_t(unsigned long, nr_pages, .swap_cluster_max = max_t(unsigned long, nr_pages,
SWAP_CLUSTER_MAX), SWAP_CLUSTER_MAX),
.gfp_mask = gfp_mask, .gfp_mask = gfp_mask,
.swappiness = vm_swappiness,
}; };
disable_swap_token(); disable_swap_token();
......
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