aboutsummaryrefslogtreecommitdiffstats
path: root/mm/rmap.c
diff options
context:
space:
mode:
authorJohannes Weiner <hannes@cmpxchg.org>2014-10-29 17:50:48 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2014-10-29 19:33:15 -0400
commitd7365e783edb858279be1d03f61bc8d5d3383d90 (patch)
treeca8c1aea5763cace1eb63022cfea83c480eef487 /mm/rmap.c
parent3a3c02ecf7f2852f122d6d16fb9b3d9cb0c6f201 (diff)
mm: memcontrol: fix missed end-writeback page accounting
Commit 0a31bc97c80c ("mm: memcontrol: rewrite uncharge API") changed page migration to uncharge the old page right away. The page is locked, unmapped, truncated, and off the LRU, but it could race with writeback ending, which then doesn't unaccount the page properly: test_clear_page_writeback() migration wait_on_page_writeback() TestClearPageWriteback() mem_cgroup_migrate() clear PCG_USED mem_cgroup_update_page_stat() if (PageCgroupUsed(pc)) decrease memcg pages under writeback release pc->mem_cgroup->move_lock The per-page statistics interface is heavily optimized to avoid a function call and a lookup_page_cgroup() in the file unmap fast path, which means it doesn't verify whether a page is still charged before clearing PageWriteback() and it has to do it in the stat update later. Rework it so that it looks up the page's memcg once at the beginning of the transaction and then uses it throughout. The charge will be verified before clearing PageWriteback() and migration can't uncharge the page as long as that is still set. The RCU lock will protect the memcg past uncharge. As far as losing the optimization goes, the following test results are from a microbenchmark that maps, faults, and unmaps a 4GB sparse file three times in a nested fashion, so that there are two negative passes that don't account but still go through the new transaction overhead. There is no actual difference: old: 33.195102545 seconds time elapsed ( +- 0.01% ) new: 33.199231369 seconds time elapsed ( +- 0.03% ) The time spent in page_remove_rmap()'s callees still adds up to the same, but the time spent in the function itself seems reduced: # Children Self Command Shared Object Symbol old: 0.12% 0.11% filemapstress [kernel.kallsyms] [k] page_remove_rmap new: 0.12% 0.08% filemapstress [kernel.kallsyms] [k] page_remove_rmap Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Acked-by: Michal Hocko <mhocko@suse.cz> Cc: Vladimir Davydov <vdavydov@parallels.com> Cc: <stable@vger.kernel.org> [3.17.x] Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/rmap.c')
-rw-r--r--mm/rmap.c20
1 files changed, 10 insertions, 10 deletions
diff --git a/mm/rmap.c b/mm/rmap.c
index 116a5053415b..f574046f77d4 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -1042,15 +1042,16 @@ void page_add_new_anon_rmap(struct page *page,
1042 */ 1042 */
1043void page_add_file_rmap(struct page *page) 1043void page_add_file_rmap(struct page *page)
1044{ 1044{
1045 bool locked; 1045 struct mem_cgroup *memcg;
1046 unsigned long flags; 1046 unsigned long flags;
1047 bool locked;
1047 1048
1048 mem_cgroup_begin_update_page_stat(page, &locked, &flags); 1049 memcg = mem_cgroup_begin_page_stat(page, &locked, &flags);
1049 if (atomic_inc_and_test(&page->_mapcount)) { 1050 if (atomic_inc_and_test(&page->_mapcount)) {
1050 __inc_zone_page_state(page, NR_FILE_MAPPED); 1051 __inc_zone_page_state(page, NR_FILE_MAPPED);
1051 mem_cgroup_inc_page_stat(page, MEM_CGROUP_STAT_FILE_MAPPED); 1052 mem_cgroup_inc_page_stat(memcg, MEM_CGROUP_STAT_FILE_MAPPED);
1052 } 1053 }
1053 mem_cgroup_end_update_page_stat(page, &locked, &flags); 1054 mem_cgroup_end_page_stat(memcg, locked, flags);
1054} 1055}
1055 1056
1056/** 1057/**
@@ -1061,9 +1062,10 @@ void page_add_file_rmap(struct page *page)
1061 */ 1062 */
1062void page_remove_rmap(struct page *page) 1063void page_remove_rmap(struct page *page)
1063{ 1064{
1065 struct mem_cgroup *uninitialized_var(memcg);
1064 bool anon = PageAnon(page); 1066 bool anon = PageAnon(page);
1065 bool locked;
1066 unsigned long flags; 1067 unsigned long flags;
1068 bool locked;
1067 1069
1068 /* 1070 /*
1069 * The anon case has no mem_cgroup page_stat to update; but may 1071 * The anon case has no mem_cgroup page_stat to update; but may
@@ -1071,7 +1073,7 @@ void page_remove_rmap(struct page *page)
1071 * we hold the lock against page_stat move: so avoid it on anon. 1073 * we hold the lock against page_stat move: so avoid it on anon.
1072 */ 1074 */
1073 if (!anon) 1075 if (!anon)
1074 mem_cgroup_begin_update_page_stat(page, &locked, &flags); 1076 memcg = mem_cgroup_begin_page_stat(page, &locked, &flags);
1075 1077
1076 /* page still mapped by someone else? */ 1078 /* page still mapped by someone else? */
1077 if (!atomic_add_negative(-1, &page->_mapcount)) 1079 if (!atomic_add_negative(-1, &page->_mapcount))
@@ -1096,8 +1098,7 @@ void page_remove_rmap(struct page *page)
1096 -hpage_nr_pages(page)); 1098 -hpage_nr_pages(page));
1097 } else { 1099 } else {
1098 __dec_zone_page_state(page, NR_FILE_MAPPED); 1100 __dec_zone_page_state(page, NR_FILE_MAPPED);
1099 mem_cgroup_dec_page_stat(page, MEM_CGROUP_STAT_FILE_MAPPED); 1101 mem_cgroup_dec_page_stat(memcg, MEM_CGROUP_STAT_FILE_MAPPED);
1100 mem_cgroup_end_update_page_stat(page, &locked, &flags);
1101 } 1102 }
1102 if (unlikely(PageMlocked(page))) 1103 if (unlikely(PageMlocked(page)))
1103 clear_page_mlock(page); 1104 clear_page_mlock(page);
@@ -1110,10 +1111,9 @@ void page_remove_rmap(struct page *page)
1110 * Leaving it set also helps swapoff to reinstate ptes 1111 * Leaving it set also helps swapoff to reinstate ptes
1111 * faster for those pages still in swapcache. 1112 * faster for those pages still in swapcache.
1112 */ 1113 */
1113 return;
1114out: 1114out:
1115 if (!anon) 1115 if (!anon)
1116 mem_cgroup_end_update_page_stat(page, &locked, &flags); 1116 mem_cgroup_end_page_stat(memcg, locked, flags);
1117} 1117}
1118 1118
1119/* 1119/*