aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@suse.cz>2011-03-22 19:30:52 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2011-03-22 20:44:02 -0400
commitef6a3c63112e865d632ff7c478ba7c7160cad0d1 (patch)
treed0bd3ee2b79674e22b8dd3f318814cd4789697b8
parent318b275fbca1ab9ec0862de71420e0e92c3d1aa7 (diff)
mm: add replace_page_cache_page() function
This function basically does: remove_from_page_cache(old); page_cache_release(old); add_to_page_cache_locked(new); Except it does this atomically, so there's no possibility for the "add" to fail because of a race. If memory cgroups are enabled, then the memory cgroup charge is also moved from the old page to the new. This function is currently used by fuse to move pages into the page cache on read, instead of copying the page contents. [minchan.kim@gmail.com: add freepage() hook to replace_page_cache_page()] Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Acked-by: Rik van Riel <riel@redhat.com> Acked-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Cc: Mel Gorman <mel@csn.ul.ie> Signed-off-by: Minchan Kim <minchan.kim@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--fs/fuse/dev.c10
-rw-r--r--include/linux/memcontrol.h4
-rw-r--r--include/linux/pagemap.h1
-rw-r--r--mm/filemap.c70
-rw-r--r--mm/memcontrol.c4
-rw-r--r--mm/migrate.c2
6 files changed, 80 insertions, 11 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 213d3cf4f5e..640fc229df1 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -737,14 +737,12 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep)
737 if (WARN_ON(PageMlocked(oldpage))) 737 if (WARN_ON(PageMlocked(oldpage)))
738 goto out_fallback_unlock; 738 goto out_fallback_unlock;
739 739
740 remove_from_page_cache(oldpage); 740 err = replace_page_cache_page(oldpage, newpage, GFP_KERNEL);
741 page_cache_release(oldpage);
742
743 err = add_to_page_cache_locked(newpage, mapping, index, GFP_KERNEL);
744 if (err) { 741 if (err) {
745 printk(KERN_WARNING "fuse_try_move_page: failed to add page"); 742 unlock_page(newpage);
746 goto out_fallback_unlock; 743 return err;
747 } 744 }
745
748 page_cache_get(newpage); 746 page_cache_get(newpage);
749 747
750 if (!(buf->flags & PIPE_BUF_FLAG_LRU)) 748 if (!(buf->flags & PIPE_BUF_FLAG_LRU))
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index f512e189be5..a1a1e5384f6 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -96,7 +96,7 @@ extern struct cgroup_subsys_state *mem_cgroup_css(struct mem_cgroup *mem);
96 96
97extern int 97extern int
98mem_cgroup_prepare_migration(struct page *page, 98mem_cgroup_prepare_migration(struct page *page,
99 struct page *newpage, struct mem_cgroup **ptr); 99 struct page *newpage, struct mem_cgroup **ptr, gfp_t gfp_mask);
100extern void mem_cgroup_end_migration(struct mem_cgroup *mem, 100extern void mem_cgroup_end_migration(struct mem_cgroup *mem,
101 struct page *oldpage, struct page *newpage, bool migration_ok); 101 struct page *oldpage, struct page *newpage, bool migration_ok);
102 102
@@ -249,7 +249,7 @@ static inline struct cgroup_subsys_state *mem_cgroup_css(struct mem_cgroup *mem)
249 249
250static inline int 250static inline int
251mem_cgroup_prepare_migration(struct page *page, struct page *newpage, 251mem_cgroup_prepare_migration(struct page *page, struct page *newpage,
252 struct mem_cgroup **ptr) 252 struct mem_cgroup **ptr, gfp_t gfp_mask)
253{ 253{
254 return 0; 254 return 0;
255} 255}
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 9c66e994540..26946ad483b 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -457,6 +457,7 @@ int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
457 pgoff_t index, gfp_t gfp_mask); 457 pgoff_t index, gfp_t gfp_mask);
458extern void remove_from_page_cache(struct page *page); 458extern void remove_from_page_cache(struct page *page);
459extern void __remove_from_page_cache(struct page *page); 459extern void __remove_from_page_cache(struct page *page);
460int replace_page_cache_page(struct page *old, struct page *new, gfp_t gfp_mask);
460 461
461/* 462/*
462 * Like add_to_page_cache_locked, but used to add newly allocated pages: 463 * Like add_to_page_cache_locked, but used to add newly allocated pages:
diff --git a/mm/filemap.c b/mm/filemap.c
index 312b6eb7843..c1459f2cdb5 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -387,6 +387,76 @@ int filemap_write_and_wait_range(struct address_space *mapping,
387EXPORT_SYMBOL(filemap_write_and_wait_range); 387EXPORT_SYMBOL(filemap_write_and_wait_range);
388 388
389/** 389/**
390 * replace_page_cache_page - replace a pagecache page with a new one
391 * @old: page to be replaced
392 * @new: page to replace with
393 * @gfp_mask: allocation mode
394 *
395 * This function replaces a page in the pagecache with a new one. On
396 * success it acquires the pagecache reference for the new page and
397 * drops it for the old page. Both the old and new pages must be
398 * locked. This function does not add the new page to the LRU, the
399 * caller must do that.
400 *
401 * The remove + add is atomic. The only way this function can fail is
402 * memory allocation failure.
403 */
404int replace_page_cache_page(struct page *old, struct page *new, gfp_t gfp_mask)
405{
406 int error;
407 struct mem_cgroup *memcg = NULL;
408
409 VM_BUG_ON(!PageLocked(old));
410 VM_BUG_ON(!PageLocked(new));
411 VM_BUG_ON(new->mapping);
412
413 /*
414 * This is not page migration, but prepare_migration and
415 * end_migration does enough work for charge replacement.
416 *
417 * In the longer term we probably want a specialized function
418 * for moving the charge from old to new in a more efficient
419 * manner.
420 */
421 error = mem_cgroup_prepare_migration(old, new, &memcg, gfp_mask);
422 if (error)
423 return error;
424
425 error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM);
426 if (!error) {
427 struct address_space *mapping = old->mapping;
428 void (*freepage)(struct page *);
429
430 pgoff_t offset = old->index;
431 freepage = mapping->a_ops->freepage;
432
433 page_cache_get(new);
434 new->mapping = mapping;
435 new->index = offset;
436
437 spin_lock_irq(&mapping->tree_lock);
438 __remove_from_page_cache(old);
439 error = radix_tree_insert(&mapping->page_tree, offset, new);
440 BUG_ON(error);
441 mapping->nrpages++;
442 __inc_zone_page_state(new, NR_FILE_PAGES);
443 if (PageSwapBacked(new))
444 __inc_zone_page_state(new, NR_SHMEM);
445 spin_unlock_irq(&mapping->tree_lock);
446 radix_tree_preload_end();
447 if (freepage)
448 freepage(old);
449 page_cache_release(old);
450 mem_cgroup_end_migration(memcg, old, new, true);
451 } else {
452 mem_cgroup_end_migration(memcg, old, new, false);
453 }
454
455 return error;
456}
457EXPORT_SYMBOL_GPL(replace_page_cache_page);
458
459/**
390 * add_to_page_cache_locked - add a locked page to the pagecache 460 * add_to_page_cache_locked - add a locked page to the pagecache
391 * @page: page to add 461 * @page: page to add
392 * @mapping: the page's address_space 462 * @mapping: the page's address_space
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index da53a252b25..6ef5c53dffc 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -2883,7 +2883,7 @@ static inline int mem_cgroup_move_swap_account(swp_entry_t entry,
2883 * page belongs to. 2883 * page belongs to.
2884 */ 2884 */
2885int mem_cgroup_prepare_migration(struct page *page, 2885int mem_cgroup_prepare_migration(struct page *page,
2886 struct page *newpage, struct mem_cgroup **ptr) 2886 struct page *newpage, struct mem_cgroup **ptr, gfp_t gfp_mask)
2887{ 2887{
2888 struct page_cgroup *pc; 2888 struct page_cgroup *pc;
2889 struct mem_cgroup *mem = NULL; 2889 struct mem_cgroup *mem = NULL;
@@ -2940,7 +2940,7 @@ int mem_cgroup_prepare_migration(struct page *page,
2940 return 0; 2940 return 0;
2941 2941
2942 *ptr = mem; 2942 *ptr = mem;
2943 ret = __mem_cgroup_try_charge(NULL, GFP_KERNEL, ptr, false, PAGE_SIZE); 2943 ret = __mem_cgroup_try_charge(NULL, gfp_mask, ptr, false, PAGE_SIZE);
2944 css_put(&mem->css);/* drop extra refcnt */ 2944 css_put(&mem->css);/* drop extra refcnt */
2945 if (ret || *ptr == NULL) { 2945 if (ret || *ptr == NULL) {
2946 if (PageAnon(page)) { 2946 if (PageAnon(page)) {
diff --git a/mm/migrate.c b/mm/migrate.c
index 352de555626..8aacce3af8c 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -678,7 +678,7 @@ static int unmap_and_move(new_page_t get_new_page, unsigned long private,
678 } 678 }
679 679
680 /* charge against new page */ 680 /* charge against new page */
681 charge = mem_cgroup_prepare_migration(page, newpage, &mem); 681 charge = mem_cgroup_prepare_migration(page, newpage, &mem, GFP_KERNEL);
682 if (charge == -ENOMEM) { 682 if (charge == -ENOMEM) {
683 rc = -ENOMEM; 683 rc = -ENOMEM;
684 goto unlock; 684 goto unlock;