DF-2865 / pbufres.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 | /* * pbufres.c -- KLD harness proving the pfreecnt check-then-decrement race * in the pbuf allocator family of sys/vm/vm_pager.c (getpbuf / getpbuf_kva / * getpbuf_mem / trypbuf / trypbuf_kva). * * Mechanism under test (getpbuf_kva, sys/vm/vm_pager.c:462-498): * * while (pfreecnt && *pfreecnt <= 0) { <-- :463 gate, NO lock * tsleep_interlock(pfreecnt, 0); ... * } * ...bucket scan... * spin_lock(&bswspin_kva[iter]); <-- per-BUCKET lock only * TAILQ_REMOVE(...); * atomic_add_int(&pbuf_kva_count, -1); * if (pfreecnt) * atomic_add_int(pfreecnt, -1); <-- :489-490 decrement * * The <=0 gate and the decrement are not atomic with respect to each other * and the bucket spinlock differs per CPU (iter = mycpuid & BSWHMASK), so * N CPUs can simultaneously observe *pfreecnt == 1, each pass the gate, and * each decrement -- driving a subsystem reservation counter that is supposed * to cap concurrency at 1 to 1-N, and putting N pbufs in flight for a * reservation of 1. * * This mirrors exactly how in-tree counters are used (nsw_rcount, * nsw_wcount_sync/async, v_pbuf_count, mnt_pbuf_count, nfs/smbfs/vinum * counters) with small caps under unprivileged-triggerable I/O load. * * Proves: * (1) vm.pbres_neg > 0 : counter observed NEGATIVE (race hit) * (2) vm.pbres_min < 0 : lowest observed counter value * (3) vm.pbres_maxinflight > 1 : more simultaneous pbuf holders than cap * * On a fixed kernel (atomic cmpxchg reservation) all three stay clean. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/kthread.h> #include <sys/buf.h> #include <sys/sysctl.h> #include <sys/types.h> #include <sys/time.h> static int pbres_cnt = 1; /* subsystem reservation, cap = 1 */ static int pbres_stop; static int pbres_alive; static int pbres_inflight; static int pbres_neg; static int pbres_min = 0x7fffffff; static int pbres_maxinflight; static int pbres_ncpu; static struct thread *pbres_td[MAXCPU]; SYSCTL_INT(_vm, OID_AUTO, pbres_neg, CTLFLAG_RD, &pbres_neg, 0, "observations of reservation counter < 0"); SYSCTL_INT(_vm, OID_AUTO, pbres_min, CTLFLAG_RD, &pbres_min, 0, "minimum observed reservation counter value"); SYSCTL_INT(_vm, OID_AUTO, pbres_maxinflight, CTLFLAG_RD, &pbres_maxinflight, 0, "max simultaneous pbuf holders (cap is 1)"); SYSCTL_INT(_vm, OID_AUTO, pbres_alive, CTLFLAG_RD, &pbres_alive, 0, "threads still running"); SYSCTL_INT(_vm, OID_AUTO, pbres_inflight, CTLFLAG_RD, &pbres_inflight, 0, "pbufs currently held by harness"); /* * Rescue: bump the reservation counter pbuf_adjcount()-style so sleepers * gated by the (known-buggy, DF-0953) wake thresholds can be released for * a clean module unload. Purely harness plumbing. */ static int pbres_bump_sysctl(SYSCTL_HANDLER_ARGS) { int error, val = 0; error = sysctl_handle_int(oidp, &val, 0, req); if (error || req->newptr == NULL) return (error); if (val > 0) { atomic_add_int(&pbres_cnt, val); wakeup(&pbres_cnt); kprintf("PBUFRES: bumped reservation by %d -> %d\n", val, pbres_cnt); } return (0); } SYSCTL_PROC(_vm, OID_AUTO, pbres_bump, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, pbres_bump_sysctl, "I", "rescue: add to the reservation counter and wake sleepers"); #define RUN_SECONDS 10 static void pbres_thread(void *arg __unused) { int start = ticks; while (!pbres_stop && (ticks - start) < RUN_SECONDS * hz) { struct buf *bp; int c, infl; /* * THE RACY CALL: gate at vm_pager.c:463, decrement at :489. */ bp = getpbuf_kva(&pbres_cnt); infl = atomic_fetchadd_int(&pbres_inflight, 1) + 1; if (infl > pbres_maxinflight) pbres_maxinflight = infl; c = pbres_cnt; if (c < 0) { if (atomic_fetchadd_int(&pbres_neg, 1) == 0) kprintf("PBUFRES: counter NEGATIVE (%d), " "cap=1, inflight=%d\n", c, infl); } if (c < pbres_min) pbres_min = c; atomic_add_int(&pbres_inflight, -1); relpbuf(bp, &pbres_cnt); } atomic_add_int(&pbres_alive, -1); wakeup(&pbres_alive); kthread_exit(); } static int pbres_modevent(module_t mod __unused, int type, void *data __unused) { int i, error; switch (type) { case MOD_LOAD: pbres_ncpu = ncpus; kprintf("PBUFRES: starting %d threads (cap=1) for %d s\n", pbres_ncpu, RUN_SECONDS); for (i = 0; i < pbres_ncpu; ++i) { error = kthread_create_cpu(pbres_thread, NULL, &pbres_td[i], i, "pbres%d", i); if (error) kprintf("PBUFRES: kthread cpu%d failed %d\n", i, error); else atomic_add_int(&pbres_alive, 1); } break; case MOD_UNLOAD: pbres_stop = 1; while (pbres_alive) tsleep(&pbres_alive, 0, "pbresw", hz / 10); kprintf("PBUFRES: done. neg=%d min=%d maxinflight=%d\n", pbres_neg, pbres_min, pbres_maxinflight); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t pbres_mod = { "pbufres", pbres_modevent, NULL }; DECLARE_MODULE(pbufres, pbres_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(pbufres, 1); |