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

[PATCH] swsusp: improve freeing of memory

This patch makes swsusp free only as much memory as needed to complete the
suspend and not as much as possible.   In the most of cases this should speed
up the suspend and make the system much more responsive after resume,
especially if a GUI (eg.  X Windows) is used.

If needed, the old behavior (ie to free as much memory as possible during
suspend) can be restored by unsetting FAST_FREE in power.h
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Acked-by: default avatarPavel Machek <pavel@suse.cz>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 7088a5c0
...@@ -73,6 +73,6 @@ unsigned long get_safe_page(gfp_t gfp_mask); ...@@ -73,6 +73,6 @@ unsigned long get_safe_page(gfp_t gfp_mask);
* XXX: We try to keep some more pages free so that I/O operations succeed * XXX: We try to keep some more pages free so that I/O operations succeed
* without paging. Might this be more? * without paging. Might this be more?
*/ */
#define PAGES_FOR_IO 512 #define PAGES_FOR_IO 1024
#endif /* _LINUX_SWSUSP_H */ #endif /* _LINUX_SWSUSP_H */
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
extern suspend_disk_method_t pm_disk_mode; extern suspend_disk_method_t pm_disk_mode;
extern int swsusp_shrink_memory(void);
extern int swsusp_suspend(void); extern int swsusp_suspend(void);
extern int swsusp_write(struct pbe *pblist, unsigned int nr_pages); extern int swsusp_write(struct pbe *pblist, unsigned int nr_pages);
extern int swsusp_check(void); extern int swsusp_check(void);
...@@ -73,31 +74,6 @@ static void power_down(suspend_disk_method_t mode) ...@@ -73,31 +74,6 @@ static void power_down(suspend_disk_method_t mode)
static int in_suspend __nosavedata = 0; static int in_suspend __nosavedata = 0;
/**
* free_some_memory - Try to free as much memory as possible
*
* ... but do not OOM-kill anyone
*
* Notice: all userland should be stopped at this point, or
* livelock is possible.
*/
static void free_some_memory(void)
{
unsigned int i = 0;
unsigned int tmp;
unsigned long pages = 0;
char *p = "-\\|/";
printk("Freeing memory... ");
while ((tmp = shrink_all_memory(10000))) {
pages += tmp;
printk("\b%c", p[i++ % 4]);
}
printk("\bdone (%li pages freed)\n", pages);
}
static inline void platform_finish(void) static inline void platform_finish(void)
{ {
if (pm_disk_mode == PM_DISK_PLATFORM) { if (pm_disk_mode == PM_DISK_PLATFORM) {
...@@ -127,8 +103,8 @@ static int prepare_processes(void) ...@@ -127,8 +103,8 @@ static int prepare_processes(void)
} }
/* Free memory before shutting down devices. */ /* Free memory before shutting down devices. */
free_some_memory(); if (!(error = swsusp_shrink_memory()))
return 0; return 0;
thaw: thaw:
thaw_processes(); thaw_processes();
enable_nonboot_cpus(); enable_nonboot_cpus();
......
...@@ -49,18 +49,26 @@ extern void thaw_processes(void); ...@@ -49,18 +49,26 @@ extern void thaw_processes(void);
extern int pm_prepare_console(void); extern int pm_prepare_console(void);
extern void pm_restore_console(void); extern void pm_restore_console(void);
/* References to section boundaries */ /* References to section boundaries */
extern const void __nosave_begin, __nosave_end; extern const void __nosave_begin, __nosave_end;
extern unsigned int nr_copy_pages; extern unsigned int nr_copy_pages;
extern suspend_pagedir_t *pagedir_nosave; extern struct pbe *pagedir_nosave;
extern suspend_pagedir_t *pagedir_save;
/*
* This compilation switch determines the way in which memory will be freed
* during suspend. If defined, only as much memory will be freed as needed
* to complete the suspend, which will make it go faster. Otherwise, the
* largest possible amount of memory will be freed.
*/
#define FAST_FREE 1
extern asmlinkage int swsusp_arch_suspend(void); extern asmlinkage int swsusp_arch_suspend(void);
extern asmlinkage int swsusp_arch_resume(void); extern asmlinkage int swsusp_arch_resume(void);
extern unsigned int count_data_pages(void);
extern void free_pagedir(struct pbe *pblist); extern void free_pagedir(struct pbe *pblist);
extern void release_eaten_pages(void);
extern struct pbe *alloc_pagedir(unsigned nr_pages, gfp_t gfp_mask, int safe_needed); extern struct pbe *alloc_pagedir(unsigned nr_pages, gfp_t gfp_mask, int safe_needed);
extern void swsusp_free(void); extern void swsusp_free(void);
extern int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed); extern int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed);
......
...@@ -37,6 +37,31 @@ struct pbe *pagedir_nosave; ...@@ -37,6 +37,31 @@ struct pbe *pagedir_nosave;
unsigned int nr_copy_pages; unsigned int nr_copy_pages;
#ifdef CONFIG_HIGHMEM #ifdef CONFIG_HIGHMEM
unsigned int count_highmem_pages(void)
{
struct zone *zone;
unsigned long zone_pfn;
unsigned int n = 0;
for_each_zone (zone)
if (is_highmem(zone)) {
mark_free_pages(zone);
for (zone_pfn = 0; zone_pfn < zone->spanned_pages; zone_pfn++) {
struct page *page;
unsigned long pfn = zone_pfn + zone->zone_start_pfn;
if (!pfn_valid(pfn))
continue;
page = pfn_to_page(pfn);
if (PageReserved(page))
continue;
if (PageNosaveFree(page))
continue;
n++;
}
}
return n;
}
struct highmem_page { struct highmem_page {
char *data; char *data;
struct page *page; struct page *page;
...@@ -152,17 +177,15 @@ static int saveable(struct zone *zone, unsigned long *zone_pfn) ...@@ -152,17 +177,15 @@ static int saveable(struct zone *zone, unsigned long *zone_pfn)
BUG_ON(PageReserved(page) && PageNosave(page)); BUG_ON(PageReserved(page) && PageNosave(page));
if (PageNosave(page)) if (PageNosave(page))
return 0; return 0;
if (PageReserved(page) && pfn_is_nosave(pfn)) { if (PageReserved(page) && pfn_is_nosave(pfn))
pr_debug("[nosave pfn 0x%lx]", pfn);
return 0; return 0;
}
if (PageNosaveFree(page)) if (PageNosaveFree(page))
return 0; return 0;
return 1; return 1;
} }
static unsigned count_data_pages(void) unsigned int count_data_pages(void)
{ {
struct zone *zone; struct zone *zone;
unsigned long zone_pfn; unsigned long zone_pfn;
...@@ -266,6 +289,35 @@ static inline void create_pbe_list(struct pbe *pblist, unsigned int nr_pages) ...@@ -266,6 +289,35 @@ static inline void create_pbe_list(struct pbe *pblist, unsigned int nr_pages)
} }
} }
/**
* On resume it is necessary to trace and eventually free the unsafe
* pages that have been allocated, because they are needed for I/O
* (on x86-64 we likely will "eat" these pages once again while
* creating the temporary page translation tables)
*/
struct eaten_page {
struct eaten_page *next;
char padding[PAGE_SIZE - sizeof(void *)];
};
static struct eaten_page *eaten_pages = NULL;
void release_eaten_pages(void)
{
struct eaten_page *p, *q;
p = eaten_pages;
while (p) {
q = p->next;
/* We don't want swsusp_free() to free this page again */
ClearPageNosave(virt_to_page(p));
free_page((unsigned long)p);
p = q;
}
eaten_pages = NULL;
}
/** /**
* @safe_needed - on resume, for storing the PBE list and the image, * @safe_needed - on resume, for storing the PBE list and the image,
* we can only use memory pages that do not conflict with the pages * we can only use memory pages that do not conflict with the pages
...@@ -284,9 +336,12 @@ static inline void *alloc_image_page(gfp_t gfp_mask, int safe_needed) ...@@ -284,9 +336,12 @@ static inline void *alloc_image_page(gfp_t gfp_mask, int safe_needed)
if (safe_needed) if (safe_needed)
do { do {
res = (void *)get_zeroed_page(gfp_mask); res = (void *)get_zeroed_page(gfp_mask);
if (res && PageNosaveFree(virt_to_page(res))) if (res && PageNosaveFree(virt_to_page(res))) {
/* This is for swsusp_free() */ /* This is for swsusp_free() */
SetPageNosave(virt_to_page(res)); SetPageNosave(virt_to_page(res));
((struct eaten_page *)res)->next = eaten_pages;
eaten_pages = res;
}
} while (res && PageNosaveFree(virt_to_page(res))); } while (res && PageNosaveFree(virt_to_page(res)));
else else
res = (void *)get_zeroed_page(gfp_mask); res = (void *)get_zeroed_page(gfp_mask);
......
...@@ -70,11 +70,13 @@ ...@@ -70,11 +70,13 @@
#include "power.h" #include "power.h"
#ifdef CONFIG_HIGHMEM #ifdef CONFIG_HIGHMEM
unsigned int count_highmem_pages(void);
int save_highmem(void); int save_highmem(void);
int restore_highmem(void); int restore_highmem(void);
#else #else
static int save_highmem(void) { return 0; } static int save_highmem(void) { return 0; }
static int restore_highmem(void) { return 0; } static int restore_highmem(void) { return 0; }
static unsigned int count_highmem_pages(void) { return 0; }
#endif #endif
extern char resume_file[]; extern char resume_file[];
...@@ -611,6 +613,52 @@ int swsusp_write(struct pbe *pblist, unsigned int nr_pages) ...@@ -611,6 +613,52 @@ int swsusp_write(struct pbe *pblist, unsigned int nr_pages)
return error; return error;
} }
/**
* swsusp_shrink_memory - Try to free as much memory as needed
*
* ... but do not OOM-kill anyone
*
* Notice: all userland should be stopped before it is called, or
* livelock is possible.
*/
#define SHRINK_BITE 10000
int swsusp_shrink_memory(void)
{
long tmp;
struct zone *zone;
unsigned long pages = 0;
unsigned int i = 0;
char *p = "-\\|/";
printk("Shrinking memory... ");
do {
#ifdef FAST_FREE
tmp = 2 * count_highmem_pages();
tmp += tmp / 50 + count_data_pages();
tmp += (tmp + PBES_PER_PAGE - 1) / PBES_PER_PAGE +
PAGES_FOR_IO;
for_each_zone (zone)
if (!is_highmem(zone))
tmp -= zone->free_pages;
if (tmp > 0) {
tmp = shrink_all_memory(SHRINK_BITE);
if (!tmp)
return -ENOMEM;
pages += tmp;
}
#else
tmp = shrink_all_memory(SHRINK_BITE);
pages += tmp;
#endif
printk("\b%c", p[i++%4]);
} while (tmp > 0);
printk("\bdone (%lu pages freed)\n", pages);
return 0;
}
int swsusp_suspend(void) int swsusp_suspend(void)
{ {
int error; int error;
...@@ -1030,8 +1078,10 @@ static int read_suspend_image(struct pbe **pblist_ptr) ...@@ -1030,8 +1078,10 @@ static int read_suspend_image(struct pbe **pblist_ptr)
/* Allocate memory for the image and read the data from swap */ /* Allocate memory for the image and read the data from swap */
if (!error) if (!error)
error = alloc_data_pages(pblist, GFP_ATOMIC, 1); error = alloc_data_pages(pblist, GFP_ATOMIC, 1);
if (!error) if (!error) {
release_eaten_pages();
error = load_image_data(pblist, &handle, nr_pages); error = load_image_data(pblist, &handle, nr_pages);
}
if (!error) if (!error)
*pblist_ptr = pblist; *pblist_ptr = pblist;
} }
......
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