[PATCH] swsusp: improve freeing of memory
authorRafael J. Wysocki <rjw@sisk.pl>
Fri, 6 Jan 2006 08:13:46 +0000 (00:13 -0800)
committerLinus Torvalds <torvalds@g5.osdl.org>
Fri, 6 Jan 2006 16:33:40 +0000 (08:33 -0800)
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@sisk.pl>
Acked-by: Pavel Machek <pavel@suse.cz>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
include/linux/suspend.h
kernel/power/disk.c
kernel/power/power.h
kernel/power/snapshot.c
kernel/power/swsusp.c

index 33bbaea23aaf3dfe70742bbd98e2f85d0a72b19b..5dc94e777fab926d408477dbea464a3c78bcd3fd 100644 (file)
@@ -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
  * without paging. Might this be more?
  */
-#define PAGES_FOR_IO   512
+#define PAGES_FOR_IO   1024
 
 #endif /* _LINUX_SWSUSP_H */
index 76a5131b0e808813a61d3f77529953137972ab96..9e51cdf7b78d57575ff87ee352cbaf9691c606ab 100644 (file)
@@ -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 void power_down(suspend_disk_method_t mode)
 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 @@ static int prepare_processes(void)
        }
 
        /* 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 977877c6dcfcb8c4a4de37b8ee17e076d7b24dbb..acdc83b3d890bcd1540bcccdfd37c2f36b6ce335 100644 (file)
@@ -49,18 +49,26 @@ extern void thaw_processes(void);
 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 152d56cdf017310adb666bd08491905f8517474f..e80d282dbf5801cfaf767304b80ee7cb1bf75d08 100644 (file)
@@ -37,6 +37,31 @@ struct pbe *pagedir_nosave;
 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 @@ static int saveable(struct zone *zone, unsigned long *zone_pfn)
        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;
@@ -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,
  *     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)
        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 b09bd7c0998d265b485670fcce75066d51fe8707..f77f9397a36455def5df2969d2efd4b2dd2d389e 100644 (file)
 #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,52 @@ int swsusp_write(struct pbe *pblist, unsigned int nr_pages)
        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 error;
@@ -1030,8 +1078,10 @@ static int read_suspend_image(struct pbe **pblist_ptr)
                /* 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;
        }