DF-2813 / ochang.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 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | /* * ochang.c - KLD driver proving the objcache_get() M_WAITOK lost-wakeup * (remote per-cpu magazine stranding) in sys/kern/kern_objcache.c. * * Finding DF-2813. * * Scenario (deterministic, private objcache): * ARM - drain the cache with M_NOWAIT gets until exhausted * (nheld == ncpus*2*magcap + cluster_limit objects held live; * this also *derives* magcap at runtime: (nheld-limit)/(2*ncpus)). * SLEEPER - kernel thread pinned to cpu5 calls objcache_get(oc, M_WAITOK); * cache exhausted, depot empty, own mags empty -> ssleep(depot) * forever (flags=0 => no PCATCH, timo=0 => no timeout). * STRAND - free exactly magcap objects on cpu0 (via IPI): they land in * cpu0's loaded magazine and NEVER reach the depot. The hot-path * wakeup check is cpucache-of-the-PUTTER -> wakeup_mycpu, which * cannot see the remote sleeper. Objects are available, the * sleeper never wakes. Observe at +1s/+5s/+10s. * PROBE - behavioral proof of stranding: run one objcache_get(M_NOWAIT) * + objcache_put() pair on the chosen cpu via IPI. On the * strand cpu it returns an object (from the per-cpu magazine, * before any exhaustion check); on the sleeper cpu it returns * NULL (exhausted). Same cache, same instant, different cpus. * RESCUE - positive control: free enough more objects on cpu0 to fill * both cpu0 magazines; the depot cycle (objcache_put depot path) * deposits a full magazine and does wakeup(depot) -> the sleeper * wakes within milliseconds. * * sysctls: * kern.ochang.cmd (write) 1=ARM 2=SLEEPER 3=STRAND 4=RESCUE 5=DRAIN 6=DESTROY 7=PROBE(strand cpu) 8=PROBE(sleeper cpu) * kern.ochang.status (read) counters + sleeper state * kern.ochang.strand_cpu (int) default 0 * kern.ochang.sleeper_cpu(int) default 5 * kern.ochang.nstrand (int) default 0 = auto (derived magcap) * kern.ochang.nrescue (int) default 0 = auto (2*magcap) * kern.ochang.last_probe (read) result of last PROBE: 1 obj, 0 NULL * * Build: make ; load with kldload ./ochang.ko */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/errno.h> #include <sys/malloc.h> #include <sys/objcache.h> #include <sys/sysctl.h> #include <sys/thread.h> #include <sys/thread2.h> #include <sys/globaldata.h> #include <sys/types.h> MALLOC_DEFINE(M_OCHANG, "ochang", "objcache hang test"); #define CLUSTER_LIMIT 8 #define MAXOBJ 512 static struct objcache *oc; static struct objcache_malloc_args margs = { 16, M_OCHANG }; static void *held[MAXOBJ]; static int nheld; static int consumed; /* held[0..consumed-1] already put */ static struct thread *sleeper_td; static volatile int sleeper_started; static volatile int sleeper_got; /* 0 = still inside objcache_get */ static volatile int sleeper_start_ticks; static volatile int sleeper_end_ticks; static void *sleeper_obj; static int strand_cpu = 0; static int sleeper_cpu = 5; static int nstrand; /* 0 => derived from ARM */ static int nrescue; /* 0 => 2 * nstrand */ static int magcap; static int last_probe = -1; static struct ipisync { volatile int done; } ipisync; static void sleeper_thread(void *arg) { void *obj; sleeper_started = 1; sleeper_start_ticks = ticks; obj = objcache_get(oc, M_WAITOK); /* expected: sleeps forever */ sleeper_end_ticks = ticks; sleeper_obj = obj; if (obj) sleeper_got = 1; else sleeper_got = -1; /* lwkt exit */ } static void ipi_puts_cb(void *arg1, int arg2, struct intrframe *frame __unused) { struct ipisync *s = arg1; int i; for (i = 0; i < arg2; i++) objcache_put(oc, held[consumed + i]); s->done = 1; wakeup(s); } static void ipi_probe_cb(void *arg1, int arg2, struct intrframe *frame __unused) { struct ipisync *s = arg1; void *obj; obj = objcache_get(oc, M_NOWAIT); if (obj != NULL) { last_probe = 1; objcache_put(oc, obj); /* return it to the same cpu mag */ } else { last_probe = 0; } s->done = 1; wakeup(s); } static int puts_on_cpu(int cpu, int n) { if (consumed + n > nheld) return (EINVAL); ipisync.done = 0; lwkt_send_ipiq3(globaldata_find(cpu), ipi_puts_cb, &ipisync, n); tsleep(&ipisync, 0, "oipi", 5 * hz); if (!ipisync.done) return (ETIMEDOUT); consumed += n; return (0); } static int probe_cpu(int cpu) { ipisync.done = 0; lwkt_send_ipiq3(globaldata_find(cpu), ipi_probe_cb, &ipisync, 0); tsleep(&ipisync, 0, "oprobe", 5 * hz); if (!ipisync.done) return (-1); return (last_probe); } static int ochang_cmd_sysctl(SYSCTL_HANDLER_ARGS) { int cmd, error; cmd = 0; error = sysctl_handle_int(oidp, &cmd, 0, req); if (error != 0 || req->newptr == NULL) return (error); switch (cmd) { case 1: /* ARM: exhaust the cache */ if (oc == NULL) return (ENXIO); nheld = 0; consumed = 0; while (nheld < MAXOBJ) { void *p = objcache_get(oc, M_NOWAIT); if (p == NULL) break; held[nheld++] = p; } magcap = (nheld - CLUSTER_LIMIT) / (2 * ncpus); if (nstrand == 0) nstrand = magcap; if (nrescue == 0) nrescue = 2 * magcap; kprintf("ochang: ARM: drained %d objects, magcap=%d " "(expect %d)\n", nheld, magcap, ncpus * 2 * magcap + CLUSTER_LIMIT); break; case 2: /* SLEEPER: thread on sleeper_cpu */ if (oc == NULL || sleeper_td != NULL) return (EBUSY); sleeper_started = 0; sleeper_got = 0; lwkt_create(sleeper_thread, NULL, &sleeper_td, NULL, 0, sleeper_cpu, "ochang_slp"); break; case 3: /* STRAND: nstrand puts on strand_cpu */ if (oc == NULL) return (ENXIO); error = puts_on_cpu(strand_cpu, nstrand); kprintf("ochang: STRAND: %d puts on cpu%d -> %d\n", nstrand, strand_cpu, error); break; case 4: /* RESCUE: nrescue more puts */ if (oc == NULL) return (ENXIO); error = puts_on_cpu(strand_cpu, nrescue); kprintf("ochang: RESCUE: %d puts on cpu%d -> %d\n", nrescue, strand_cpu, error); break; case 5: /* DRAIN: return the rest */ if (oc == NULL) return (ENXIO); if (sleeper_got == 1 && sleeper_obj != NULL) { objcache_put(oc, sleeper_obj); sleeper_obj = NULL; } while (consumed < nheld) { objcache_put(oc, held[consumed]); consumed++; } kprintf("ochang: DRAIN: all objects returned\n"); break; case 6: /* DESTROY */ if (oc == NULL) return (ENXIO); if (sleeper_td != NULL && sleeper_got == 0) return (EBUSY); /* still hung */ objcache_destroy(oc); oc = NULL; sleeper_td = NULL; kprintf("ochang: cache destroyed\n"); break; case 7: /* PROBE strand cpu */ if (oc == NULL) return (ENXIO); last_probe = probe_cpu(strand_cpu); kprintf("ochang: PROBE cpu%d -> %s\n", strand_cpu, last_probe == 1 ? "OBJECT AVAILABLE" : last_probe == 0 ? "NULL (exhausted)" : "timeout"); break; case 8: /* PROBE sleeper cpu */ if (oc == NULL) return (ENXIO); last_probe = probe_cpu(sleeper_cpu); kprintf("ochang: PROBE cpu%d -> %s\n", sleeper_cpu, last_probe == 1 ? "OBJECT AVAILABLE" : last_probe == 0 ? "NULL (exhausted)" : "timeout"); break; default: return (EINVAL); } return (0); } static int ochang_status_sysctl(SYSCTL_HANDLER_ARGS) { char buf[1024]; int len = 0; len += ksnprintf(buf + len, sizeof(buf) - len, "nheld=%d consumed=%d magcap=%d nstrand=%d nrescue=%d " "last_probe=%d\n", nheld, consumed, magcap, nstrand, nrescue, last_probe); len += ksnprintf(buf + len, sizeof(buf) - len, "sleeper: started=%d got=%d", sleeper_started, sleeper_got); if (sleeper_started && sleeper_got == 1) len += ksnprintf(buf + len, sizeof(buf) - len, " latency=%d ticks", sleeper_end_ticks - sleeper_start_ticks); else if (sleeper_started && sleeper_got == 0 && sleeper_td != NULL) { int asleep_ticks = ticks - sleeper_start_ticks; len += ksnprintf(buf + len, sizeof(buf) - len, " STUCK for %d ticks (%d s), TDF_TSLEEPQ=%d wmesg=\"%s\"", asleep_ticks, asleep_ticks / hz, (sleeper_td->td_flags & TDF_TSLEEPQ) ? 1 : 0, sleeper_td->td_wmesg ? sleeper_td->td_wmesg : "?"); } len += ksnprintf(buf + len, sizeof(buf) - len, "\n"); return (SYSCTL_OUT(req, buf, len)); } SYSCTL_NODE(_kern, OID_AUTO, ochang, CTLFLAG_RW, 0, "ochang"); SYSCTL_PROC(_kern_ochang, OID_AUTO, cmd, CTLTYPE_INT | CTLFLAG_RW, 0, 0, ochang_cmd_sysctl, "I", "ochang command"); SYSCTL_PROC(_kern_ochang, OID_AUTO, status, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, ochang_status_sysctl, "A", "ochang status"); SYSCTL_INT(_kern_ochang, OID_AUTO, strand_cpu, CTLFLAG_RW, &strand_cpu, 0, "cpu where stranded frees happen"); SYSCTL_INT(_kern_ochang, OID_AUTO, sleeper_cpu, CTLFLAG_RW, &sleeper_cpu, 0, "cpu of the sleeping getter"); SYSCTL_INT(_kern_ochang, OID_AUTO, nstrand, CTLFLAG_RW, &nstrand, 0, "objects freed on strand cpu (0=auto)"); SYSCTL_INT(_kern_ochang, OID_AUTO, nrescue, CTLFLAG_RW, &nrescue, 0, "objects freed for rescue (0=auto)"); SYSCTL_INT(_kern_ochang, OID_AUTO, last_probe, CTLFLAG_RD, &last_probe, 0, "result of last probe"); static int ochang_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: oc = objcache_create("ochang", CLUSTER_LIMIT, 0, NULL, NULL, NULL, objcache_malloc_alloc, objcache_malloc_free, &margs); kprintf("ochang: cache created, ncpus=%d\n", ncpus); break; case MOD_UNLOAD: if (oc != NULL) { kprintf("ochang: cache still alive, refusing " "(cmd 4 then 5 6 first)\n"); return (EBUSY); } break; default: break; } return (0); } static moduledata_t ochang_mod = { "ochang", ochang_modevent, NULL }; DECLARE_MODULE(ochang, ochang_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |