DF-3093 / race2.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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | /* * DF-3093 racer v3: barrier-synchronized concurrent FAT allocation. * * MSDOSFS_LOCK_MP()/MSDOSFS_UNLOCK_MP() expand to NOTHING * (sys/vfs/msdosfs/msdosfsmount.h:216-218) and struct msdosfsmount has * no lock member, so clusteralloc()/chainalloc()/usemap_alloc() and the * pm_inusemap scan run unsynchronized on SMP. * * v3 (lessons from v1/v2 attempts): * - each child is pinned to its own CPU via usched_set(2) (forked * children otherwise timeshare one CPU and never truly overlap); * - the barrier is one go-word per child, each on its own cache line, * written sequentially by the parent (a single shared spin word * serializes the wakeups through one bouncing cache line); * - large per-write cluster counts (default 64 MiB = 16384 clusters * on a 4 KiB-cluster FAT32): chainlength() then READS 512 bitmap * words (~microseconds) before chainalloc() starts SETTING bits, * which scales the read-vs-set race window into the microsecond * range instead of ~100 ns; * - target files are pre-created; children pre-open their fd so the * only post-barrier kernel work is the write(2) itself; * - distinct fill byte per child ('A'+i) so cross-linked clusters * show as foreign bytes; the offline FAT in-degree scanner * (fat_scan.c) is the primary detector. * * build: cc -O2 -o race2 race2.c * usage: ./race2 <mnt> <nprocs> <rounds> <chunkbytes> [pin=1] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <time.h> #include <sys/types.h> #include <sys/usched.h> #include <sys/wait.h> #include <sys/mman.h> #define MAXP 32 struct goent { volatile unsigned int v; char pad[60]; }; struct shm { struct goent go[MAXP]; /* per-child barrier words */ volatile unsigned int ready[MAXP]; volatile long wrv[MAXP]; /* write() retval */ volatile int werr[MAXP]; /* errno if short */ volatile long long trel[MAXP]; /* ns timestamp at release */ volatile long long tdone[MAXP]; /* ns at write() return */ }; static struct shm *shm; static long long nsnow(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ((long long)ts.tv_sec * 1000000000LL + ts.tv_nsec); } int main(int argc, char **argv) { const char *mnt; int nprocs, rounds, pin; long chunk; int i, r, fd; char path[512]; char *buf; pid_t pids[MAXP]; if (argc != 5 && argc != 6) { fprintf(stderr, "usage: %s mnt nprocs rounds chunkbytes [pin=1]\n", argv[0]); return (1); } mnt = argv[1]; nprocs = atoi(argv[2]); rounds = atoi(argv[3]); chunk = strtol(argv[4], NULL, 0); pin = (argc == 6) ? atoi(argv[5]) : 1; if (nprocs < 2 || nprocs > MAXP || rounds < 1 || chunk < 512) { fprintf(stderr, "bad args\n"); return (1); } shm = mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); if (shm == MAP_FAILED) { perror("mmap"); return (1); } memset((void *)shm, 0, sizeof(*shm)); /* pre-create every round's files, serialized, before any racing */ for (r = 0; r < rounds; r++) { for (i = 0; i < nprocs; i++) { snprintf(path, sizeof(path), "%s/f%dr%d", mnt, r, i); fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror(path); return (1); } close(fd); } } buf = malloc(chunk); for (i = 0; i < nprocs; i++) { pid_t pid = fork(); if (pid == 0) { if (pin) { int cpuid = i; if (usched_set(0, USCHED_SET_CPU, &cpuid, sizeof(cpuid)) != 0) perror("usched_set"); } memset(buf, 'A' + (i % 26), chunk); for (r = 0; r < rounds; r++) { long long t0; ssize_t n; snprintf(path, sizeof(path), "%s/f%dr%d", mnt, r, i); fd = open(path, O_WRONLY); if (fd < 0) { shm->wrv[i * 8 + r % 8] = -1000 - errno; _exit(1); } __sync_synchronize(); shm->ready[i] = r + 1; while (shm->go[i].v < (unsigned)(r + 1)) ; __sync_synchronize(); t0 = nsnow(); shm->trel[i] = t0; n = write(fd, buf, chunk); shm->tdone[i] = nsnow(); shm->wrv[i] = (long)n; if (n != chunk) shm->werr[i] = errno; close(fd); } _exit(0); } pids[i] = pid; } /* parent: for each round, wait for readiness then fire */ for (r = 0; r < rounds; r++) { int spin = 0; for (;;) { int done = 1; for (i = 0; i < nprocs; i++) if (shm->ready[i] < (unsigned)(r + 1)) { done = 0; break; } if (done) break; usleep(50); if (++spin > 4000000) { fprintf(stderr, "barrier timeout r=%d\n", r); return (1); } } __sync_synchronize(); for (i = 0; i < nprocs; i++) shm->go[i].v = r + 1; /* FIRE (per line) */ } /* reap ALL children, then report (avoids reporting slots of * children that have not finished yet) */ for (i = 0; i < nprocs; i++) { int st; waitpid(pids[i], &st, 0); if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) fprintf(stderr, "child %d status %#x\n", i, st); } /* timing report for the LAST round */ { long long minrel = 0; for (i = 0; i < nprocs; i++) if (shm->trel[i] && (minrel == 0 || shm->trel[i] < minrel)) minrel = shm->trel[i]; for (i = 0; i < nprocs; i++) printf(" child %d: rel+%lld ns write=%ld%s " "dur=%lld us\n", i, shm->trel[i] ? shm->trel[i] - minrel : -1, shm->wrv[i], shm->werr[i] ? strerror(shm->werr[i]) : "", shm->tdone[i] && shm->trel[i] ? (shm->tdone[i] - shm->trel[i]) / 1000 : -1); } /* in-mount foreign-byte check */ { unsigned long foreign_total = 0; char *rb = malloc(chunk); for (r = 0; r < rounds; r++) { for (i = 0; i < nprocs; i++) { ssize_t n, k; unsigned long foreign = 0; snprintf(path, sizeof(path), "%s/f%dr%d", mnt, r, i); fd = open(path, O_RDONLY); if (fd < 0) continue; n = read(fd, rb, chunk); for (k = 0; k < n; k++) if ((unsigned char)rb[k] != (unsigned char)('A' + (i % 26))) foreign++; if (foreign) { printf(" r%d p%d: %lu foreign bytes " "<== CROSS-LINK EVIDENCE\n", r, i, foreign); foreign_total += foreign; } close(fd); } } free(rb); printf("race2: %d procs x %d rounds x %ldB, " "foreign bytes total: %lu%s\n", nprocs, rounds, chunk, foreign_total, foreign_total ? " <== REPRODUCED" : ""); } return (0); } |