DF-3250 / race_e2.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 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 131 132 133 134 135 136 137 138 | /* * DF-3250 race driver - two processes, tight spin barrier, race open() * of the same (cold) ext2 inode via two DIFFERENT directories (hardlinks) * so the two lookups do not serialize on a common parent-dir lock. * Both threads miss ext2_ihashget and enter ext2_alloc_vnode; when the * loser's hash_lock check lands after the winner's unlock, ext2_ihashins * returns EBUSY and the handler at ext2_vfsops.c:1313-1320 double-frees * the inode -> KKASSERT bmap panic (KMALLOC_CHECK_DOUBLE_FREE). * * usage: race_e2 <dirA> <dirB> <nfiles> [max_seconds] * thread0 opens <dirA>/rNNNNN, thread1 opens <dirB>/rNNNNN (same ino) */ #include <sys/types.h> #include <sys/mman.h> #include <sys/wait.h> #include <sys/usched.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <time.h> struct shared { volatile uint64_t narrate; /* monotonic arrival count */ volatile uint64_t ndone; /* monotonic completion count */ volatile uint32_t stop; }; static inline void cpu_pause(void) { __asm__ volatile("pause"); } static inline uint64_t rdtsc(void) { unsigned int lo, hi; __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi)); return ((uint64_t)hi << 32) | lo; } int main(int argc, char **argv) { const char *dir; long nfiles; int id, fd; uint64_t k; time_t t0, tmax = 0; struct shared *s; char path[512]; if (argc < 4) { fprintf(stderr, "usage: %s <dirA> <dirB> <nfiles> " "[max_seconds]\n", argv[0]); return (2); } id = fork() != 0; /* parent: 1, child: 0 */ dir = id ? argv[2] : argv[1]; /* parent->dirB, child->dirA */ nfiles = atol(argv[3]); if (argc > 4) tmax = atol(argv[4]); s = mmap(NULL, sizeof(*s), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); memset((void *)s, 0, sizeof(*s)); id = fork() != 0; /* parent: 1, child: 0 */ { int cpu = id; if (usched_set(getpid(), USCHED_SET_CPU, &cpu, sizeof(cpu)) != 0) fprintf(stderr, "id=%d: usched_set(cpu %d) FAILED " "(errno-based pin fallback)\n", id, cpu); else fprintf(stderr, "id=%d pinned to cpu %d\n", id, cpu); } t0 = time(NULL); for (k = 0; ; k++) { uint64_t idx; if (s->stop) break; if (tmax && (uint64_t)(time(NULL) - t0) > (uint64_t)tmax) { s->stop = 1; break; } /* barrier: both arrive for round k */ __sync_add_and_fetch(&s->narrate, 1); while (s->narrate < 2 * (k + 1)) cpu_pause(); /* * Phase sweep: the parent (thread B, second arriver) * delays (k mod 2000) * ~1400 tsc-cycles (approx 0..4ms * at ~3GHz) before its open, so across rounds it samples * every offset relative to the winner's whole cold-vget * timeline (dir-cache misses included). */ if (id) { uint64_t spin = (k % 2000ULL) * 1400ULL; uint64_t tend = rdtsc() + spin; while (rdtsc() < tend) cpu_pause(); } idx = k % (uint64_t)nfiles; snprintf(path, sizeof(path), "%s/r%05llu", dir, (unsigned long long)idx); fd = open(path, O_RDONLY); if (fd >= 0) close(fd); /* completion barrier for round k */ __sync_add_and_fetch(&s->ndone, 1); while (s->ndone < 2 * (k + 1)) cpu_pause(); if (id && (k % 20000) == 0) fprintf(stderr, "attempts=%llu elapsed=%ld\n", (unsigned long long)k, (long)(time(NULL) - t0)); } if (id) { int st; wait(&st); fprintf(stderr, "total attempts ~%llu, exiting\n", (unsigned long long)k); } return (0); } |