[PATCH] swsusp: improve freeing of memory
Rafael J. Wysocki [Fri, 6 Jan 2006 08:13:46 +0000 (00:13 -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 33bbaea..5dc94e7 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 76a5131..9e51cdf 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 977877c..acdc83b 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 152d56c..e80d282 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;
@@ -267,6 +290,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
  *     which had been used before suspend.
@@ -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 b09bd7c..f77f939 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;
        }