Blame view
mm/mmu_gather.c
8.44 KB
196d9d8bb mm/memory: Move m... |
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <linux/gfp.h> #include <linux/highmem.h> #include <linux/kernel.h> #include <linux/mmdebug.h> #include <linux/mm_types.h> #include <linux/pagemap.h> #include <linux/rcupdate.h> #include <linux/smp.h> #include <linux/swap.h> #include <asm/pgalloc.h> #include <asm/tlb.h> |
580a586c4 asm-generic/tlb: ... |
13 |
#ifndef CONFIG_MMU_GATHER_NO_GATHER |
952a31c9e asm-generic/tlb: ... |
14 |
|
196d9d8bb mm/memory: Move m... |
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
static bool tlb_next_batch(struct mmu_gather *tlb) { struct mmu_gather_batch *batch; batch = tlb->active; if (batch->next) { tlb->active = batch->next; return true; } if (tlb->batch_count == MAX_GATHER_BATCH_COUNT) return false; batch = (void *)__get_free_pages(GFP_NOWAIT | __GFP_NOWARN, 0); if (!batch) return false; tlb->batch_count++; batch->next = NULL; batch->nr = 0; batch->max = MAX_GATHER_BATCH; tlb->active->next = batch; tlb->active = batch; return true; } |
952a31c9e asm-generic/tlb: ... |
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
static void tlb_batch_pages_flush(struct mmu_gather *tlb) { struct mmu_gather_batch *batch; for (batch = &tlb->local; batch && batch->nr; batch = batch->next) { free_pages_and_swap_cache(batch->pages, batch->nr); batch->nr = 0; } tlb->active = &tlb->local; } static void tlb_batch_list_free(struct mmu_gather *tlb) { struct mmu_gather_batch *batch, *next; for (batch = tlb->local.next; batch; batch = next) { next = batch->next; free_pages((unsigned long)batch, 0); } tlb->local.next = NULL; } bool __tlb_remove_page_size(struct mmu_gather *tlb, struct page *page, int page_size) { struct mmu_gather_batch *batch; VM_BUG_ON(!tlb->end); |
3af4bd033 asm-generic/tlb: ... |
69 |
#ifdef CONFIG_MMU_GATHER_PAGE_SIZE |
952a31c9e asm-generic/tlb: ... |
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
VM_WARN_ON(tlb->page_size != page_size); #endif batch = tlb->active; /* * Add the page and check if we are full. If so * force a flush. */ batch->pages[batch->nr++] = page; if (batch->nr == batch->max) { if (!tlb_next_batch(tlb)) return true; batch = tlb->active; } VM_BUG_ON_PAGE(batch->nr > batch->max, page); return false; } |
580a586c4 asm-generic/tlb: ... |
88 |
#endif /* MMU_GATHER_NO_GATHER */ |
952a31c9e asm-generic/tlb: ... |
89 |
|
0d6e24d43 asm-generic/tlb: ... |
90 |
#ifdef CONFIG_MMU_GATHER_TABLE_FREE |
196d9d8bb mm/memory: Move m... |
91 |
|
0d6e24d43 asm-generic/tlb: ... |
92 93 94 95 96 97 98 99 100 101 102 |
static void __tlb_remove_table_free(struct mmu_table_batch *batch) { int i; for (i = 0; i < batch->nr; i++) __tlb_remove_table(batch->tables[i]); free_page((unsigned long)batch); } #ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE |
196d9d8bb mm/memory: Move m... |
103 104 |
/* |
0d6e24d43 asm-generic/tlb: ... |
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
* Semi RCU freeing of the page directories. * * This is needed by some architectures to implement software pagetable walkers. * * gup_fast() and other software pagetable walkers do a lockless page-table * walk and therefore needs some synchronization with the freeing of the page * directories. The chosen means to accomplish that is by disabling IRQs over * the walk. * * Architectures that use IPIs to flush TLBs will then automagically DTRT, * since we unlink the page, flush TLBs, free the page. Since the disabling of * IRQs delays the completion of the TLB flush we can never observe an already * freed page. * * Architectures that do not have this (PPC) need to delay the freeing by some * other means, this is that means. * * What we do is batch the freed directory pages (tables) and RCU free them. * We use the sched RCU variant, as that guarantees that IRQ/preempt disabling * holds off grace periods. * * However, in order to batch these pages we need to allocate storage, this * allocation is deep inside the MM code and can thus easily fail on memory * pressure. To guarantee progress we fall back to single table freeing, see * the implementation of tlb_remove_table_one(). * |
196d9d8bb mm/memory: Move m... |
131 |
*/ |
196d9d8bb mm/memory: Move m... |
132 133 134 135 136 |
static void tlb_remove_table_smp_sync(void *arg) { /* Simply deliver the interrupt */ } |
0d6e24d43 asm-generic/tlb: ... |
137 |
static void tlb_remove_table_sync_one(void) |
196d9d8bb mm/memory: Move m... |
138 139 140 141 142 143 |
{ /* * This isn't an RCU grace period and hence the page-tables cannot be * assumed to be actually RCU-freed. * * It is however sufficient for software page-table walkers that rely on |
0d6e24d43 asm-generic/tlb: ... |
144 |
* IRQ disabling. |
196d9d8bb mm/memory: Move m... |
145 146 |
*/ smp_call_function(tlb_remove_table_smp_sync, NULL, 1); |
196d9d8bb mm/memory: Move m... |
147 148 149 150 |
} static void tlb_remove_table_rcu(struct rcu_head *head) { |
0d6e24d43 asm-generic/tlb: ... |
151 152 |
__tlb_remove_table_free(container_of(head, struct mmu_table_batch, rcu)); } |
196d9d8bb mm/memory: Move m... |
153 |
|
0d6e24d43 asm-generic/tlb: ... |
154 155 156 157 |
static void tlb_remove_table_free(struct mmu_table_batch *batch) { call_rcu(&batch->rcu, tlb_remove_table_rcu); } |
196d9d8bb mm/memory: Move m... |
158 |
|
0d6e24d43 asm-generic/tlb: ... |
159 |
#else /* !CONFIG_MMU_GATHER_RCU_TABLE_FREE */ |
196d9d8bb mm/memory: Move m... |
160 |
|
0d6e24d43 asm-generic/tlb: ... |
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
static void tlb_remove_table_sync_one(void) { } static void tlb_remove_table_free(struct mmu_table_batch *batch) { __tlb_remove_table_free(batch); } #endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */ /* * If we want tlb_remove_table() to imply TLB invalidates. */ static inline void tlb_table_invalidate(struct mmu_gather *tlb) { if (tlb_needs_table_invalidate()) { /* * Invalidate page-table caches used by hardware walkers. Then * we still need to RCU-sched wait while freeing the pages * because software walkers can still be in-flight. */ tlb_flush_mmu_tlbonly(tlb); } } static void tlb_remove_table_one(void *table) { tlb_remove_table_sync_one(); __tlb_remove_table(table); |
196d9d8bb mm/memory: Move m... |
189 |
} |
0a8caf211 asm-generic/tlb: ... |
190 |
static void tlb_table_flush(struct mmu_gather *tlb) |
196d9d8bb mm/memory: Move m... |
191 192 193 194 195 |
{ struct mmu_table_batch **batch = &tlb->batch; if (*batch) { tlb_table_invalidate(tlb); |
0d6e24d43 asm-generic/tlb: ... |
196 |
tlb_remove_table_free(*batch); |
196d9d8bb mm/memory: Move m... |
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
*batch = NULL; } } void tlb_remove_table(struct mmu_gather *tlb, void *table) { struct mmu_table_batch **batch = &tlb->batch; if (*batch == NULL) { *batch = (struct mmu_table_batch *)__get_free_page(GFP_NOWAIT | __GFP_NOWARN); if (*batch == NULL) { tlb_table_invalidate(tlb); tlb_remove_table_one(table); return; } (*batch)->nr = 0; } (*batch)->tables[(*batch)->nr++] = table; if ((*batch)->nr == MAX_TABLE_BATCH) tlb_table_flush(tlb); } |
0d6e24d43 asm-generic/tlb: ... |
219 220 221 222 223 224 225 226 227 228 229 |
static inline void tlb_table_init(struct mmu_gather *tlb) { tlb->batch = NULL; } #else /* !CONFIG_MMU_GATHER_TABLE_FREE */ static inline void tlb_table_flush(struct mmu_gather *tlb) { } static inline void tlb_table_init(struct mmu_gather *tlb) { } #endif /* CONFIG_MMU_GATHER_TABLE_FREE */ |
196d9d8bb mm/memory: Move m... |
230 |
|
0a8caf211 asm-generic/tlb: ... |
231 232 |
static void tlb_flush_mmu_free(struct mmu_gather *tlb) { |
0a8caf211 asm-generic/tlb: ... |
233 |
tlb_table_flush(tlb); |
580a586c4 asm-generic/tlb: ... |
234 |
#ifndef CONFIG_MMU_GATHER_NO_GATHER |
0a8caf211 asm-generic/tlb: ... |
235 236 237 238 239 240 241 242 243 |
tlb_batch_pages_flush(tlb); #endif } void tlb_flush_mmu(struct mmu_gather *tlb) { tlb_flush_mmu_tlbonly(tlb); tlb_flush_mmu_free(tlb); } |
196d9d8bb mm/memory: Move m... |
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
/** * tlb_gather_mmu - initialize an mmu_gather structure for page-table tear-down * @tlb: the mmu_gather structure to initialize * @mm: the mm_struct of the target address space * @start: start of the region that will be removed from the page-table * @end: end of the region that will be removed from the page-table * * Called to initialize an (on-stack) mmu_gather structure for page-table * tear-down from @mm. The @start and @end are set to 0 and -1 * respectively when @mm is without users and we're going to destroy * the full address space (exit/execve). */ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long start, unsigned long end) { |
1808d65b5 asm-generic/tlb: ... |
259 260 261 262 |
tlb->mm = mm; /* Is it from 0 to ~0? */ tlb->fullmm = !(start | (end+1)); |
580a586c4 asm-generic/tlb: ... |
263 |
#ifndef CONFIG_MMU_GATHER_NO_GATHER |
1808d65b5 asm-generic/tlb: ... |
264 265 266 267 268 269 270 |
tlb->need_flush_all = 0; tlb->local.next = NULL; tlb->local.nr = 0; tlb->local.max = ARRAY_SIZE(tlb->__pages); tlb->active = &tlb->local; tlb->batch_count = 0; #endif |
0d6e24d43 asm-generic/tlb: ... |
271 |
tlb_table_init(tlb); |
3af4bd033 asm-generic/tlb: ... |
272 |
#ifdef CONFIG_MMU_GATHER_PAGE_SIZE |
1808d65b5 asm-generic/tlb: ... |
273 274 275 276 |
tlb->page_size = 0; #endif __tlb_reset_range(tlb); |
196d9d8bb mm/memory: Move m... |
277 278 |
inc_tlb_flush_pending(tlb->mm); } |
1808d65b5 asm-generic/tlb: ... |
279 280 281 282 283 284 285 286 287 |
/** * tlb_finish_mmu - finish an mmu_gather structure * @tlb: the mmu_gather structure to finish * @start: start of the region that will be removed from the page-table * @end: end of the region that will be removed from the page-table * * Called at the end of the shootdown operation to free up any resources that * were required. */ |
196d9d8bb mm/memory: Move m... |
288 289 290 291 292 |
void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end) { /* * If there are parallel threads are doing PTE changes on same range |
c1e8d7c6a mmap locking API:... |
293 |
* under non-exclusive lock (e.g., mmap_lock read-side) but defer TLB |
7a30df49f mm: mmu_gather: r... |
294 295 296 297 298 299 300 301 |
* flush by batching, one thread may end up seeing inconsistent PTEs * and result in having stale TLB entries. So flush TLB forcefully * if we detect parallel PTE batching threads. * * However, some syscalls, e.g. munmap(), may free page tables, this * needs force flush everything in the given range. Otherwise this * may result in having stale TLB entries for some architectures, * e.g. aarch64, that could specify flush what level TLB. |
196d9d8bb mm/memory: Move m... |
302 |
*/ |
1808d65b5 asm-generic/tlb: ... |
303 |
if (mm_tlb_flush_nested(tlb->mm)) { |
7a30df49f mm: mmu_gather: r... |
304 305 306 307 308 309 310 311 312 |
/* * The aarch64 yields better performance with fullmm by * avoiding multiple CPUs spamming TLBI messages at the * same time. * * On x86 non-fullmm doesn't yield significant difference * against fullmm. */ tlb->fullmm = 1; |
1808d65b5 asm-generic/tlb: ... |
313 |
__tlb_reset_range(tlb); |
7a30df49f mm: mmu_gather: r... |
314 |
tlb->freed_tables = 1; |
1808d65b5 asm-generic/tlb: ... |
315 |
} |
196d9d8bb mm/memory: Move m... |
316 |
|
1808d65b5 asm-generic/tlb: ... |
317 |
tlb_flush_mmu(tlb); |
580a586c4 asm-generic/tlb: ... |
318 |
#ifndef CONFIG_MMU_GATHER_NO_GATHER |
1808d65b5 asm-generic/tlb: ... |
319 320 |
tlb_batch_list_free(tlb); #endif |
196d9d8bb mm/memory: Move m... |
321 322 |
dec_tlb_flush_pending(tlb->mm); } |