DF-2729 / df2728.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 | /* * DF-2728 / DF-2729 trigger module for sys/kern/lwkt_thread.c (DragonFly 6.5-DEVELOPMENT) * * Mode 1 (DF-2728): remotely schedule a *preemptable* (interrupt-style) thread via * the generic _lwkt_schedule() remote branch (sys/kern/lwkt_thread.c:1310). * On the target cpu the IPI is processed by lwkt_process_ipiq_frame() with a * frame, lwkt_schedule_remote() (lwkt_thread.c:1336) deliberately drops the * critical section and calls _lwkt_schedule() -> _lwkt_schedule_post() -> * ntd->td_preemptable(ntd, 1) == lwkt_preempt(). * lwkt_preempt() line 1026 then executes: * KKASSERT(gd->gd_processing_ipiq == 0); * while gd->gd_processing_ipiq == 1 (incremented by lwkt_process_ipiq_frame * at sys/kern/lwkt_ipiq.c:605) -> INVARIANTS panic. * On a stock (non-INVariANTS) kernel the exact same sequence is the *designed* * preemption path and runs cleanly (fully balanced crit/intr-nesting counts). * * Mode 2 (DF-2729): simulate the push/pull migration window (lwkt_setcpu_self / * lwkt_giveaway set TDF_MIGRATING before the thread is inert; a racing wakeup * IPI lands on the old home cpu) by setting TDF_MIGRATING on a thread homed * on the target cpu, then calling lwkt_schedule() from this cpu. * The IPI handler runs lwkt_schedule_remote() -> _lwkt_schedule() which first * hits: * KKASSERT((td->td_flags & TDF_MIGRATING) == 0); (lwkt_thread.c:1293) * even though _lwkt_enqueue() (lwkt_thread.c:188) explicitly and *by design* * swallows enqueues of TDF_MIGRATING threads. INVARIANTS panic; stock kernel * gracefully skips the enqueue (the designed behavior the assert forbids). * * Mode 3 (control): plain remote schedule of a non-migrating, non-preemptable * thread - must NOT panic. Proves the module machinery itself is sound. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/thread.h> #include <sys/globaldata.h> #include <machine/smp.h> #include <machine/cpu.h> /* cpu_set_thread_handler */ /* no prototype in a clean header on this branch; exported kernel symbol */ void cpu_set_thread_handler(thread_t td, void (*rfunc)(void), void (*func)(void *), void *arg); static struct thread *g_td1; /* preemptable target (mode 1) */ static struct thread *g_td2; /* migrating target (mode 2) */ static int g_tcpu; static void df2728_sleeper(void *arg __unused) { /* ithread-style body: never runs before the triggers fire anyway */ for (;;) { lwkt_deschedule_self(curthread); lwkt_switch(); } } static int df2728_mode_sysctl(SYSCTL_HANDLER_ARGS) { int mode = 0; int error; error = sysctl_handle_int(oidp, &mode, 0, req); if (error || req->newptr == NULL) return (error); switch (mode) { case 1: kprintf("df2728: mode1(DF-2728): lwkt_schedule(td1=%p) " "cpu%d -> cpu%d, td_preemptable=lwkt_preempt\n", g_td1, mycpuid, g_tcpu); lwkt_schedule(g_td1); kprintf("df2728: mode1: returned WITHOUT panic " "(non-INVariANTS semantics: designed preempt)\n"); break; case 2: kprintf("df2728: mode2(DF-2729): TDF_MIGRATING window + " "lwkt_schedule(td2=%p) cpu%d -> cpu%d\n", g_td2, mycpuid, g_tcpu); atomic_set_int(&g_td2->td_flags, TDF_MIGRATING); lwkt_schedule(g_td2); atomic_clear_int(&g_td2->td_flags, TDF_MIGRATING); kprintf("df2728: mode2: returned WITHOUT panic " "(non-INVariANTS semantics: enqueue swallowed by design)\n"); break; case 3: kprintf("df2728: mode3(control): plain remote schedule td2=%p\n", g_td2); lwkt_schedule(g_td2); kprintf("df2728: mode3: ok, no panic (control)\n"); break; default: return (EINVAL); } return (0); } SYSCTL_PROC(_debug, OID_AUTO, df2728, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df2728_mode_sysctl, "I", "DF-2728/DF-2729 trigger: 1|2|3"); static int df2728_modevent(module_t mod __unused, int type, void *data __unused) { switch (type) { case MOD_LOAD: g_tcpu = (mycpuid + 1) % ncpus; /* interrupt-thread analogue: never started, preemptable */ g_td1 = lwkt_alloc_thread(NULL, LWKT_THREAD_STACK, g_tcpu, TDF_NOSTART); g_td1->td_preemptable = lwkt_preempt; g_td1->td_pri = TDPRI_INT_MED; cpu_set_thread_handler(g_td1, lwkt_exit, df2728_sleeper, NULL); /* plain kernel-thread analogue: never started */ g_td2 = lwkt_alloc_thread(NULL, LWKT_THREAD_STACK, g_tcpu, TDF_NOSTART); g_td2->td_pri = TDPRI_KERN_DAEMON; cpu_set_thread_handler(g_td2, lwkt_exit, df2728_sleeper, NULL); kprintf("df2728: loaded on cpu%d, target cpu%d, td1=%p td2=%p\n", mycpuid, g_tcpu, g_td1, g_td2); return (0); case MOD_UNLOAD: return (0); } return (EOPNOTSUPP); } static moduledata_t df2728_mod = { "df2728", df2728_modevent, 0 }; DECLARE_MODULE(df2728, df2728_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |