Commit c3ed96a7 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] handle radix_tree_node allocation failures

This patch uses the radix_tree_preload() API in add_to_page_cache().

A new gfp_mask argument is added to add_to_page_cache(), which is then passed
on to radix_tree_preload().   It's pretty simple.

In the case of adding pages to swapcache we're still using GFP_ATOMIC, so
these addition attempts can still fail.  That's OK, because the error is
handled and, unlike file pages, it will not cause user applicaton failures.
This codepath (radix-tree node exhaustion on swapout) was well tested in the
days when the swapper_space radix tree was fragmented all over the place due
to unfortunate swp_entry bit layout.
parent 9fb6fde9
...@@ -234,8 +234,12 @@ int hugetlb_prefault(struct address_space *mapping, struct vm_area_struct *vma) ...@@ -234,8 +234,12 @@ int hugetlb_prefault(struct address_space *mapping, struct vm_area_struct *vma)
ret = -ENOMEM; ret = -ENOMEM;
goto out; goto out;
} }
add_to_page_cache(page, mapping, idx); ret = add_to_page_cache(page, mapping, idx, GFP_ATOMIC);
unlock_page(page); unlock_page(page);
if (ret) {
free_huge_page(page);
goto out;
}
} }
set_huge_pte(mm, vma, page, pte, vma->vm_flags & VM_WRITE); set_huge_pte(mm, vma, page, pte, vma->vm_flags & VM_WRITE);
} }
......
...@@ -476,9 +476,16 @@ static int alloc_shared_hugetlb_pages(int key, unsigned long addr, unsigned long ...@@ -476,9 +476,16 @@ static int alloc_shared_hugetlb_pages(int key, unsigned long addr, unsigned long
page = alloc_hugetlb_page(); page = alloc_hugetlb_page();
if (page == NULL) { if (page == NULL) {
pte_unmap(pte); pte_unmap(pte);
retval = -ENOMEM;
goto out;
}
retval = add_to_page_cache(page, mapping,
idx, GFP_ATOMIC);
if (retval) {
pte_unmap(pte);
free_hugetlb_page(page);
goto out; goto out;
} }
add_to_page_cache(page, mapping, idx);
} }
set_huge_pte(mm, vma, page, pte, set_huge_pte(mm, vma, page, pte,
(vma->vm_flags & VM_WRITE)); (vma->vm_flags & VM_WRITE));
......
...@@ -232,8 +232,12 @@ int hugetlb_prefault(struct address_space *mapping, struct vm_area_struct *vma) ...@@ -232,8 +232,12 @@ int hugetlb_prefault(struct address_space *mapping, struct vm_area_struct *vma)
ret = -ENOMEM; ret = -ENOMEM;
goto out; goto out;
} }
add_to_page_cache(page, mapping, idx); ret = add_to_page_cache(page, mapping, idx, GFP_ATOMIC);
unlock_page(page); unlock_page(page);
if (ret) {
free_huge_page(page);
goto out;
}
} }
set_huge_pte(mm, vma, page, pte, vma->vm_flags & VM_WRITE); set_huge_pte(mm, vma, page, pte, vma->vm_flags & VM_WRITE);
} }
......
...@@ -275,7 +275,8 @@ mpage_readpages(struct address_space *mapping, struct list_head *pages, ...@@ -275,7 +275,8 @@ mpage_readpages(struct address_space *mapping, struct list_head *pages,
prefetchw(&page->flags); prefetchw(&page->flags);
list_del(&page->list); list_del(&page->list);
if (!add_to_page_cache(page, mapping, page->index)) { if (!add_to_page_cache(page, mapping,
page->index, GFP_KERNEL)) {
bio = do_mpage_readpage(bio, page, bio = do_mpage_readpage(bio, page,
nr_pages - page_idx, nr_pages - page_idx,
&last_block_in_bio, get_block); &last_block_in_bio, get_block);
......
...@@ -66,10 +66,10 @@ extern struct page * read_cache_page(struct address_space *mapping, ...@@ -66,10 +66,10 @@ extern struct page * read_cache_page(struct address_space *mapping,
extern int read_cache_pages(struct address_space *mapping, extern int read_cache_pages(struct address_space *mapping,
struct list_head *pages, filler_t *filler, void *data); struct list_head *pages, filler_t *filler, void *data);
extern int add_to_page_cache(struct page *page, int add_to_page_cache(struct page *page, struct address_space *mapping,
struct address_space *mapping, unsigned long index); unsigned long index, int gfp_mask);
extern int add_to_page_cache_lru(struct page *page, int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
struct address_space *mapping, unsigned long index); unsigned long index, int gfp_mask);
extern void remove_from_page_cache(struct page *page); extern void remove_from_page_cache(struct page *page);
extern void __remove_from_page_cache(struct page *page); extern void __remove_from_page_cache(struct page *page);
......
...@@ -202,28 +202,31 @@ int filemap_fdatawait(struct address_space * mapping) ...@@ -202,28 +202,31 @@ int filemap_fdatawait(struct address_space * mapping)
* *
* This function does not add the page to the LRU. The caller must do that. * This function does not add the page to the LRU. The caller must do that.
*/ */
int add_to_page_cache(struct page *page, int add_to_page_cache(struct page *page, struct address_space *mapping,
struct address_space *mapping, pgoff_t offset) pgoff_t offset, int gfp_mask)
{ {
int error; int error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM);
page_cache_get(page); if (error == 0) {
write_lock(&mapping->page_lock); page_cache_get(page);
error = radix_tree_insert(&mapping->page_tree, offset, page); write_lock(&mapping->page_lock);
if (!error) { error = radix_tree_insert(&mapping->page_tree, offset, page);
SetPageLocked(page); if (!error) {
___add_to_page_cache(page, mapping, offset); SetPageLocked(page);
} else { ___add_to_page_cache(page, mapping, offset);
page_cache_release(page); } else {
page_cache_release(page);
}
write_unlock(&mapping->page_lock);
radix_tree_preload_end();
} }
write_unlock(&mapping->page_lock);
return error; return error;
} }
int add_to_page_cache_lru(struct page *page, int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
struct address_space *mapping, pgoff_t offset) pgoff_t offset, int gfp_mask)
{ {
int ret = add_to_page_cache(page, mapping, offset); int ret = add_to_page_cache(page, mapping, offset, gfp_mask);
if (ret == 0) if (ret == 0)
lru_cache_add(page); lru_cache_add(page);
return ret; return ret;
...@@ -432,7 +435,8 @@ struct page *find_or_create_page(struct address_space *mapping, ...@@ -432,7 +435,8 @@ struct page *find_or_create_page(struct address_space *mapping,
if (!cached_page) if (!cached_page)
return NULL; return NULL;
} }
err = add_to_page_cache_lru(cached_page, mapping, index); err = add_to_page_cache_lru(cached_page, mapping,
index, gfp_mask);
if (!err) { if (!err) {
page = cached_page; page = cached_page;
cached_page = NULL; cached_page = NULL;
...@@ -488,6 +492,7 @@ struct page * ...@@ -488,6 +492,7 @@ struct page *
grab_cache_page_nowait(struct address_space *mapping, unsigned long index) grab_cache_page_nowait(struct address_space *mapping, unsigned long index)
{ {
struct page *page = find_get_page(mapping, index); struct page *page = find_get_page(mapping, index);
int gfp_mask;
if (page) { if (page) {
if (!TestSetPageLocked(page)) if (!TestSetPageLocked(page))
...@@ -495,8 +500,9 @@ grab_cache_page_nowait(struct address_space *mapping, unsigned long index) ...@@ -495,8 +500,9 @@ grab_cache_page_nowait(struct address_space *mapping, unsigned long index)
page_cache_release(page); page_cache_release(page);
return NULL; return NULL;
} }
page = alloc_pages(mapping->gfp_mask & ~__GFP_FS, 0); gfp_mask = mapping->gfp_mask & ~__GFP_FS;
if (page && add_to_page_cache_lru(page, mapping, index)) { page = alloc_pages(gfp_mask, 0);
if (page && add_to_page_cache_lru(page, mapping, index, gfp_mask)) {
page_cache_release(page); page_cache_release(page);
page = NULL; page = NULL;
} }
...@@ -648,7 +654,8 @@ void do_generic_mapping_read(struct address_space *mapping, ...@@ -648,7 +654,8 @@ void do_generic_mapping_read(struct address_space *mapping,
break; break;
} }
} }
error = add_to_page_cache_lru(cached_page, mapping, index); error = add_to_page_cache_lru(cached_page, mapping,
index, GFP_KERNEL);
if (error) { if (error) {
if (error == -EEXIST) if (error == -EEXIST)
goto find_page; goto find_page;
...@@ -940,7 +947,7 @@ static int page_cache_read(struct file * file, unsigned long offset) ...@@ -940,7 +947,7 @@ static int page_cache_read(struct file * file, unsigned long offset)
if (!page) if (!page)
return -ENOMEM; return -ENOMEM;
error = add_to_page_cache_lru(page, mapping, offset); error = add_to_page_cache_lru(page, mapping, offset, GFP_KERNEL);
if (!error) { if (!error) {
error = mapping->a_ops->readpage(file, page); error = mapping->a_ops->readpage(file, page);
page_cache_release(page); page_cache_release(page);
...@@ -1327,7 +1334,8 @@ static inline struct page *__read_cache_page(struct address_space *mapping, ...@@ -1327,7 +1334,8 @@ static inline struct page *__read_cache_page(struct address_space *mapping,
if (!cached_page) if (!cached_page)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
err = add_to_page_cache_lru(cached_page, mapping, index); err = add_to_page_cache_lru(cached_page, mapping,
index, GFP_KERNEL);
if (err == -EEXIST) if (err == -EEXIST)
goto repeat; goto repeat;
if (err < 0) { if (err < 0) {
...@@ -1406,7 +1414,8 @@ __grab_cache_page(struct address_space *mapping, unsigned long index, ...@@ -1406,7 +1414,8 @@ __grab_cache_page(struct address_space *mapping, unsigned long index,
if (!*cached_page) if (!*cached_page)
return NULL; return NULL;
} }
err = add_to_page_cache(*cached_page, mapping, index); err = add_to_page_cache(*cached_page, mapping,
index, GFP_KERNEL);
if (err == -EEXIST) if (err == -EEXIST)
goto repeat; goto repeat;
if (err == 0) { if (err == 0) {
......
...@@ -65,7 +65,7 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages, ...@@ -65,7 +65,7 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages,
while (!list_empty(pages)) { while (!list_empty(pages)) {
page = list_entry(pages->prev, struct page, list); page = list_entry(pages->prev, struct page, list);
list_del(&page->list); list_del(&page->list);
if (add_to_page_cache(page, mapping, page->index)) { if (add_to_page_cache(page, mapping, page->index, GFP_KERNEL)) {
page_cache_release(page); page_cache_release(page);
continue; continue;
} }
...@@ -93,7 +93,8 @@ static int read_pages(struct address_space *mapping, struct file *filp, ...@@ -93,7 +93,8 @@ static int read_pages(struct address_space *mapping, struct file *filp,
for (page_idx = 0; page_idx < nr_pages; page_idx++) { for (page_idx = 0; page_idx < nr_pages; page_idx++) {
struct page *page = list_entry(pages->prev, struct page, list); struct page *page = list_entry(pages->prev, struct page, list);
list_del(&page->list); list_del(&page->list);
if (!add_to_page_cache(page, mapping, page->index)) { if (!add_to_page_cache(page, mapping,
page->index, GFP_KERNEL)) {
mapping->a_ops->readpage(filp, page); mapping->a_ops->readpage(filp, page);
if (!pagevec_add(&lru_pvec, page)) if (!pagevec_add(&lru_pvec, page))
__pagevec_lru_add(&lru_pvec); __pagevec_lru_add(&lru_pvec);
......
...@@ -889,7 +889,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p ...@@ -889,7 +889,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p
} }
if (error || swap.val || if (error || swap.val ||
(error = add_to_page_cache_lru( (error = add_to_page_cache_lru(
filepage, mapping, idx))) { filepage, mapping, idx, GFP_ATOMIC))) {
spin_unlock(&info->lock); spin_unlock(&info->lock);
page_cache_release(filepage); page_cache_release(filepage);
shmem_free_block(inode); shmem_free_block(inode);
......
...@@ -78,7 +78,7 @@ int add_to_swap_cache(struct page *page, swp_entry_t entry) ...@@ -78,7 +78,7 @@ int add_to_swap_cache(struct page *page, swp_entry_t entry)
INC_CACHE_INFO(noent_race); INC_CACHE_INFO(noent_race);
return -ENOENT; return -ENOENT;
} }
error = add_to_page_cache(page, &swapper_space, entry.val); error = add_to_page_cache(page, &swapper_space, entry.val, GFP_ATOMIC);
/* /*
* Anon pages are already on the LRU, we don't run lru_cache_add here. * Anon pages are already on the LRU, we don't run lru_cache_add here.
*/ */
...@@ -149,7 +149,8 @@ int add_to_swap(struct page * page) ...@@ -149,7 +149,8 @@ int add_to_swap(struct page * page)
/* /*
* Add it to the swap cache and mark it dirty * Add it to the swap cache and mark it dirty
*/ */
err = add_to_page_cache(page, &swapper_space, entry.val); err = add_to_page_cache(page, &swapper_space,
entry.val, GFP_ATOMIC);
if (pf_flags & PF_MEMALLOC) if (pf_flags & PF_MEMALLOC)
current->flags |= PF_MEMALLOC; current->flags |= PF_MEMALLOC;
......
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