aboutsummaryrefslogtreecommitdiffstats
path: root/include/linux
diff options
context:
space:
mode:
authorRik van Riel <riel@redhat.com>2010-03-05 16:42:07 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2010-03-06 14:26:26 -0500
commit5beb49305251e5669852ed541e8e2f2f7696c53e (patch)
tree46457450a22f23938b24904aeba5d4ada2f53b20 /include/linux
parent648bcc771145172a14bc35eeb849ed08f6aa4f1e (diff)
mm: change anon_vma linking to fix multi-process server scalability issue
The old anon_vma code can lead to scalability issues with heavily forking workloads. Specifically, each anon_vma will be shared between the parent process and all its child processes. In a workload with 1000 child processes and a VMA with 1000 anonymous pages per process that get COWed, this leads to a system with a million anonymous pages in the same anon_vma, each of which is mapped in just one of the 1000 processes. However, the current rmap code needs to walk them all, leading to O(N) scanning complexity for each page. This can result in systems where one CPU is walking the page tables of 1000 processes in page_referenced_one, while all other CPUs are stuck on the anon_vma lock. This leads to catastrophic failure for a benchmark like AIM7, where the total number of processes can reach in the tens of thousands. Real workloads are still a factor 10 less process intensive than AIM7, but they are catching up. This patch changes the way anon_vmas and VMAs are linked, which allows us to associate multiple anon_vmas with a VMA. At fork time, each child process gets its own anon_vmas, in which its COWed pages will be instantiated. The parents' anon_vma is also linked to the VMA, because non-COWed pages could be present in any of the children. This reduces rmap scanning complexity to O(1) for the pages of the 1000 child processes, with O(N) complexity for at most 1/N pages in the system. This reduces the average scanning cost in heavily forking workloads from O(N) to 2. The only real complexity in this patch stems from the fact that linking a VMA to anon_vmas now involves memory allocations. This means vma_adjust can fail, if it needs to attach a VMA to anon_vma structures. This in turn means error handling needs to be added to the calling functions. A second source of complexity is that, because there can be multiple anon_vmas, the anon_vma linking in vma_adjust can no longer be done under "the" anon_vma lock. To prevent the rmap code from walking up an incomplete VMA, this patch introduces the VM_LOCK_RMAP VMA flag. This bit flag uses the same slot as the NOMMU VM_MAPPED_COPY, with an ifdef in mm.h to make sure it is impossible to compile a kernel that needs both symbolic values for the same bitflag. Some test results: Without the anon_vma changes, when AIM7 hits around 9.7k users (on a test box with 16GB RAM and not quite enough IO), the system ends up running >99% in system time, with every CPU on the same anon_vma lock in the pageout code. With these changes, AIM7 hits the cross-over point around 29.7k users. This happens with ~99% IO wait time, there never seems to be any spike in system time. The anon_vma lock contention appears to be resolved. [akpm@linux-foundation.org: cleanups] Signed-off-by: Rik van Riel <riel@redhat.com> Cc: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> Cc: Larry Woodman <lwoodman@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com> Cc: Minchan Kim <minchan.kim@gmail.com> Cc: Andrea Arcangeli <aarcange@redhat.com> Cc: Hugh Dickins <hugh.dickins@tiscali.co.uk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'include/linux')
-rw-r--r--include/linux/mm.h6
-rw-r--r--include/linux/mm_types.h3
-rw-r--r--include/linux/rmap.h35
3 files changed, 38 insertions, 6 deletions
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 8e580c07d171..8e2841a2f441 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -97,7 +97,11 @@ extern unsigned int kobjsize(const void *objp);
97#define VM_NORESERVE 0x00200000 /* should the VM suppress accounting */ 97#define VM_NORESERVE 0x00200000 /* should the VM suppress accounting */
98#define VM_HUGETLB 0x00400000 /* Huge TLB Page VM */ 98#define VM_HUGETLB 0x00400000 /* Huge TLB Page VM */
99#define VM_NONLINEAR 0x00800000 /* Is non-linear (remap_file_pages) */ 99#define VM_NONLINEAR 0x00800000 /* Is non-linear (remap_file_pages) */
100#ifdef CONFIG_MMU
101#define VM_LOCK_RMAP 0x01000000 /* Do not follow this rmap (mmu mmap) */
102#else
100#define VM_MAPPED_COPY 0x01000000 /* T if mapped copy of data (nommu mmap) */ 103#define VM_MAPPED_COPY 0x01000000 /* T if mapped copy of data (nommu mmap) */
104#endif
101#define VM_INSERTPAGE 0x02000000 /* The vma has had "vm_insert_page()" done on it */ 105#define VM_INSERTPAGE 0x02000000 /* The vma has had "vm_insert_page()" done on it */
102#define VM_ALWAYSDUMP 0x04000000 /* Always include in core dumps */ 106#define VM_ALWAYSDUMP 0x04000000 /* Always include in core dumps */
103 107
@@ -1216,7 +1220,7 @@ static inline void vma_nonlinear_insert(struct vm_area_struct *vma,
1216 1220
1217/* mmap.c */ 1221/* mmap.c */
1218extern int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin); 1222extern int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin);
1219extern void vma_adjust(struct vm_area_struct *vma, unsigned long start, 1223extern int vma_adjust(struct vm_area_struct *vma, unsigned long start,
1220 unsigned long end, pgoff_t pgoff, struct vm_area_struct *insert); 1224 unsigned long end, pgoff_t pgoff, struct vm_area_struct *insert);
1221extern struct vm_area_struct *vma_merge(struct mm_struct *, 1225extern struct vm_area_struct *vma_merge(struct mm_struct *,
1222 struct vm_area_struct *prev, unsigned long addr, unsigned long end, 1226 struct vm_area_struct *prev, unsigned long addr, unsigned long end,
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 19549d7275ab..048b46270aa5 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -163,7 +163,8 @@ struct vm_area_struct {
163 * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack 163 * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
164 * or brk vma (with NULL file) can only be in an anon_vma list. 164 * or brk vma (with NULL file) can only be in an anon_vma list.
165 */ 165 */
166 struct list_head anon_vma_node; /* Serialized by anon_vma->lock */ 166 struct list_head anon_vma_chain; /* Serialized by mmap_sem &
167 * page_table_lock */
167 struct anon_vma *anon_vma; /* Serialized by page_table_lock */ 168 struct anon_vma *anon_vma; /* Serialized by page_table_lock */
168 169
169 /* Function pointers to deal with this struct. */ 170 /* Function pointers to deal with this struct. */
diff --git a/include/linux/rmap.h b/include/linux/rmap.h
index b019ae64e2ab..62da2001d55c 100644
--- a/include/linux/rmap.h
+++ b/include/linux/rmap.h
@@ -37,7 +37,27 @@ struct anon_vma {
37 * is serialized by a system wide lock only visible to 37 * is serialized by a system wide lock only visible to
38 * mm_take_all_locks() (mm_all_locks_mutex). 38 * mm_take_all_locks() (mm_all_locks_mutex).
39 */ 39 */
40 struct list_head head; /* List of private "related" vmas */ 40 struct list_head head; /* Chain of private "related" vmas */
41};
42
43/*
44 * The copy-on-write semantics of fork mean that an anon_vma
45 * can become associated with multiple processes. Furthermore,
46 * each child process will have its own anon_vma, where new
47 * pages for that process are instantiated.
48 *
49 * This structure allows us to find the anon_vmas associated
50 * with a VMA, or the VMAs associated with an anon_vma.
51 * The "same_vma" list contains the anon_vma_chains linking
52 * all the anon_vmas associated with this VMA.
53 * The "same_anon_vma" list contains the anon_vma_chains
54 * which link all the VMAs associated with this anon_vma.
55 */
56struct anon_vma_chain {
57 struct vm_area_struct *vma;
58 struct anon_vma *anon_vma;
59 struct list_head same_vma; /* locked by mmap_sem & page_table_lock */
60 struct list_head same_anon_vma; /* locked by anon_vma->lock */
41}; 61};
42 62
43#ifdef CONFIG_MMU 63#ifdef CONFIG_MMU
@@ -89,12 +109,19 @@ static inline void anon_vma_unlock(struct vm_area_struct *vma)
89 */ 109 */
90void anon_vma_init(void); /* create anon_vma_cachep */ 110void anon_vma_init(void); /* create anon_vma_cachep */
91int anon_vma_prepare(struct vm_area_struct *); 111int anon_vma_prepare(struct vm_area_struct *);
92void __anon_vma_merge(struct vm_area_struct *, struct vm_area_struct *); 112void unlink_anon_vmas(struct vm_area_struct *);
93void anon_vma_unlink(struct vm_area_struct *); 113int anon_vma_clone(struct vm_area_struct *, struct vm_area_struct *);
94void anon_vma_link(struct vm_area_struct *); 114int anon_vma_fork(struct vm_area_struct *, struct vm_area_struct *);
95void __anon_vma_link(struct vm_area_struct *); 115void __anon_vma_link(struct vm_area_struct *);
96void anon_vma_free(struct anon_vma *); 116void anon_vma_free(struct anon_vma *);
97 117
118static inline void anon_vma_merge(struct vm_area_struct *vma,
119 struct vm_area_struct *next)
120{
121 VM_BUG_ON(vma->anon_vma != next->anon_vma);
122 unlink_anon_vmas(next);
123}
124
98/* 125/*
99 * rmap interfaces called when adding or removing pte of page 126 * rmap interfaces called when adding or removing pte of page
100 */ 127 */