aboutsummaryrefslogtreecommitdiffstats
path: root/mm/rmap.c
diff options
context:
space:
mode:
authorAndrea Arcangeli <aarcange@redhat.com>2012-01-10 18:08:05 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2012-01-10 19:30:44 -0500
commit948f017b093a9baac23855fcd920d3a970b71bb6 (patch)
tree4536a9cbe1077133e600da2072998f5e60c696c6 /mm/rmap.c
parentdf0a6daa01fa3856c08f4274d4f21a8092caa480 (diff)
mremap: enforce rmap src/dst vma ordering in case of vma_merge() succeeding in copy_vma()
migrate was doing an rmap_walk with speculative lock-less access on pagetables. That could lead it to not serializing properly against mremap PT locks. But a second problem remains in the order of vmas in the same_anon_vma list used by the rmap_walk. If vma_merge succeeds in copy_vma, the src vma could be placed after the dst vma in the same_anon_vma list. That could still lead to migrate missing some pte. This patch adds an anon_vma_moveto_tail() function to force the dst vma at the end of the list before mremap starts to solve the problem. If the mremap is very large and there are a lots of parents or childs sharing the anon_vma root lock, this should still scale better than taking the anon_vma root lock around every pte copy practically for the whole duration of mremap. Update: Hugh noticed special care is needed in the error path where move_page_tables goes in the reverse direction, a second anon_vma_moveto_tail() call is needed in the error path. This program exercises the anon_vma_moveto_tail: === int main() { static struct timeval oldstamp, newstamp; long diffsec; char *p, *p2, *p3, *p4; if (posix_memalign((void **)&p, 2*1024*1024, SIZE)) perror("memalign"), exit(1); if (posix_memalign((void **)&p2, 2*1024*1024, SIZE)) perror("memalign"), exit(1); if (posix_memalign((void **)&p3, 2*1024*1024, SIZE)) perror("memalign"), exit(1); memset(p, 0xff, SIZE); printf("%p\n", p); memset(p2, 0xff, SIZE); memset(p3, 0x77, 4096); if (memcmp(p, p2, SIZE)) printf("error\n"); p4 = mremap(p+SIZE/2, SIZE/2, SIZE/2, MREMAP_FIXED|MREMAP_MAYMOVE, p3); if (p4 != p3) perror("mremap"), exit(1); p4 = mremap(p4, SIZE/2, SIZE/2, MREMAP_FIXED|MREMAP_MAYMOVE, p+SIZE/2); if (p4 != p+SIZE/2) perror("mremap"), exit(1); if (memcmp(p, p2, SIZE)) printf("error\n"); printf("ok\n"); return 0; } === $ perf probe -a anon_vma_moveto_tail Add new event: probe:anon_vma_moveto_tail (on anon_vma_moveto_tail) You can now use it on all perf tools, such as: perf record -e probe:anon_vma_moveto_tail -aR sleep 1 $ perf record -e probe:anon_vma_moveto_tail -aR ./anon_vma_moveto_tail 0x7f2ca2800000 ok [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.043 MB perf.data (~1860 samples) ] $ perf report --stdio 100.00% anon_vma_moveto [kernel.kallsyms] [k] anon_vma_moveto_tail Signed-off-by: Andrea Arcangeli <aarcange@redhat.com> Reported-by: Nai Xia <nai.xia@gmail.com> Acked-by: Mel Gorman <mgorman@suse.de> Cc: Hugh Dickins <hughd@google.com> Cc: Pawel Sikora <pluto@agmk.net> 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.c45
1 files changed, 45 insertions, 0 deletions
diff --git a/mm/rmap.c b/mm/rmap.c
index a4fd3680038b..a2e5ce1fa081 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -272,6 +272,51 @@ int anon_vma_clone(struct vm_area_struct *dst, struct vm_area_struct *src)
272} 272}
273 273
274/* 274/*
275 * Some rmap walk that needs to find all ptes/hugepmds without false
276 * negatives (like migrate and split_huge_page) running concurrent
277 * with operations that copy or move pagetables (like mremap() and
278 * fork()) to be safe. They depend on the anon_vma "same_anon_vma"
279 * list to be in a certain order: the dst_vma must be placed after the
280 * src_vma in the list. This is always guaranteed by fork() but
281 * mremap() needs to call this function to enforce it in case the
282 * dst_vma isn't newly allocated and chained with the anon_vma_clone()
283 * function but just an extension of a pre-existing vma through
284 * vma_merge.
285 *
286 * NOTE: the same_anon_vma list can still be changed by other
287 * processes while mremap runs because mremap doesn't hold the
288 * anon_vma mutex to prevent modifications to the list while it
289 * runs. All we need to enforce is that the relative order of this
290 * process vmas isn't changing (we don't care about other vmas
291 * order). Each vma corresponds to an anon_vma_chain structure so
292 * there's no risk that other processes calling anon_vma_moveto_tail()
293 * and changing the same_anon_vma list under mremap() will screw with
294 * the relative order of this process vmas in the list, because we
295 * they can't alter the order of any vma that belongs to this
296 * process. And there can't be another anon_vma_moveto_tail() running
297 * concurrently with mremap() coming from this process because we hold
298 * the mmap_sem for the whole mremap(). fork() ordering dependency
299 * also shouldn't be affected because fork() only cares that the
300 * parent vmas are placed in the list before the child vmas and
301 * anon_vma_moveto_tail() won't reorder vmas from either the fork()
302 * parent or child.
303 */
304void anon_vma_moveto_tail(struct vm_area_struct *dst)
305{
306 struct anon_vma_chain *pavc;
307 struct anon_vma *root = NULL;
308
309 list_for_each_entry_reverse(pavc, &dst->anon_vma_chain, same_vma) {
310 struct anon_vma *anon_vma = pavc->anon_vma;
311 VM_BUG_ON(pavc->vma != dst);
312 root = lock_anon_vma_root(root, anon_vma);
313 list_del(&pavc->same_anon_vma);
314 list_add_tail(&pavc->same_anon_vma, &anon_vma->head);
315 }
316 unlock_anon_vma_root(root);
317}
318
319/*
275 * Attach vma to its own anon_vma, as well as to the anon_vmas that 320 * Attach vma to its own anon_vma, as well as to the anon_vmas that
276 * the corresponding VMA in the parent process is attached to. 321 * the corresponding VMA in the parent process is attached to.
277 * Returns 0 on success, non-zero on failure. 322 * Returns 0 on success, non-zero on failure.