diff options
author | Peter Zijlstra <a.p.zijlstra@chello.nl> | 2012-11-18 21:14:23 -0500 |
---|---|---|
committer | Mel Gorman <mgorman@suse.de> | 2012-12-11 09:28:34 -0500 |
commit | 7da4d641c58d201c3cc1835c05ca1a7fa26f0856 (patch) | |
tree | bf40cc40433383ee94559ea2ef19a282dead358f | |
parent | 4fd017708c4a067da51a2b5cf8aedddf4e840b1f (diff) |
mm: Count the number of pages affected in change_protection()
This will be used for three kinds of purposes:
- to optimize mprotect()
- to speed up working set scanning for working set areas that
have not been touched
- to more accurately scan per real working set
No change in functionality from this patch.
Suggested-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Hugh Dickins <hughd@google.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r-- | include/linux/hugetlb.h | 8 | ||||
-rw-r--r-- | include/linux/mm.h | 3 | ||||
-rw-r--r-- | mm/hugetlb.c | 10 | ||||
-rw-r--r-- | mm/mprotect.c | 58 |
4 files changed, 61 insertions, 18 deletions
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 225164842ab6..06e691baab86 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h | |||
@@ -87,7 +87,7 @@ struct page *follow_huge_pud(struct mm_struct *mm, unsigned long address, | |||
87 | pud_t *pud, int write); | 87 | pud_t *pud, int write); |
88 | int pmd_huge(pmd_t pmd); | 88 | int pmd_huge(pmd_t pmd); |
89 | int pud_huge(pud_t pmd); | 89 | int pud_huge(pud_t pmd); |
90 | void hugetlb_change_protection(struct vm_area_struct *vma, | 90 | unsigned long hugetlb_change_protection(struct vm_area_struct *vma, |
91 | unsigned long address, unsigned long end, pgprot_t newprot); | 91 | unsigned long address, unsigned long end, pgprot_t newprot); |
92 | 92 | ||
93 | #else /* !CONFIG_HUGETLB_PAGE */ | 93 | #else /* !CONFIG_HUGETLB_PAGE */ |
@@ -132,7 +132,11 @@ static inline void copy_huge_page(struct page *dst, struct page *src) | |||
132 | { | 132 | { |
133 | } | 133 | } |
134 | 134 | ||
135 | #define hugetlb_change_protection(vma, address, end, newprot) | 135 | static inline unsigned long hugetlb_change_protection(struct vm_area_struct *vma, |
136 | unsigned long address, unsigned long end, pgprot_t newprot) | ||
137 | { | ||
138 | return 0; | ||
139 | } | ||
136 | 140 | ||
137 | static inline void __unmap_hugepage_range_final(struct mmu_gather *tlb, | 141 | static inline void __unmap_hugepage_range_final(struct mmu_gather *tlb, |
138 | struct vm_area_struct *vma, unsigned long start, | 142 | struct vm_area_struct *vma, unsigned long start, |
diff --git a/include/linux/mm.h b/include/linux/mm.h index bcaab4e6fe91..1856c62d82cd 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h | |||
@@ -1078,6 +1078,9 @@ extern unsigned long move_page_tables(struct vm_area_struct *vma, | |||
1078 | extern unsigned long do_mremap(unsigned long addr, | 1078 | extern unsigned long do_mremap(unsigned long addr, |
1079 | unsigned long old_len, unsigned long new_len, | 1079 | unsigned long old_len, unsigned long new_len, |
1080 | unsigned long flags, unsigned long new_addr); | 1080 | unsigned long flags, unsigned long new_addr); |
1081 | extern unsigned long change_protection(struct vm_area_struct *vma, unsigned long start, | ||
1082 | unsigned long end, pgprot_t newprot, | ||
1083 | int dirty_accountable); | ||
1081 | extern int mprotect_fixup(struct vm_area_struct *vma, | 1084 | extern int mprotect_fixup(struct vm_area_struct *vma, |
1082 | struct vm_area_struct **pprev, unsigned long start, | 1085 | struct vm_area_struct **pprev, unsigned long start, |
1083 | unsigned long end, unsigned long newflags); | 1086 | unsigned long end, unsigned long newflags); |
diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 59a0059b39e2..712895eda0d0 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c | |||
@@ -3014,7 +3014,7 @@ same_page: | |||
3014 | return i ? i : -EFAULT; | 3014 | return i ? i : -EFAULT; |
3015 | } | 3015 | } |
3016 | 3016 | ||
3017 | void hugetlb_change_protection(struct vm_area_struct *vma, | 3017 | unsigned long hugetlb_change_protection(struct vm_area_struct *vma, |
3018 | unsigned long address, unsigned long end, pgprot_t newprot) | 3018 | unsigned long address, unsigned long end, pgprot_t newprot) |
3019 | { | 3019 | { |
3020 | struct mm_struct *mm = vma->vm_mm; | 3020 | struct mm_struct *mm = vma->vm_mm; |
@@ -3022,6 +3022,7 @@ void hugetlb_change_protection(struct vm_area_struct *vma, | |||
3022 | pte_t *ptep; | 3022 | pte_t *ptep; |
3023 | pte_t pte; | 3023 | pte_t pte; |
3024 | struct hstate *h = hstate_vma(vma); | 3024 | struct hstate *h = hstate_vma(vma); |
3025 | unsigned long pages = 0; | ||
3025 | 3026 | ||
3026 | BUG_ON(address >= end); | 3027 | BUG_ON(address >= end); |
3027 | flush_cache_range(vma, address, end); | 3028 | flush_cache_range(vma, address, end); |
@@ -3032,12 +3033,15 @@ void hugetlb_change_protection(struct vm_area_struct *vma, | |||
3032 | ptep = huge_pte_offset(mm, address); | 3033 | ptep = huge_pte_offset(mm, address); |
3033 | if (!ptep) | 3034 | if (!ptep) |
3034 | continue; | 3035 | continue; |
3035 | if (huge_pmd_unshare(mm, &address, ptep)) | 3036 | if (huge_pmd_unshare(mm, &address, ptep)) { |
3037 | pages++; | ||
3036 | continue; | 3038 | continue; |
3039 | } | ||
3037 | if (!huge_pte_none(huge_ptep_get(ptep))) { | 3040 | if (!huge_pte_none(huge_ptep_get(ptep))) { |
3038 | pte = huge_ptep_get_and_clear(mm, address, ptep); | 3041 | pte = huge_ptep_get_and_clear(mm, address, ptep); |
3039 | pte = pte_mkhuge(pte_modify(pte, newprot)); | 3042 | pte = pte_mkhuge(pte_modify(pte, newprot)); |
3040 | set_huge_pte_at(mm, address, ptep, pte); | 3043 | set_huge_pte_at(mm, address, ptep, pte); |
3044 | pages++; | ||
3041 | } | 3045 | } |
3042 | } | 3046 | } |
3043 | spin_unlock(&mm->page_table_lock); | 3047 | spin_unlock(&mm->page_table_lock); |
@@ -3049,6 +3053,8 @@ void hugetlb_change_protection(struct vm_area_struct *vma, | |||
3049 | */ | 3053 | */ |
3050 | flush_tlb_range(vma, start, end); | 3054 | flush_tlb_range(vma, start, end); |
3051 | mutex_unlock(&vma->vm_file->f_mapping->i_mmap_mutex); | 3055 | mutex_unlock(&vma->vm_file->f_mapping->i_mmap_mutex); |
3056 | |||
3057 | return pages << h->order; | ||
3052 | } | 3058 | } |
3053 | 3059 | ||
3054 | int hugetlb_reserve_pages(struct inode *inode, | 3060 | int hugetlb_reserve_pages(struct inode *inode, |
diff --git a/mm/mprotect.c b/mm/mprotect.c index a40992610ab6..1e265be25f85 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c | |||
@@ -35,12 +35,13 @@ static inline pgprot_t pgprot_modify(pgprot_t oldprot, pgprot_t newprot) | |||
35 | } | 35 | } |
36 | #endif | 36 | #endif |
37 | 37 | ||
38 | static void change_pte_range(struct mm_struct *mm, pmd_t *pmd, | 38 | static unsigned long change_pte_range(struct mm_struct *mm, pmd_t *pmd, |
39 | unsigned long addr, unsigned long end, pgprot_t newprot, | 39 | unsigned long addr, unsigned long end, pgprot_t newprot, |
40 | int dirty_accountable) | 40 | int dirty_accountable) |
41 | { | 41 | { |
42 | pte_t *pte, oldpte; | 42 | pte_t *pte, oldpte; |
43 | spinlock_t *ptl; | 43 | spinlock_t *ptl; |
44 | unsigned long pages = 0; | ||
44 | 45 | ||
45 | pte = pte_offset_map_lock(mm, pmd, addr, &ptl); | 46 | pte = pte_offset_map_lock(mm, pmd, addr, &ptl); |
46 | arch_enter_lazy_mmu_mode(); | 47 | arch_enter_lazy_mmu_mode(); |
@@ -60,6 +61,7 @@ static void change_pte_range(struct mm_struct *mm, pmd_t *pmd, | |||
60 | ptent = pte_mkwrite(ptent); | 61 | ptent = pte_mkwrite(ptent); |
61 | 62 | ||
62 | ptep_modify_prot_commit(mm, addr, pte, ptent); | 63 | ptep_modify_prot_commit(mm, addr, pte, ptent); |
64 | pages++; | ||
63 | } else if (IS_ENABLED(CONFIG_MIGRATION) && !pte_file(oldpte)) { | 65 | } else if (IS_ENABLED(CONFIG_MIGRATION) && !pte_file(oldpte)) { |
64 | swp_entry_t entry = pte_to_swp_entry(oldpte); | 66 | swp_entry_t entry = pte_to_swp_entry(oldpte); |
65 | 67 | ||
@@ -72,18 +74,22 @@ static void change_pte_range(struct mm_struct *mm, pmd_t *pmd, | |||
72 | set_pte_at(mm, addr, pte, | 74 | set_pte_at(mm, addr, pte, |
73 | swp_entry_to_pte(entry)); | 75 | swp_entry_to_pte(entry)); |
74 | } | 76 | } |
77 | pages++; | ||
75 | } | 78 | } |
76 | } while (pte++, addr += PAGE_SIZE, addr != end); | 79 | } while (pte++, addr += PAGE_SIZE, addr != end); |
77 | arch_leave_lazy_mmu_mode(); | 80 | arch_leave_lazy_mmu_mode(); |
78 | pte_unmap_unlock(pte - 1, ptl); | 81 | pte_unmap_unlock(pte - 1, ptl); |
82 | |||
83 | return pages; | ||
79 | } | 84 | } |
80 | 85 | ||
81 | static inline void change_pmd_range(struct vm_area_struct *vma, pud_t *pud, | 86 | static inline unsigned long change_pmd_range(struct vm_area_struct *vma, pud_t *pud, |
82 | unsigned long addr, unsigned long end, pgprot_t newprot, | 87 | unsigned long addr, unsigned long end, pgprot_t newprot, |
83 | int dirty_accountable) | 88 | int dirty_accountable) |
84 | { | 89 | { |
85 | pmd_t *pmd; | 90 | pmd_t *pmd; |
86 | unsigned long next; | 91 | unsigned long next; |
92 | unsigned long pages = 0; | ||
87 | 93 | ||
88 | pmd = pmd_offset(pud, addr); | 94 | pmd = pmd_offset(pud, addr); |
89 | do { | 95 | do { |
@@ -91,35 +97,42 @@ static inline void change_pmd_range(struct vm_area_struct *vma, pud_t *pud, | |||
91 | if (pmd_trans_huge(*pmd)) { | 97 | if (pmd_trans_huge(*pmd)) { |
92 | if (next - addr != HPAGE_PMD_SIZE) | 98 | if (next - addr != HPAGE_PMD_SIZE) |
93 | split_huge_page_pmd(vma->vm_mm, pmd); | 99 | split_huge_page_pmd(vma->vm_mm, pmd); |
94 | else if (change_huge_pmd(vma, pmd, addr, newprot)) | 100 | else if (change_huge_pmd(vma, pmd, addr, newprot)) { |
101 | pages += HPAGE_PMD_NR; | ||
95 | continue; | 102 | continue; |
103 | } | ||
96 | /* fall through */ | 104 | /* fall through */ |
97 | } | 105 | } |
98 | if (pmd_none_or_clear_bad(pmd)) | 106 | if (pmd_none_or_clear_bad(pmd)) |
99 | continue; | 107 | continue; |
100 | change_pte_range(vma->vm_mm, pmd, addr, next, newprot, | 108 | pages += change_pte_range(vma->vm_mm, pmd, addr, next, newprot, |
101 | dirty_accountable); | 109 | dirty_accountable); |
102 | } while (pmd++, addr = next, addr != end); | 110 | } while (pmd++, addr = next, addr != end); |
111 | |||
112 | return pages; | ||
103 | } | 113 | } |
104 | 114 | ||
105 | static inline void change_pud_range(struct vm_area_struct *vma, pgd_t *pgd, | 115 | static inline unsigned long change_pud_range(struct vm_area_struct *vma, pgd_t *pgd, |
106 | unsigned long addr, unsigned long end, pgprot_t newprot, | 116 | unsigned long addr, unsigned long end, pgprot_t newprot, |
107 | int dirty_accountable) | 117 | int dirty_accountable) |
108 | { | 118 | { |
109 | pud_t *pud; | 119 | pud_t *pud; |
110 | unsigned long next; | 120 | unsigned long next; |
121 | unsigned long pages = 0; | ||
111 | 122 | ||
112 | pud = pud_offset(pgd, addr); | 123 | pud = pud_offset(pgd, addr); |
113 | do { | 124 | do { |
114 | next = pud_addr_end(addr, end); | 125 | next = pud_addr_end(addr, end); |
115 | if (pud_none_or_clear_bad(pud)) | 126 | if (pud_none_or_clear_bad(pud)) |
116 | continue; | 127 | continue; |
117 | change_pmd_range(vma, pud, addr, next, newprot, | 128 | pages += change_pmd_range(vma, pud, addr, next, newprot, |
118 | dirty_accountable); | 129 | dirty_accountable); |
119 | } while (pud++, addr = next, addr != end); | 130 | } while (pud++, addr = next, addr != end); |
131 | |||
132 | return pages; | ||
120 | } | 133 | } |
121 | 134 | ||
122 | static void change_protection(struct vm_area_struct *vma, | 135 | static unsigned long change_protection_range(struct vm_area_struct *vma, |
123 | unsigned long addr, unsigned long end, pgprot_t newprot, | 136 | unsigned long addr, unsigned long end, pgprot_t newprot, |
124 | int dirty_accountable) | 137 | int dirty_accountable) |
125 | { | 138 | { |
@@ -127,6 +140,7 @@ static void change_protection(struct vm_area_struct *vma, | |||
127 | pgd_t *pgd; | 140 | pgd_t *pgd; |
128 | unsigned long next; | 141 | unsigned long next; |
129 | unsigned long start = addr; | 142 | unsigned long start = addr; |
143 | unsigned long pages = 0; | ||
130 | 144 | ||
131 | BUG_ON(addr >= end); | 145 | BUG_ON(addr >= end); |
132 | pgd = pgd_offset(mm, addr); | 146 | pgd = pgd_offset(mm, addr); |
@@ -135,10 +149,30 @@ static void change_protection(struct vm_area_struct *vma, | |||
135 | next = pgd_addr_end(addr, end); | 149 | next = pgd_addr_end(addr, end); |
136 | if (pgd_none_or_clear_bad(pgd)) | 150 | if (pgd_none_or_clear_bad(pgd)) |
137 | continue; | 151 | continue; |
138 | change_pud_range(vma, pgd, addr, next, newprot, | 152 | pages += change_pud_range(vma, pgd, addr, next, newprot, |
139 | dirty_accountable); | 153 | dirty_accountable); |
140 | } while (pgd++, addr = next, addr != end); | 154 | } while (pgd++, addr = next, addr != end); |
155 | |||
141 | flush_tlb_range(vma, start, end); | 156 | flush_tlb_range(vma, start, end); |
157 | |||
158 | return pages; | ||
159 | } | ||
160 | |||
161 | unsigned long change_protection(struct vm_area_struct *vma, unsigned long start, | ||
162 | unsigned long end, pgprot_t newprot, | ||
163 | int dirty_accountable) | ||
164 | { | ||
165 | struct mm_struct *mm = vma->vm_mm; | ||
166 | unsigned long pages; | ||
167 | |||
168 | mmu_notifier_invalidate_range_start(mm, start, end); | ||
169 | if (is_vm_hugetlb_page(vma)) | ||
170 | pages = hugetlb_change_protection(vma, start, end, newprot); | ||
171 | else | ||
172 | pages = change_protection_range(vma, start, end, newprot, dirty_accountable); | ||
173 | mmu_notifier_invalidate_range_end(mm, start, end); | ||
174 | |||
175 | return pages; | ||
142 | } | 176 | } |
143 | 177 | ||
144 | int | 178 | int |
@@ -213,12 +247,8 @@ success: | |||
213 | dirty_accountable = 1; | 247 | dirty_accountable = 1; |
214 | } | 248 | } |
215 | 249 | ||
216 | mmu_notifier_invalidate_range_start(mm, start, end); | 250 | change_protection(vma, start, end, vma->vm_page_prot, dirty_accountable); |
217 | if (is_vm_hugetlb_page(vma)) | 251 | |
218 | hugetlb_change_protection(vma, start, end, vma->vm_page_prot); | ||
219 | else | ||
220 | change_protection(vma, start, end, vma->vm_page_prot, dirty_accountable); | ||
221 | mmu_notifier_invalidate_range_end(mm, start, end); | ||
222 | vm_stat_account(mm, oldflags, vma->vm_file, -nrpages); | 252 | vm_stat_account(mm, oldflags, vma->vm_file, -nrpages); |
223 | vm_stat_account(mm, newflags, vma->vm_file, nrpages); | 253 | vm_stat_account(mm, newflags, vma->vm_file, nrpages); |
224 | perf_event_mmap(vma); | 254 | perf_event_mmap(vma); |