[PATCH] swsusp: use less memory during resume
Make swsusp allocate only as much memory as needed to store the image data and metadata during resume. Without this patch swsusp additionally allocates many page frames that will conflict with the "original" locations of the image data and are considered as "unsafe", treating them as "eaten" pages (ie. allocated but unusable). The patch makes swsusp allocate as many pages as it'll need to store the data read from the image in one shot, creating a list of allocated "safe" pages, and use the observation that all pages allocated by it are marked with the PG_nosave and PG_nosave_free flags set. Namely, when it's about to load an image page, swsusp can check whether the page frame corresponding to the "original" location of this page has been allocated (ie. if the page frame has the PG_nosave and PG_nosave_free flags set) and if so, it can load the page directly into this page frame. Otherwise it uses an allocated "safe" page from the list to store the data that will be copied to their "original" location later on. This allows us to save many page copyings and page allocations during resume and in the future it may allow us to load images greater than 50% of the normal zone. 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>
This commit is contained in:
parent
7bff24e255
commit
968808b895
|
@ -55,7 +55,7 @@ struct snapshot_handle {
|
||||||
unsigned int page;
|
unsigned int page;
|
||||||
unsigned int page_offset;
|
unsigned int page_offset;
|
||||||
unsigned int prev;
|
unsigned int prev;
|
||||||
struct pbe *pbe;
|
struct pbe *pbe, *last_pbe;
|
||||||
void *buffer;
|
void *buffer;
|
||||||
unsigned int buf_offset;
|
unsigned int buf_offset;
|
||||||
};
|
};
|
||||||
|
|
|
@ -401,62 +401,29 @@ static inline void create_pbe_list(struct pbe *pblist, unsigned int nr_pages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static unsigned int unsafe_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;
|
|
||||||
|
|
||||||
static 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
|
||||||
* which had been used before suspend.
|
* used before suspend.
|
||||||
*
|
*
|
||||||
* The unsafe pages are marked with the PG_nosave_free flag
|
* The unsafe pages are marked with the PG_nosave_free flag
|
||||||
*
|
* and we count them using unsafe_pages
|
||||||
* Allocated but unusable (ie eaten) memory pages should be marked
|
|
||||||
* so that swsusp_free() can release them
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static inline void *alloc_image_page(gfp_t gfp_mask, int safe_needed)
|
static inline void *alloc_image_page(gfp_t gfp_mask, int safe_needed)
|
||||||
{
|
{
|
||||||
void *res;
|
void *res;
|
||||||
|
|
||||||
|
res = (void *)get_zeroed_page(gfp_mask);
|
||||||
if (safe_needed)
|
if (safe_needed)
|
||||||
do {
|
while (res && PageNosaveFree(virt_to_page(res))) {
|
||||||
|
/* The page is unsafe, mark it for swsusp_free() */
|
||||||
|
SetPageNosave(virt_to_page(res));
|
||||||
|
unsafe_pages++;
|
||||||
res = (void *)get_zeroed_page(gfp_mask);
|
res = (void *)get_zeroed_page(gfp_mask);
|
||||||
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);
|
|
||||||
if (res) {
|
if (res) {
|
||||||
SetPageNosave(virt_to_page(res));
|
SetPageNosave(virt_to_page(res));
|
||||||
SetPageNosaveFree(virt_to_page(res));
|
SetPageNosaveFree(virt_to_page(res));
|
||||||
|
@ -751,6 +718,8 @@ static int mark_unsafe_pages(struct pbe *pblist)
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe_pages = 0;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,42 +797,99 @@ static inline struct pbe *unpack_orig_addresses(unsigned long *buf,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create_image - use metadata contained in the PBE list
|
* prepare_image - use metadata contained in the PBE list
|
||||||
* pointed to by pagedir_nosave to mark the pages that will
|
* pointed to by pagedir_nosave to mark the pages that will
|
||||||
* be overwritten in the process of restoring the system
|
* be overwritten in the process of restoring the system
|
||||||
* memory state from the image and allocate memory for
|
* memory state from the image ("unsafe" pages) and allocate
|
||||||
* the image avoiding these pages
|
* memory for the image
|
||||||
|
*
|
||||||
|
* The idea is to allocate the PBE list first and then
|
||||||
|
* allocate as many pages as it's needed for the image data,
|
||||||
|
* but not to assign these pages to the PBEs initially.
|
||||||
|
* Instead, we just mark them as allocated and create a list
|
||||||
|
* of "safe" which will be used later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int create_image(struct snapshot_handle *handle)
|
struct safe_page {
|
||||||
|
struct safe_page *next;
|
||||||
|
char padding[PAGE_SIZE - sizeof(void *)];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct safe_page *safe_pages;
|
||||||
|
|
||||||
|
static int prepare_image(struct snapshot_handle *handle)
|
||||||
{
|
{
|
||||||
int error = 0;
|
int error = 0;
|
||||||
struct pbe *p, *pblist;
|
unsigned int nr_pages = nr_copy_pages;
|
||||||
|
struct pbe *p, *pblist = NULL;
|
||||||
|
|
||||||
p = pagedir_nosave;
|
p = pagedir_nosave;
|
||||||
error = mark_unsafe_pages(p);
|
error = mark_unsafe_pages(p);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
pblist = alloc_pagedir(nr_copy_pages, GFP_ATOMIC, 1);
|
pblist = alloc_pagedir(nr_pages, GFP_ATOMIC, 1);
|
||||||
if (pblist)
|
if (pblist)
|
||||||
copy_page_backup_list(pblist, p);
|
copy_page_backup_list(pblist, p);
|
||||||
free_pagedir(p, 0);
|
free_pagedir(p, 0);
|
||||||
if (!pblist)
|
if (!pblist)
|
||||||
error = -ENOMEM;
|
error = -ENOMEM;
|
||||||
}
|
}
|
||||||
if (!error)
|
safe_pages = NULL;
|
||||||
error = alloc_data_pages(pblist, GFP_ATOMIC, 1);
|
if (!error && nr_pages > unsafe_pages) {
|
||||||
|
nr_pages -= unsafe_pages;
|
||||||
|
while (nr_pages--) {
|
||||||
|
struct safe_page *ptr;
|
||||||
|
|
||||||
|
ptr = (struct safe_page *)get_zeroed_page(GFP_ATOMIC);
|
||||||
|
if (!ptr) {
|
||||||
|
error = -ENOMEM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!PageNosaveFree(virt_to_page(ptr))) {
|
||||||
|
/* The page is "safe", add it to the list */
|
||||||
|
ptr->next = safe_pages;
|
||||||
|
safe_pages = ptr;
|
||||||
|
}
|
||||||
|
/* Mark the page as allocated */
|
||||||
|
SetPageNosave(virt_to_page(ptr));
|
||||||
|
SetPageNosaveFree(virt_to_page(ptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
release_eaten_pages();
|
|
||||||
pagedir_nosave = pblist;
|
pagedir_nosave = pblist;
|
||||||
} else {
|
} else {
|
||||||
pagedir_nosave = NULL;
|
|
||||||
handle->pbe = NULL;
|
handle->pbe = NULL;
|
||||||
nr_copy_pages = 0;
|
swsusp_free();
|
||||||
nr_meta_pages = 0;
|
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *get_buffer(struct snapshot_handle *handle)
|
||||||
|
{
|
||||||
|
struct pbe *pbe = handle->pbe, *last = handle->last_pbe;
|
||||||
|
struct page *page = virt_to_page(pbe->orig_address);
|
||||||
|
|
||||||
|
if (PageNosave(page) && PageNosaveFree(page)) {
|
||||||
|
/*
|
||||||
|
* We have allocated the "original" page frame and we can
|
||||||
|
* use it directly to store the read page
|
||||||
|
*/
|
||||||
|
pbe->address = 0;
|
||||||
|
if (last && last->next)
|
||||||
|
last->next = NULL;
|
||||||
|
return (void *)pbe->orig_address;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* The "original" page frame has not been allocated and we have to
|
||||||
|
* use a "safe" page frame to store the read page
|
||||||
|
*/
|
||||||
|
pbe->address = (unsigned long)safe_pages;
|
||||||
|
safe_pages = safe_pages->next;
|
||||||
|
if (last)
|
||||||
|
last->next = pbe;
|
||||||
|
handle->last_pbe = pbe;
|
||||||
|
return (void *)pbe->address;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snapshot_write_next - used for writing the system memory snapshot.
|
* snapshot_write_next - used for writing the system memory snapshot.
|
||||||
*
|
*
|
||||||
|
@ -908,15 +934,16 @@ int snapshot_write_next(struct snapshot_handle *handle, size_t count)
|
||||||
} else if (handle->prev <= nr_meta_pages) {
|
} else if (handle->prev <= nr_meta_pages) {
|
||||||
handle->pbe = unpack_orig_addresses(buffer, handle->pbe);
|
handle->pbe = unpack_orig_addresses(buffer, handle->pbe);
|
||||||
if (!handle->pbe) {
|
if (!handle->pbe) {
|
||||||
error = create_image(handle);
|
error = prepare_image(handle);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
handle->pbe = pagedir_nosave;
|
handle->pbe = pagedir_nosave;
|
||||||
handle->buffer = (void *)handle->pbe->address;
|
handle->last_pbe = NULL;
|
||||||
|
handle->buffer = get_buffer(handle);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handle->pbe = handle->pbe->next;
|
handle->pbe = handle->pbe->next;
|
||||||
handle->buffer = (void *)handle->pbe->address;
|
handle->buffer = get_buffer(handle);
|
||||||
}
|
}
|
||||||
handle->prev = handle->page;
|
handle->prev = handle->page;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue