|
| 1 | +#include <pthread.h> |
| 2 | +#include <assert.h> |
| 3 | +#include <err.h> |
| 4 | +#include <stdlib.h> |
| 5 | +#include <stdio.h> |
| 6 | +#include <sys/mman.h> |
| 7 | +#include <unistd.h> |
| 8 | +#include <sys/signal.h> |
| 9 | +#include <fcntl.h> |
| 10 | + |
| 11 | +#include <mach/mach.h> |
| 12 | +#include <mach/mach_vm.h> |
| 13 | + |
| 14 | +unsigned long *mapping = (void*)0x900000000UL; |
| 15 | +mach_vm_address_t mapping2 = 0x900000000UL; |
| 16 | + |
| 17 | +static const unsigned long magic_value = 0; |
| 18 | + |
| 19 | +volatile int loops = 0; |
| 20 | +void *thread_fn(void *dummy) { |
| 21 | + while (1) { |
| 22 | + loops++; |
| 23 | + unsigned long value = *(volatile unsigned long *)mapping; |
| 24 | + if (value != magic_value) { |
| 25 | + printf("saw 0x%016lx\n", value); |
| 26 | + } |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +//void *thread_fn_dummy(void *dummy) { while (1); } |
| 31 | + |
| 32 | +int main(void) { |
| 33 | + setbuf(stdout, NULL); |
| 34 | + if (mach_vm_allocate(mach_task_self(), &mapping2, 0x4000, VM_FLAGS_FIXED|VM_FLAGS_PURGABLE) != KERN_SUCCESS) |
| 35 | + errx(1, "mach_vm_allocate"); |
| 36 | + assert(mapping2 == (unsigned long)mapping); |
| 37 | + mapping[1] = 1; // fault in |
| 38 | + |
| 39 | + pthread_t thread; |
| 40 | + //if (pthread_create(&thread, NULL, thread_fn_dummy, NULL)) |
| 41 | + // errx(1, "pthread_create"); |
| 42 | + if (pthread_create(&thread, NULL, thread_fn, NULL)) |
| 43 | + errx(1, "pthread_create"); |
| 44 | + while (1) { |
| 45 | + usleep(1000); |
| 46 | + int state = VM_PURGABLE_EMPTY; |
| 47 | + int res; |
| 48 | + if ((res=mach_vm_purgable_control(mach_task_self(), mapping2, VM_PURGABLE_SET_STATE, &state)) != KERN_SUCCESS) |
| 49 | + errx(1, "mach_vm_purgable_control (set empty) = %d", res); |
| 50 | + usleep(1000); |
| 51 | + state = VM_PURGABLE_NONVOLATILE; |
| 52 | + if (mach_vm_purgable_control(mach_task_self(), mapping2, VM_PURGABLE_SET_STATE, &state) != KERN_SUCCESS) |
| 53 | + errx(1, "mach_vm_purgable_control (set nonvolatile)"); |
| 54 | + mapping[1] = 1; |
| 55 | + } |
| 56 | +} |
| 57 | +================= |
| 58 | + |
| 59 | +Unfortunately, the only Mac I have here is a Mac Mini, so I patched |
| 60 | +the function pmap_flush() in XNU (version 4570.71.2) as follows to |
| 61 | +make the same problem appear on machines with less cores (and also |
| 62 | +added a bunch of printfs): |
| 63 | + |
| 64 | +================= |
| 65 | + mp_disable_preemption(); |
| 66 | + |
| 67 | + my_cpu = cpu_number(); |
| 68 | + cpus_to_signal = pfc->pfc_cpus; |
| 69 | ++ cpus_to_signal &= 0; |
| 70 | + |
| 71 | + PMAP_TRACE_CONSTANT(PMAP_CODE(PMAP__FLUSH_DELAYED_TLBS) | DBG_FUNC_START, |
| 72 | + NULL, cpus_to_signal); |
| 73 | + |
| 74 | + for (cpu = 0, cpu_bit = 1; cpu < real_ncpus && cpus_to_signal; cpu++, cpu_bit <<= 1) { |
| 75 | +================= |
| 76 | + |
| 77 | +With the patched kernel, I can occasionally observe the effects of |
| 78 | +missing TLB flushes: |
| 79 | + |
| 80 | +================= |
| 81 | +$ gcc -o test test.c |
| 82 | +$ time ./test |
| 83 | +saw 0x0000000000000400 |
| 84 | +saw 0x00007fff64d472a8 |
| 85 | +saw 0xffffffffffffffff |
| 86 | +^C |
| 87 | + |
| 88 | +real 3m54.100s |
| 89 | +user 3m53.462s |
| 90 | +sys 0m4.032s |
0 commit comments