diff options
author | KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> | 2011-01-20 17:44:25 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-01-20 20:02:06 -0500 |
commit | 987eba66e0e6aa654d60881a14731a353ee0acb4 (patch) | |
tree | 04138f68481ccd3e8f9447037c3737c568fa3330 /mm | |
parent | ece35ca810326946ddc930c43356312ad5de44d4 (diff) |
memcg: fix rmdir, force_empty with THP
Now, when THP is enabled, memcg's rmdir() function is broken because
move_account() for THP page is not supported.
This will cause account leak or -EBUSY issue at rmdir().
This patch fixes the issue by supporting move_account() THP pages.
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: Daisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Cc: Balbir Singh <balbir@linux.vnet.ibm.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm')
-rw-r--r-- | mm/memcontrol.c | 37 |
1 files changed, 26 insertions, 11 deletions
diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 7a94ef6b35e2..5b562b375cbd 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c | |||
@@ -2197,8 +2197,11 @@ void mem_cgroup_split_huge_fixup(struct page *head, struct page *tail) | |||
2197 | */ | 2197 | */ |
2198 | 2198 | ||
2199 | static void __mem_cgroup_move_account(struct page_cgroup *pc, | 2199 | static void __mem_cgroup_move_account(struct page_cgroup *pc, |
2200 | struct mem_cgroup *from, struct mem_cgroup *to, bool uncharge) | 2200 | struct mem_cgroup *from, struct mem_cgroup *to, bool uncharge, |
2201 | int charge_size) | ||
2201 | { | 2202 | { |
2203 | int nr_pages = charge_size >> PAGE_SHIFT; | ||
2204 | |||
2202 | VM_BUG_ON(from == to); | 2205 | VM_BUG_ON(from == to); |
2203 | VM_BUG_ON(PageLRU(pc->page)); | 2206 | VM_BUG_ON(PageLRU(pc->page)); |
2204 | VM_BUG_ON(!page_is_cgroup_locked(pc)); | 2207 | VM_BUG_ON(!page_is_cgroup_locked(pc)); |
@@ -2212,14 +2215,14 @@ static void __mem_cgroup_move_account(struct page_cgroup *pc, | |||
2212 | __this_cpu_inc(to->stat->count[MEM_CGROUP_STAT_FILE_MAPPED]); | 2215 | __this_cpu_inc(to->stat->count[MEM_CGROUP_STAT_FILE_MAPPED]); |
2213 | preempt_enable(); | 2216 | preempt_enable(); |
2214 | } | 2217 | } |
2215 | mem_cgroup_charge_statistics(from, PageCgroupCache(pc), -1); | 2218 | mem_cgroup_charge_statistics(from, PageCgroupCache(pc), -nr_pages); |
2216 | if (uncharge) | 2219 | if (uncharge) |
2217 | /* This is not "cancel", but cancel_charge does all we need. */ | 2220 | /* This is not "cancel", but cancel_charge does all we need. */ |
2218 | mem_cgroup_cancel_charge(from, PAGE_SIZE); | 2221 | mem_cgroup_cancel_charge(from, charge_size); |
2219 | 2222 | ||
2220 | /* caller should have done css_get */ | 2223 | /* caller should have done css_get */ |
2221 | pc->mem_cgroup = to; | 2224 | pc->mem_cgroup = to; |
2222 | mem_cgroup_charge_statistics(to, PageCgroupCache(pc), 1); | 2225 | mem_cgroup_charge_statistics(to, PageCgroupCache(pc), nr_pages); |
2223 | /* | 2226 | /* |
2224 | * We charges against "to" which may not have any tasks. Then, "to" | 2227 | * We charges against "to" which may not have any tasks. Then, "to" |
2225 | * can be under rmdir(). But in current implementation, caller of | 2228 | * can be under rmdir(). But in current implementation, caller of |
@@ -2234,15 +2237,19 @@ static void __mem_cgroup_move_account(struct page_cgroup *pc, | |||
2234 | * __mem_cgroup_move_account() | 2237 | * __mem_cgroup_move_account() |
2235 | */ | 2238 | */ |
2236 | static int mem_cgroup_move_account(struct page_cgroup *pc, | 2239 | static int mem_cgroup_move_account(struct page_cgroup *pc, |
2237 | struct mem_cgroup *from, struct mem_cgroup *to, bool uncharge) | 2240 | struct mem_cgroup *from, struct mem_cgroup *to, |
2241 | bool uncharge, int charge_size) | ||
2238 | { | 2242 | { |
2239 | int ret = -EINVAL; | 2243 | int ret = -EINVAL; |
2240 | unsigned long flags; | 2244 | unsigned long flags; |
2241 | 2245 | ||
2246 | if ((charge_size > PAGE_SIZE) && !PageTransHuge(pc->page)) | ||
2247 | return -EBUSY; | ||
2248 | |||
2242 | lock_page_cgroup(pc); | 2249 | lock_page_cgroup(pc); |
2243 | if (PageCgroupUsed(pc) && pc->mem_cgroup == from) { | 2250 | if (PageCgroupUsed(pc) && pc->mem_cgroup == from) { |
2244 | move_lock_page_cgroup(pc, &flags); | 2251 | move_lock_page_cgroup(pc, &flags); |
2245 | __mem_cgroup_move_account(pc, from, to, uncharge); | 2252 | __mem_cgroup_move_account(pc, from, to, uncharge, charge_size); |
2246 | move_unlock_page_cgroup(pc, &flags); | 2253 | move_unlock_page_cgroup(pc, &flags); |
2247 | ret = 0; | 2254 | ret = 0; |
2248 | } | 2255 | } |
@@ -2267,6 +2274,8 @@ static int mem_cgroup_move_parent(struct page_cgroup *pc, | |||
2267 | struct cgroup *cg = child->css.cgroup; | 2274 | struct cgroup *cg = child->css.cgroup; |
2268 | struct cgroup *pcg = cg->parent; | 2275 | struct cgroup *pcg = cg->parent; |
2269 | struct mem_cgroup *parent; | 2276 | struct mem_cgroup *parent; |
2277 | int charge = PAGE_SIZE; | ||
2278 | unsigned long flags; | ||
2270 | int ret; | 2279 | int ret; |
2271 | 2280 | ||
2272 | /* Is ROOT ? */ | 2281 | /* Is ROOT ? */ |
@@ -2278,17 +2287,23 @@ static int mem_cgroup_move_parent(struct page_cgroup *pc, | |||
2278 | goto out; | 2287 | goto out; |
2279 | if (isolate_lru_page(page)) | 2288 | if (isolate_lru_page(page)) |
2280 | goto put; | 2289 | goto put; |
2290 | /* The page is isolated from LRU and we have no race with splitting */ | ||
2291 | charge = PAGE_SIZE << compound_order(page); | ||
2281 | 2292 | ||
2282 | parent = mem_cgroup_from_cont(pcg); | 2293 | parent = mem_cgroup_from_cont(pcg); |
2283 | ret = __mem_cgroup_try_charge(NULL, gfp_mask, &parent, false, | 2294 | ret = __mem_cgroup_try_charge(NULL, gfp_mask, &parent, false, charge); |
2284 | PAGE_SIZE); | ||
2285 | if (ret || !parent) | 2295 | if (ret || !parent) |
2286 | goto put_back; | 2296 | goto put_back; |
2287 | 2297 | ||
2288 | ret = mem_cgroup_move_account(pc, child, parent, true); | 2298 | if (charge > PAGE_SIZE) |
2299 | flags = compound_lock_irqsave(page); | ||
2300 | |||
2301 | ret = mem_cgroup_move_account(pc, child, parent, true, charge); | ||
2289 | if (ret) | 2302 | if (ret) |
2290 | mem_cgroup_cancel_charge(parent, PAGE_SIZE); | 2303 | mem_cgroup_cancel_charge(parent, charge); |
2291 | put_back: | 2304 | put_back: |
2305 | if (charge > PAGE_SIZE) | ||
2306 | compound_unlock_irqrestore(page, flags); | ||
2292 | putback_lru_page(page); | 2307 | putback_lru_page(page); |
2293 | put: | 2308 | put: |
2294 | put_page(page); | 2309 | put_page(page); |
@@ -4868,7 +4883,7 @@ retry: | |||
4868 | goto put; | 4883 | goto put; |
4869 | pc = lookup_page_cgroup(page); | 4884 | pc = lookup_page_cgroup(page); |
4870 | if (!mem_cgroup_move_account(pc, | 4885 | if (!mem_cgroup_move_account(pc, |
4871 | mc.from, mc.to, false)) { | 4886 | mc.from, mc.to, false, PAGE_SIZE)) { |
4872 | mc.precharge--; | 4887 | mc.precharge--; |
4873 | /* we uncharge from mc.from later. */ | 4888 | /* we uncharge from mc.from later. */ |
4874 | mc.moved_charge++; | 4889 | mc.moved_charge++; |