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)
ret = -ENOMEM;
goto out;
}
add_to_page_cache(page, mapping, idx);
ret = add_to_page_cache(page, mapping, idx, GFP_ATOMIC);
unlock_page(page);
if (ret) {
free_huge_page(page);
goto out;
}
}
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
page = alloc_hugetlb_page();
if (page == NULL) {
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;
}
add_to_page_cache(page, mapping, idx);
}
set_huge_pte(mm, vma, page, pte,
(vma->vm_flags & VM_WRITE));
......
......@@ -232,8 +232,12 @@ int hugetlb_prefault(struct address_space *mapping, struct vm_area_struct *vma)
ret = -ENOMEM;
goto out;
}
add_to_page_cache(page, mapping, idx);
ret = add_to_page_cache(page, mapping, idx, GFP_ATOMIC);
unlock_page(page);
if (ret) {
free_huge_page(page);
goto out;
}
}
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,
prefetchw(&page->flags);
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,
nr_pages - page_idx,
&last_block_in_bio, get_block);
......
......@@ -66,10 +66,10 @@ extern struct page * read_cache_page(struct address_space *mapping,
extern int read_cache_pages(struct address_space *mapping,
struct list_head *pages, filler_t *filler, void *data);
extern int add_to_page_cache(struct page *page,
struct address_space *mapping, unsigned long index);
extern int add_to_page_cache_lru(struct page *page,
struct address_space *mapping, unsigned long index);
int add_to_page_cache(struct page *page, struct address_space *mapping,
unsigned long index, int gfp_mask);
int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
unsigned long index, int gfp_mask);
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)
*
* This function does not add the page to the LRU. The caller must do that.
*/
int add_to_page_cache(struct page *page,
struct address_space *mapping, pgoff_t offset)
int add_to_page_cache(struct page *page, struct address_space *mapping,
pgoff_t offset, int gfp_mask)
{
int error;
int error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM);
page_cache_get(page);
write_lock(&mapping->page_lock);
error = radix_tree_insert(&mapping->page_tree, offset, page);
if (!error) {
SetPageLocked(page);
___add_to_page_cache(page, mapping, offset);
} else {
page_cache_release(page);
if (error == 0) {
page_cache_get(page);
write_lock(&mapping->page_lock);
error = radix_tree_insert(&mapping->page_tree, offset, page);
if (!error) {
SetPageLocked(page);
___add_to_page_cache(page, mapping, offset);
} else {
page_cache_release(page);
}
write_unlock(&mapping->page_lock);
radix_tree_preload_end();
}
write_unlock(&mapping->page_lock);
return error;
}
int add_to_page_cache_lru(struct page *page,
struct address_space *mapping, pgoff_t offset)
int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
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)
lru_cache_add(page);
return ret;
......@@ -432,7 +435,8 @@ struct page *find_or_create_page(struct address_space *mapping,
if (!cached_page)
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) {
page = cached_page;
cached_page = NULL;
......@@ -488,6 +492,7 @@ struct page *
grab_cache_page_nowait(struct address_space *mapping, unsigned long index)
{
struct page *page = find_get_page(mapping, index);
int gfp_mask;
if (page) {
if (!TestSetPageLocked(page))
......@@ -495,8 +500,9 @@ grab_cache_page_nowait(struct address_space *mapping, unsigned long index)
page_cache_release(page);
return NULL;
}
page = alloc_pages(mapping->gfp_mask & ~__GFP_FS, 0);
if (page && add_to_page_cache_lru(page, mapping, index)) {
gfp_mask = mapping->gfp_mask & ~__GFP_FS;
page = alloc_pages(gfp_mask, 0);
if (page && add_to_page_cache_lru(page, mapping, index, gfp_mask)) {
page_cache_release(page);
page = NULL;
}
......@@ -648,7 +654,8 @@ void do_generic_mapping_read(struct address_space *mapping,
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 == -EEXIST)
goto find_page;
......@@ -940,7 +947,7 @@ static int page_cache_read(struct file * file, unsigned long offset)
if (!page)
return -ENOMEM;
error = add_to_page_cache_lru(page, mapping, offset);
error = add_to_page_cache_lru(page, mapping, offset, GFP_KERNEL);
if (!error) {
error = mapping->a_ops->readpage(file, page);
page_cache_release(page);
......@@ -1327,7 +1334,8 @@ static inline struct page *__read_cache_page(struct address_space *mapping,
if (!cached_page)
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)
goto repeat;
if (err < 0) {
......@@ -1406,7 +1414,8 @@ __grab_cache_page(struct address_space *mapping, unsigned long index,
if (!*cached_page)
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)
goto repeat;
if (err == 0) {
......
......@@ -65,7 +65,7 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages,
while (!list_empty(pages)) {
page = list_entry(pages->prev, struct 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);
continue;
}
......@@ -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++) {
struct page *page = list_entry(pages->prev, struct 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);
if (!pagevec_add(&lru_pvec, page))
__pagevec_lru_add(&lru_pvec);
......
......@@ -889,7 +889,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p
}
if (error || swap.val ||
(error = add_to_page_cache_lru(
filepage, mapping, idx))) {
filepage, mapping, idx, GFP_ATOMIC))) {
spin_unlock(&info->lock);
page_cache_release(filepage);
shmem_free_block(inode);
......
......@@ -78,7 +78,7 @@ int add_to_swap_cache(struct page *page, swp_entry_t entry)
INC_CACHE_INFO(noent_race);
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.
*/
......@@ -149,7 +149,8 @@ int add_to_swap(struct page * page)
/*
* 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)
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