Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/tlb: replace INVALIDATE_TLB_VECTOR by CALL_FUNCTION_VECTOR

There are 32 INVALIDATE_TLB_VECTOR now in kernel. That is quite big
amount of vector in IDT. But it is still not enough, since modern x86
sever has more cpu number. That still causes heavy lock contention
in TLB flushing.

The patch using generic smp call function to replace it. That saved 32
vector number in IDT, and resolved the lock contention in TLB
flushing on large system.

In the NHM EX machine 4P * 8cores * HT = 64 CPUs, hackbench pthread
has 3% performance increase.

Signed-off-by: Alex Shi <alex.shi@intel.com>
Link: http://lkml.kernel.org/r/1340845344-27557-9-git-send-email-alex.shi@intel.com
Signed-off-by: H. Peter Anvin <hpa@zytor.com>

authored by

Alex Shi and committed by
H. Peter Anvin
52aec330 611ae8e3

+45 -304
-9
arch/x86/include/asm/entry_arch.h
··· 15 15 BUILD_INTERRUPT(call_function_single_interrupt,CALL_FUNCTION_SINGLE_VECTOR) 16 16 BUILD_INTERRUPT(irq_move_cleanup_interrupt,IRQ_MOVE_CLEANUP_VECTOR) 17 17 BUILD_INTERRUPT(reboot_interrupt,REBOOT_VECTOR) 18 - 19 - .irp idx,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, \ 20 - 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 21 - .if NUM_INVALIDATE_TLB_VECTORS > \idx 22 - BUILD_INTERRUPT3(invalidate_interrupt\idx, 23 - (INVALIDATE_TLB_VECTOR_START)+\idx, 24 - smp_invalidate_interrupt) 25 - .endif 26 - .endr 27 18 #endif 28 19 29 20 BUILD_INTERRUPT(x86_platform_ipi, X86_PLATFORM_IPI_VECTOR)
-11
arch/x86/include/asm/irq_vectors.h
··· 119 119 */ 120 120 #define LOCAL_TIMER_VECTOR 0xef 121 121 122 - /* up to 32 vectors used for spreading out TLB flushes: */ 123 - #if NR_CPUS <= 32 124 - # define NUM_INVALIDATE_TLB_VECTORS (NR_CPUS) 125 - #else 126 - # define NUM_INVALIDATE_TLB_VECTORS (32) 127 - #endif 128 - 129 - #define INVALIDATE_TLB_VECTOR_END (0xee) 130 - #define INVALIDATE_TLB_VECTOR_START \ 131 - (INVALIDATE_TLB_VECTOR_END-NUM_INVALIDATE_TLB_VECTORS+1) 132 - 133 122 #define NR_VECTORS 256 134 123 135 124 #define FPU_IRQ 13
-18
arch/x86/kernel/entry_64.S
··· 1048 1048 apicinterrupt X86_PLATFORM_IPI_VECTOR \ 1049 1049 x86_platform_ipi smp_x86_platform_ipi 1050 1050 1051 - #ifdef CONFIG_SMP 1052 - ALIGN 1053 - INTR_FRAME 1054 - .irp idx,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, \ 1055 - 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 1056 - .if NUM_INVALIDATE_TLB_VECTORS > \idx 1057 - ENTRY(invalidate_interrupt\idx) 1058 - pushq_cfi $~(INVALIDATE_TLB_VECTOR_START+\idx) 1059 - jmp .Lcommon_invalidate_interrupt0 1060 - CFI_ADJUST_CFA_OFFSET -8 1061 - END(invalidate_interrupt\idx) 1062 - .endif 1063 - .endr 1064 - CFI_ENDPROC 1065 - apicinterrupt INVALIDATE_TLB_VECTOR_START, \ 1066 - invalidate_interrupt0, smp_invalidate_interrupt 1067 - #endif 1068 - 1069 1051 apicinterrupt THRESHOLD_APIC_VECTOR \ 1070 1052 threshold_interrupt smp_threshold_interrupt 1071 1053 apicinterrupt THERMAL_APIC_VECTOR \
-73
arch/x86/kernel/irqinit.c
··· 171 171 */ 172 172 alloc_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt); 173 173 174 - /* IPIs for invalidation */ 175 - #define ALLOC_INVTLB_VEC(NR) \ 176 - alloc_intr_gate(INVALIDATE_TLB_VECTOR_START+NR, \ 177 - invalidate_interrupt##NR) 178 - 179 - switch (NUM_INVALIDATE_TLB_VECTORS) { 180 - default: 181 - ALLOC_INVTLB_VEC(31); 182 - case 31: 183 - ALLOC_INVTLB_VEC(30); 184 - case 30: 185 - ALLOC_INVTLB_VEC(29); 186 - case 29: 187 - ALLOC_INVTLB_VEC(28); 188 - case 28: 189 - ALLOC_INVTLB_VEC(27); 190 - case 27: 191 - ALLOC_INVTLB_VEC(26); 192 - case 26: 193 - ALLOC_INVTLB_VEC(25); 194 - case 25: 195 - ALLOC_INVTLB_VEC(24); 196 - case 24: 197 - ALLOC_INVTLB_VEC(23); 198 - case 23: 199 - ALLOC_INVTLB_VEC(22); 200 - case 22: 201 - ALLOC_INVTLB_VEC(21); 202 - case 21: 203 - ALLOC_INVTLB_VEC(20); 204 - case 20: 205 - ALLOC_INVTLB_VEC(19); 206 - case 19: 207 - ALLOC_INVTLB_VEC(18); 208 - case 18: 209 - ALLOC_INVTLB_VEC(17); 210 - case 17: 211 - ALLOC_INVTLB_VEC(16); 212 - case 16: 213 - ALLOC_INVTLB_VEC(15); 214 - case 15: 215 - ALLOC_INVTLB_VEC(14); 216 - case 14: 217 - ALLOC_INVTLB_VEC(13); 218 - case 13: 219 - ALLOC_INVTLB_VEC(12); 220 - case 12: 221 - ALLOC_INVTLB_VEC(11); 222 - case 11: 223 - ALLOC_INVTLB_VEC(10); 224 - case 10: 225 - ALLOC_INVTLB_VEC(9); 226 - case 9: 227 - ALLOC_INVTLB_VEC(8); 228 - case 8: 229 - ALLOC_INVTLB_VEC(7); 230 - case 7: 231 - ALLOC_INVTLB_VEC(6); 232 - case 6: 233 - ALLOC_INVTLB_VEC(5); 234 - case 5: 235 - ALLOC_INVTLB_VEC(4); 236 - case 4: 237 - ALLOC_INVTLB_VEC(3); 238 - case 3: 239 - ALLOC_INVTLB_VEC(2); 240 - case 2: 241 - ALLOC_INVTLB_VEC(1); 242 - case 1: 243 - ALLOC_INVTLB_VEC(0); 244 - break; 245 - } 246 - 247 174 /* IPI for generic function call */ 248 175 alloc_intr_gate(CALL_FUNCTION_VECTOR, call_function_interrupt); 249 176
+45 -193
arch/x86/mm/tlb.c
··· 28 28 * 29 29 * More scalable flush, from Andi Kleen 30 30 * 31 - * To avoid global state use 8 different call vectors. 32 - * Each CPU uses a specific vector to trigger flushes on other 33 - * CPUs. Depending on the received vector the target CPUs look into 34 - * the right array slot for the flush data. 35 - * 36 - * With more than 8 CPUs they are hashed to the 8 available 37 - * vectors. The limited global vector space forces us to this right now. 38 - * In future when interrupts are split into per CPU domains this could be 39 - * fixed, at the cost of triggering multiple IPIs in some cases. 31 + * Implement flush IPI by CALL_FUNCTION_VECTOR, Alex Shi 40 32 */ 41 33 42 - union smp_flush_state { 43 - struct { 44 - struct mm_struct *flush_mm; 45 - unsigned long flush_start; 46 - unsigned long flush_end; 47 - raw_spinlock_t tlbstate_lock; 48 - DECLARE_BITMAP(flush_cpumask, NR_CPUS); 49 - }; 50 - char pad[INTERNODE_CACHE_BYTES]; 51 - } ____cacheline_internodealigned_in_smp; 52 - 53 - /* State is put into the per CPU data section, but padded 54 - to a full cache line because other CPUs can access it and we don't 55 - want false sharing in the per cpu data segment. */ 56 - static union smp_flush_state flush_state[NUM_INVALIDATE_TLB_VECTORS]; 57 - 58 - static DEFINE_PER_CPU_READ_MOSTLY(int, tlb_vector_offset); 34 + struct flush_tlb_info { 35 + struct mm_struct *flush_mm; 36 + unsigned long flush_start; 37 + unsigned long flush_end; 38 + }; 59 39 60 40 /* 61 41 * We cannot call mmdrop() because we are in interrupt context, ··· 54 74 EXPORT_SYMBOL_GPL(leave_mm); 55 75 56 76 /* 57 - * 58 77 * The flush IPI assumes that a thread switch happens in this order: 59 78 * [cpu0: the cpu that switches] 60 79 * 1) switch_mm() either 1a) or 1b) 61 80 * 1a) thread switch to a different mm 62 - * 1a1) cpu_clear(cpu, old_mm->cpu_vm_mask); 63 - * Stop ipi delivery for the old mm. This is not synchronized with 64 - * the other cpus, but smp_invalidate_interrupt ignore flush ipis 65 - * for the wrong mm, and in the worst case we perform a superfluous 66 - * tlb flush. 67 - * 1a2) set cpu mmu_state to TLBSTATE_OK 68 - * Now the smp_invalidate_interrupt won't call leave_mm if cpu0 69 - * was in lazy tlb mode. 70 - * 1a3) update cpu active_mm 81 + * 1a1) set cpu_tlbstate to TLBSTATE_OK 82 + * Now the tlb flush NMI handler flush_tlb_func won't call leave_mm 83 + * if cpu0 was in lazy tlb mode. 84 + * 1a2) update cpu active_mm 71 85 * Now cpu0 accepts tlb flushes for the new mm. 72 - * 1a4) cpu_set(cpu, new_mm->cpu_vm_mask); 86 + * 1a3) cpu_set(cpu, new_mm->cpu_vm_mask); 73 87 * Now the other cpus will send tlb flush ipis. 74 88 * 1a4) change cr3. 89 + * 1a5) cpu_clear(cpu, old_mm->cpu_vm_mask); 90 + * Stop ipi delivery for the old mm. This is not synchronized with 91 + * the other cpus, but flush_tlb_func ignore flush ipis for the wrong 92 + * mm, and in the worst case we perform a superfluous tlb flush. 75 93 * 1b) thread switch without mm change 76 - * cpu active_mm is correct, cpu0 already handles 77 - * flush ipis. 78 - * 1b1) set cpu mmu_state to TLBSTATE_OK 94 + * cpu active_mm is correct, cpu0 already handles flush ipis. 95 + * 1b1) set cpu_tlbstate to TLBSTATE_OK 79 96 * 1b2) test_and_set the cpu bit in cpu_vm_mask. 80 97 * Atomically set the bit [other cpus will start sending flush ipis], 81 98 * and test the bit. ··· 85 108 * runs in kernel space, the cpu could load tlb entries for user space 86 109 * pages. 87 110 * 88 - * The good news is that cpu mmu_state is local to each cpu, no 111 + * The good news is that cpu_tlbstate is local to each cpu, no 89 112 * write/read ordering problems. 90 113 */ 91 114 92 115 /* 93 - * TLB flush IPI: 94 - * 116 + * TLB flush funcation: 95 117 * 1) Flush the tlb entries if the cpu uses the mm that's being flushed. 96 118 * 2) Leave the mm if we are in the lazy tlb mode. 97 - * 98 - * Interrupts are disabled. 99 119 */ 100 - 101 - /* 102 - * FIXME: use of asmlinkage is not consistent. On x86_64 it's noop 103 - * but still used for documentation purpose but the usage is slightly 104 - * inconsistent. On x86_32, asmlinkage is regparm(0) but interrupt 105 - * entry calls in with the first parameter in %eax. Maybe define 106 - * intrlinkage? 107 - */ 108 - #ifdef CONFIG_X86_64 109 - asmlinkage 110 - #endif 111 - void smp_invalidate_interrupt(struct pt_regs *regs) 120 + static void flush_tlb_func(void *info) 112 121 { 113 - unsigned int cpu; 114 - unsigned int sender; 115 - union smp_flush_state *f; 122 + struct flush_tlb_info *f = info; 116 123 117 - cpu = smp_processor_id(); 118 - /* 119 - * orig_rax contains the negated interrupt vector. 120 - * Use that to determine where the sender put the data. 121 - */ 122 - sender = ~regs->orig_ax - INVALIDATE_TLB_VECTOR_START; 123 - f = &flush_state[sender]; 124 + if (f->flush_mm != this_cpu_read(cpu_tlbstate.active_mm)) 125 + return; 124 126 125 - if (!cpumask_test_cpu(cpu, to_cpumask(f->flush_cpumask))) 126 - goto out; 127 - /* 128 - * This was a BUG() but until someone can quote me the 129 - * line from the intel manual that guarantees an IPI to 130 - * multiple CPUs is retried _only_ on the erroring CPUs 131 - * its staying as a return 132 - * 133 - * BUG(); 134 - */ 135 - 136 - if (f->flush_mm == this_cpu_read(cpu_tlbstate.active_mm)) { 137 - if (this_cpu_read(cpu_tlbstate.state) == TLBSTATE_OK) { 138 - if (f->flush_end == TLB_FLUSH_ALL 139 - || !cpu_has_invlpg) 140 - local_flush_tlb(); 141 - else if (!f->flush_end) 142 - __flush_tlb_single(f->flush_start); 143 - else { 144 - unsigned long addr; 145 - addr = f->flush_start; 146 - while (addr < f->flush_end) { 147 - __flush_tlb_single(addr); 148 - addr += PAGE_SIZE; 149 - } 127 + if (this_cpu_read(cpu_tlbstate.state) == TLBSTATE_OK) { 128 + if (f->flush_end == TLB_FLUSH_ALL || !cpu_has_invlpg) 129 + local_flush_tlb(); 130 + else if (!f->flush_end) 131 + __flush_tlb_single(f->flush_start); 132 + else { 133 + unsigned long addr; 134 + addr = f->flush_start; 135 + while (addr < f->flush_end) { 136 + __flush_tlb_single(addr); 137 + addr += PAGE_SIZE; 150 138 } 151 - } else 152 - leave_mm(cpu); 153 - } 154 - out: 155 - ack_APIC_irq(); 156 - smp_mb__before_clear_bit(); 157 - cpumask_clear_cpu(cpu, to_cpumask(f->flush_cpumask)); 158 - smp_mb__after_clear_bit(); 159 - inc_irq_stat(irq_tlb_count); 160 - } 139 + } 140 + } else 141 + leave_mm(smp_processor_id()); 161 142 162 - static void flush_tlb_others_ipi(const struct cpumask *cpumask, 163 - struct mm_struct *mm, unsigned long start, 164 - unsigned long end) 165 - { 166 - unsigned int sender; 167 - union smp_flush_state *f; 168 - 169 - /* Caller has disabled preemption */ 170 - sender = this_cpu_read(tlb_vector_offset); 171 - f = &flush_state[sender]; 172 - 173 - if (nr_cpu_ids > NUM_INVALIDATE_TLB_VECTORS) 174 - raw_spin_lock(&f->tlbstate_lock); 175 - 176 - f->flush_mm = mm; 177 - f->flush_start = start; 178 - f->flush_end = end; 179 - if (cpumask_andnot(to_cpumask(f->flush_cpumask), cpumask, cpumask_of(smp_processor_id()))) { 180 - /* 181 - * We have to send the IPI only to 182 - * CPUs affected. 183 - */ 184 - apic->send_IPI_mask(to_cpumask(f->flush_cpumask), 185 - INVALIDATE_TLB_VECTOR_START + sender); 186 - 187 - while (!cpumask_empty(to_cpumask(f->flush_cpumask))) 188 - cpu_relax(); 189 - } 190 - 191 - f->flush_mm = NULL; 192 - f->flush_start = 0; 193 - f->flush_end = 0; 194 - if (nr_cpu_ids > NUM_INVALIDATE_TLB_VECTORS) 195 - raw_spin_unlock(&f->tlbstate_lock); 196 143 } 197 144 198 145 void native_flush_tlb_others(const struct cpumask *cpumask, 199 146 struct mm_struct *mm, unsigned long start, 200 147 unsigned long end) 201 148 { 149 + struct flush_tlb_info info; 150 + info.flush_mm = mm; 151 + info.flush_start = start; 152 + info.flush_end = end; 153 + 202 154 if (is_uv_system()) { 203 155 unsigned int cpu; 204 156 205 157 cpu = smp_processor_id(); 206 158 cpumask = uv_flush_tlb_others(cpumask, mm, start, end, cpu); 207 159 if (cpumask) 208 - flush_tlb_others_ipi(cpumask, mm, start, end); 160 + smp_call_function_many(cpumask, flush_tlb_func, 161 + &info, 1); 209 162 return; 210 163 } 211 - flush_tlb_others_ipi(cpumask, mm, start, end); 164 + smp_call_function_many(cpumask, flush_tlb_func, &info, 1); 212 165 } 213 - 214 - static void __cpuinit calculate_tlb_offset(void) 215 - { 216 - int cpu, node, nr_node_vecs, idx = 0; 217 - /* 218 - * we are changing tlb_vector_offset for each CPU in runtime, but this 219 - * will not cause inconsistency, as the write is atomic under X86. we 220 - * might see more lock contentions in a short time, but after all CPU's 221 - * tlb_vector_offset are changed, everything should go normal 222 - * 223 - * Note: if NUM_INVALIDATE_TLB_VECTORS % nr_online_nodes !=0, we might 224 - * waste some vectors. 225 - **/ 226 - if (nr_online_nodes > NUM_INVALIDATE_TLB_VECTORS) 227 - nr_node_vecs = 1; 228 - else 229 - nr_node_vecs = NUM_INVALIDATE_TLB_VECTORS/nr_online_nodes; 230 - 231 - for_each_online_node(node) { 232 - int node_offset = (idx % NUM_INVALIDATE_TLB_VECTORS) * 233 - nr_node_vecs; 234 - int cpu_offset = 0; 235 - for_each_cpu(cpu, cpumask_of_node(node)) { 236 - per_cpu(tlb_vector_offset, cpu) = node_offset + 237 - cpu_offset; 238 - cpu_offset++; 239 - cpu_offset = cpu_offset % nr_node_vecs; 240 - } 241 - idx++; 242 - } 243 - } 244 - 245 - static int __cpuinit tlb_cpuhp_notify(struct notifier_block *n, 246 - unsigned long action, void *hcpu) 247 - { 248 - switch (action & 0xf) { 249 - case CPU_ONLINE: 250 - case CPU_DEAD: 251 - calculate_tlb_offset(); 252 - } 253 - return NOTIFY_OK; 254 - } 255 - 256 - static int __cpuinit init_smp_flush(void) 257 - { 258 - int i; 259 - 260 - for (i = 0; i < ARRAY_SIZE(flush_state); i++) 261 - raw_spin_lock_init(&flush_state[i].tlbstate_lock); 262 - 263 - calculate_tlb_offset(); 264 - hotcpu_notifier(tlb_cpuhp_notify, 0); 265 - return 0; 266 - } 267 - core_initcall(init_smp_flush); 268 166 269 167 void flush_tlb_current_task(void) 270 168 {