[PATCH 3/3][Resend] swsusp: improve freeing of memory

From: Rafael J. Wysocki
Date: Thu Nov 17 2005 - 17:36:57 EST


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: Rafael J. Wysocki <rjw@xxxxxxx>
Acked-by: Pavel Machek <pavel@xxxxxxx>

include/linux/suspend.h | 2 -
kernel/power/disk.c | 30 ++--------------------
kernel/power/power.h | 14 ++++++++--
kernel/power/snapshot.c | 65 ++++++++++++++++++++++++++++++++++++++++++++----
kernel/power/swsusp.c | 51 ++++++++++++++++++++++++++++++++++++-
5 files changed, 125 insertions(+), 37 deletions(-)

Index: linux-2.6.15-rc1-mm1/kernel/power/disk.c
===================================================================
--- linux-2.6.15-rc1-mm1.orig/kernel/power/disk.c 2005-11-17 22:52:01.000000000 +0100
+++ linux-2.6.15-rc1-mm1/kernel/power/disk.c 2005-11-17 22:52:04.000000000 +0100
@@ -24,6 +24,7 @@

extern suspend_disk_method_t pm_disk_mode;

+extern int swsusp_shrink_memory(void);
extern int swsusp_suspend(void);
extern int swsusp_write(struct pbe *pblist, unsigned int nr_pages);
extern int swsusp_check(void);
@@ -73,31 +74,6 @@
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)
{
if (pm_disk_mode == PM_DISK_PLATFORM) {
@@ -127,8 +103,8 @@
}

/* Free memory before shutting down devices. */
- free_some_memory();
- return 0;
+ if (!(error = swsusp_shrink_memory()))
+ return 0;
thaw:
thaw_processes();
enable_nonboot_cpus();
Index: linux-2.6.15-rc1-mm1/kernel/power/power.h
===================================================================
--- linux-2.6.15-rc1-mm1.orig/kernel/power/power.h 2005-11-17 22:52:01.000000000 +0100
+++ linux-2.6.15-rc1-mm1/kernel/power/power.h 2005-11-17 22:52:04.000000000 +0100
@@ -49,18 +49,26 @@
extern int pm_prepare_console(void);
extern void pm_restore_console(void);

-
/* References to section boundaries */
extern const void __nosave_begin, __nosave_end;

extern unsigned int nr_copy_pages;
-extern suspend_pagedir_t *pagedir_nosave;
-extern suspend_pagedir_t *pagedir_save;
+extern struct pbe *pagedir_nosave;
+
+/*
+ * 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_resume(void);

+extern unsigned int count_data_pages(void);
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 void swsusp_free(void);
extern int alloc_data_pages(struct pbe *pblist, gfp_t gfp_mask, int safe_needed);
Index: linux-2.6.15-rc1-mm1/kernel/power/snapshot.c
===================================================================
--- linux-2.6.15-rc1-mm1.orig/kernel/power/snapshot.c 2005-11-17 22:52:01.000000000 +0100
+++ linux-2.6.15-rc1-mm1/kernel/power/snapshot.c 2005-11-17 22:52:04.000000000 +0100
@@ -37,6 +37,31 @@
unsigned int nr_copy_pages;

#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 {
char *data;
struct page *page;
@@ -152,17 +177,15 @@
BUG_ON(PageReserved(page) && PageNosave(page));
if (PageNosave(page))
return 0;
- if (PageReserved(page) && pfn_is_nosave(pfn)) {
- pr_debug("[nosave pfn 0x%lx]", pfn);
+ if (PageReserved(page) && pfn_is_nosave(pfn))
return 0;
- }
if (PageNosaveFree(page))
return 0;

return 1;
}

-static unsigned count_data_pages(void)
+unsigned int count_data_pages(void)
{
struct zone *zone;
unsigned long zone_pfn;
@@ -267,6 +290,35 @@
}

/**
+ * 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,
* we can only use memory pages that do not conflict with the pages
* which had been used before suspend.
@@ -284,9 +336,12 @@
if (safe_needed)
do {
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() */
SetPageNosave(virt_to_page(res));
+ ((struct eaten_page *)res)->next = eaten_pages;
+ eaten_pages = res;
+ }
} while (res && PageNosaveFree(virt_to_page(res)));
else
res = (void *)get_zeroed_page(gfp_mask);
Index: linux-2.6.15-rc1-mm1/kernel/power/swsusp.c
===================================================================
--- linux-2.6.15-rc1-mm1.orig/kernel/power/swsusp.c 2005-11-17 22:52:01.000000000 +0100
+++ linux-2.6.15-rc1-mm1/kernel/power/swsusp.c 2005-11-17 22:52:04.000000000 +0100
@@ -70,11 +70,13 @@
#include "power.h"

#ifdef CONFIG_HIGHMEM
+unsigned int count_highmem_pages(void);
int save_highmem(void);
int restore_highmem(void);
#else
static int save_highmem(void) { return 0; }
static int restore_highmem(void) { return 0; }
+static unsigned int count_highmem_pages(void) { return 0; }
#endif

extern char resume_file[];
@@ -611,6 +613,51 @@
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 = count_data_pages() + count_highmem_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 error;
@@ -1030,8 +1077,10 @@
/* Allocate memory for the image and read the data from swap */
if (!error)
error = alloc_data_pages(pblist, GFP_ATOMIC, 1);
- if (!error)
+ if (!error) {
+ release_eaten_pages();
error = load_image_data(pblist, &handle, nr_pages);
+ }
if (!error)
*pblist_ptr = pblist;
}
Index: linux-2.6.15-rc1-mm1/include/linux/suspend.h
===================================================================
--- linux-2.6.15-rc1-mm1.orig/include/linux/suspend.h 2005-11-17 22:52:01.000000000 +0100
+++ linux-2.6.15-rc1-mm1/include/linux/suspend.h 2005-11-17 22:52:04.000000000 +0100
@@ -73,6 +73,6 @@
* XXX: We try to keep some more pages free so that I/O operations succeed
* without paging. Might this be more?
*/
-#define PAGES_FOR_IO 512
+#define PAGES_FOR_IO 1024

#endif /* _LINUX_SWSUSP_H */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/