diff options
author | KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> | 2012-03-21 19:34:25 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-21 20:55:01 -0400 |
commit | 89c06bd52fb9ffceddf84f7309d2e8c9f1666216 (patch) | |
tree | 43ec3d97a89988bc143bed5796bcd7bef64212dc /include/linux/memcontrol.h | |
parent | 312734c04e2fecc58429aec98194e4ff12d8f7d6 (diff) |
memcg: use new logic for page stat accounting
Now, page-stat-per-memcg is recorded into per page_cgroup flag by
duplicating page's status into the flag. The reason is that memcg has a
feature to move a page from a group to another group and we have race
between "move" and "page stat accounting",
Under current logic, assume CPU-A and CPU-B. CPU-A does "move" and CPU-B
does "page stat accounting".
When CPU-A goes 1st,
CPU-A CPU-B
update "struct page" info.
move_lock_mem_cgroup(memcg)
see pc->flags
copy page stat to new group
overwrite pc->mem_cgroup.
move_unlock_mem_cgroup(memcg)
move_lock_mem_cgroup(mem)
set pc->flags
update page stat accounting
move_unlock_mem_cgroup(mem)
stat accounting is guarded by move_lock_mem_cgroup() and "move" logic
(CPU-A) doesn't see changes in "struct page" information.
But it's costly to have the same information both in 'struct page' and
'struct page_cgroup'. And, there is a potential problem.
For example, assume we have PG_dirty accounting in memcg.
PG_..is a flag for struct page.
PCG_ is a flag for struct page_cgroup.
(This is just an example. The same problem can be found in any
kind of page stat accounting.)
CPU-A CPU-B
TestSet PG_dirty
(delay) TestClear PG_dirty
if (TestClear(PCG_dirty))
memcg->nr_dirty--
if (TestSet(PCG_dirty))
memcg->nr_dirty++
Here, memcg->nr_dirty = +1, this is wrong. This race was reported by Greg
Thelen <gthelen@google.com>. Now, only FILE_MAPPED is supported but
fortunately, it's serialized by page table lock and this is not real bug,
_now_,
If this potential problem is caused by having duplicated information in
struct page and struct page_cgroup, we may be able to fix this by using
original 'struct page' information. But we'll have a problem in "move
account"
Assume we use only PG_dirty.
CPU-A CPU-B
TestSet PG_dirty
(delay) move_lock_mem_cgroup()
if (PageDirty(page))
new_memcg->nr_dirty++
pc->mem_cgroup = new_memcg;
move_unlock_mem_cgroup()
move_lock_mem_cgroup()
memcg = pc->mem_cgroup
new_memcg->nr_dirty++
accounting information may be double-counted. This was original reason to
have PCG_xxx flags but it seems PCG_xxx has another problem.
I think we need a bigger lock as
move_lock_mem_cgroup(page)
TestSetPageDirty(page)
update page stats (without any checks)
move_unlock_mem_cgroup(page)
This fixes both of problems and we don't have to duplicate page flag into
page_cgroup. Please note: move_lock_mem_cgroup() is held only when there
are possibility of "account move" under the system. So, in most path,
status update will go without atomic locks.
This patch introduces mem_cgroup_begin_update_page_stat() and
mem_cgroup_end_update_page_stat() both should be called at modifying
'struct page' information if memcg takes care of it. as
mem_cgroup_begin_update_page_stat()
modify page information
mem_cgroup_update_page_stat()
=> never check any 'struct page' info, just update counters.
mem_cgroup_end_update_page_stat().
This patch is slow because we need to call begin_update_page_stat()/
end_update_page_stat() regardless of accounted will be changed or not. A
following patch adds an easy optimization and reduces the cost.
[akpm@linux-foundation.org: s/lock/locked/]
[hughd@google.com: fix deadlock by avoiding stat lock when anon]
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: Greg Thelen <gthelen@google.com>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Cc: Ying Han <yinghan@google.com>
Signed-off-by: Hugh Dickins <hughd@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'include/linux/memcontrol.h')
-rw-r--r-- | include/linux/memcontrol.h | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index c54e5dfa1962..bf7ae01fc93b 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h | |||
@@ -141,6 +141,31 @@ static inline bool mem_cgroup_disabled(void) | |||
141 | return false; | 141 | return false; |
142 | } | 142 | } |
143 | 143 | ||
144 | void __mem_cgroup_begin_update_page_stat(struct page *page, bool *locked, | ||
145 | unsigned long *flags); | ||
146 | |||
147 | static inline void mem_cgroup_begin_update_page_stat(struct page *page, | ||
148 | bool *locked, unsigned long *flags) | ||
149 | { | ||
150 | if (mem_cgroup_disabled()) | ||
151 | return; | ||
152 | rcu_read_lock(); | ||
153 | *locked = false; | ||
154 | return __mem_cgroup_begin_update_page_stat(page, locked, flags); | ||
155 | } | ||
156 | |||
157 | void __mem_cgroup_end_update_page_stat(struct page *page, | ||
158 | unsigned long *flags); | ||
159 | static inline void mem_cgroup_end_update_page_stat(struct page *page, | ||
160 | bool *locked, unsigned long *flags) | ||
161 | { | ||
162 | if (mem_cgroup_disabled()) | ||
163 | return; | ||
164 | if (*locked) | ||
165 | __mem_cgroup_end_update_page_stat(page, flags); | ||
166 | rcu_read_unlock(); | ||
167 | } | ||
168 | |||
144 | void mem_cgroup_update_page_stat(struct page *page, | 169 | void mem_cgroup_update_page_stat(struct page *page, |
145 | enum mem_cgroup_page_stat_item idx, | 170 | enum mem_cgroup_page_stat_item idx, |
146 | int val); | 171 | int val); |
@@ -341,6 +366,16 @@ mem_cgroup_print_oom_info(struct mem_cgroup *memcg, struct task_struct *p) | |||
341 | { | 366 | { |
342 | } | 367 | } |
343 | 368 | ||
369 | static inline void mem_cgroup_begin_update_page_stat(struct page *page, | ||
370 | bool *locked, unsigned long *flags) | ||
371 | { | ||
372 | } | ||
373 | |||
374 | static inline void mem_cgroup_end_update_page_stat(struct page *page, | ||
375 | bool *locked, unsigned long *flags) | ||
376 | { | ||
377 | } | ||
378 | |||
344 | static inline void mem_cgroup_inc_page_stat(struct page *page, | 379 | static inline void mem_cgroup_inc_page_stat(struct page *page, |
345 | enum mem_cgroup_page_stat_item idx) | 380 | enum mem_cgroup_page_stat_item idx) |
346 | { | 381 | { |