code wiki / _hdl_build / nx_nishi_usb_preempt.nx

nx_nishi_usb_preempt.nx source

↩ module page · 148 lines · 9767 B

1// nx_nishi_usb_preempt.nx -- x86 ladder R-KERN-3: PREEMPTIVE multitasking (timer IRQ0 preempts tasks). 2// 3// The capstone of the kernel-internals thread: "a kernel that runs tasks". nx_kernel_sched proved a 4// COOPERATIVE scheduler (tasks yield). This proves a PREEMPTIVE one: a timer (IRQ0, IF-gated like the 5// real PIC/PIT, delivered between instructions) forces a context switch between two tasks that NEVER 6// yield -- each runs an infinite increment loop, and the timer alone makes both progress, round-robin. 7// It composes onto R-KERN-1's IDT: the timer handler is the IDT[0x20] vector; here the scheduler's 8// context switch + the IF-gated timer delivery are modeled (same level as nx_kernel_sched's emu_resume), 9// the tasks are real x86 the emu executes. 10// 11// The proof that the context switch is REAL (registers preserved across preemption): each task sets its 12// counter pointer rbx ONCE at entry, then loops reading/writing [rbx] without re-setting it. The timer 13// preempts mid-loop repeatedly; both counters keep advancing correctly ONLY if each task's rbx (and 14// rax/rcx) survive every switch -- i.e. a genuine per-task context, not shared state. 15// 16// KAT: (T1) BOTH tasks made real progress (both counters > 0) = preemptive multitasking; (T2) round-robin 17// fairness (the two counters are close, neither starved); (T3) the timer actually drove the switching 18// (>= ~the requested number of ticks occurred). NEG/liar-kill (T4): with the timer MASKED (the kernel's 19// STI is a no-op), task B NEVER runs (counter B == 0) -- proving progress is driven by real IF-gated timer 20// preemption, not by the tasks cooperating. 21// 22// HONEST SCOPE: the context-switch mechanism + the IF-gated periodic timer are modeled (as in 23// nx_kernel_sched / nx_nishios_irq); a real 8259 PIC + 8254 PIT + a full x86 context-switch handler that 24// saves/restores via the stack are the hardware refinements. No /dev, no hardware writes (Rule 26). 25// expect_exit: 0 license_tier: ORIGINAL 26import "nx_syscalls.nx" 27const CTR_MAGIC_5000000: i64 = 5000000 28const CTR_MAGIC_65536: i64 = 65536 29 30func pe_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 31func pe_num(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 } 32func pe_st64(mem: *u8, a: i64, v: i64) -> i64 { var i: i64=0; while i<8 { mem[a+i]=((v>>(i*8))&0xff) as u8; i=i+1 } return 0 } 33func pe_ld64(mem: *u8, a: i64) -> i64 { var v: i64=0; var i: i64=0; while i<8 { v=v|((mem[a+i] as i64)<<(i*8)); i=i+1 } return v } 34func pe_b(c: *u8, o: i64, b: i64) -> i64 { c[o]=(b & 0xff) as u8; return o+1 } 35func pe_mov(c: *u8, o: i64, modrm: i64, imm: i64) -> i64 { 36 c[o]=0x48 as u8; c[o+1]=0xC7 as u8; c[o+2]=(modrm&0xff) as u8 37 c[o+3]=(imm&0xff) as u8; c[o+4]=((imm>>8)&0xff) as u8; c[o+5]=((imm>>16)&0xff) as u8; c[o+6]=((imm>>24)&0xff) as u8 38 return o+7 39} 40 41// PREEMPTIVE 2-task emu. reg2[cur*16+i] = task cur's register i; pc2[cur] = task cur's pc. Between 42// instructions the timer ticks; every `thresh` instructions, if IF is set, it PREEMPTS (switches cur). 43// sti_en models the PIC/PIT being enabled (the kernel's STI). out[0]=ticks delivered. 44func emu_preempt(code: *u8, mem: *u8, thresh: i64, maxticks: i64, sti_en: i64, pcA: i64, pcB: i64, out: *i64) -> i64 { 45 let reg2: *i64 = sys_mmap(2 * 16 * 8) as *i64 46 let pc2: *i64 = sys_mmap(2 * 8) as *i64 47 pc2[0]=pcA; pc2[1]=pcB 48 var cur: i64=0 49 var timer: i64=0 50 var ticks: i64=0 51 var iflag: i64=0 52 var guard: i64=0 53 while guard < CTR_MAGIC_5000000 { 54 guard = guard + 1 55 if ticks >= maxticks { out[0]=ticks; return 0 } 56 timer = timer + 1 57 if timer >= thresh { timer=0; if iflag==1 { if cur==0 { cur=1 } else { cur=0 } ticks=ticks+1 } } // TIMER PREEMPT 58 let base: i64 = cur*16 59 let p: i64 = pc2[cur] 60 let b: i64 = code[p] as i64 61 var h: i64 = 0 62 if b==0xFB { if sti_en==1 { iflag=1 } pc2[cur]=p+1; h=1 } // sti 63 if h==0 { if b==0xFA { iflag=0; pc2[cur]=p+1; h=1 } } // cli 64 if h==0 { if b==0xEB { var r: i64=code[p+1] as i64; if r>127 { r=r-256 } pc2[cur]=p+2+r; h=1 } } // jmp rel8 65 if h==0 { if b==0x48 { 66 let o2: i64 = code[p+1] as i64 67 if o2==0xC7 { let m: i64=code[p+2] as i64; let imm: i64=(code[p+3] as i64)|((code[p+4] as i64)<<8)|((code[p+5] as i64)<<16)|((code[p+6] as i64)<<24); reg2[base+(m&7)]=imm; pc2[cur]=p+7; h=1 } 68 if h==0 { if o2==0x8B { let m: i64=code[p+2] as i64; if ((m>>6)&3)==3 { reg2[base+((m>>3)&7)]=reg2[base+(m&7)] } else { reg2[base+((m>>3)&7)]=pe_ld64(mem, reg2[base+(m&7)]) } pc2[cur]=p+3; h=1 } } 69 if h==0 { if o2==0x89 { let m: i64=code[p+2] as i64; if ((m>>6)&3)==3 { reg2[base+(m&7)]=reg2[base+((m>>3)&7)] } else { pe_st64(mem, reg2[base+(m&7)], reg2[base+((m>>3)&7)]) } pc2[cur]=p+3; h=1 } } 70 if h==0 { if o2==0x01 { let m: i64=code[p+2] as i64; reg2[base+(m&7)]=reg2[base+(m&7)]+reg2[base+((m>>3)&7)]; pc2[cur]=p+3; h=1 } } 71 if h==0 { out[0]=ticks; return 0 - 1 } 72 } } 73 if h==0 { out[0]=ticks; return 0 - 3 } 74 } 75 out[0]=ticks 76 return 0 77} 78 79const CTR_A: i64 = 0xA000 80const CTR_B: i64 = 0xB000 81 82func main() -> i64 { 83 pe_puts("x86 ladder R-KERN-3: PREEMPTIVE multitasking -- a timer IRQ preempts two non-yielding tasks\n" as *u8) 84 85 // author two tasks; each sets its counter pointer ONCE, enables nothing else, then loops forever 86 // incrementing [rbx]. (taskA also issues STI to enable the timer; taskB inherits IF.) 87 let code: *u8 = sys_mmap(256) 88 var o: i64 = 0 89 let taskA: i64 = o 90 o = pe_mov(code, o, 0xC3, CTR_A) // mov rbx, CTR_A 91 o = pe_mov(code, o, 0xC1, 1) // mov rcx, 1 92 o = pe_b(code, o, 0xFB) // sti (enable the timer) 93 let loopA: i64 = o 94 code[o]=0x48 as u8; code[o+1]=0x8B as u8; code[o+2]=0x03 as u8; o=o+3 // mov rax, [rbx] 95 code[o]=0x48 as u8; code[o+1]=0x01 as u8; code[o+2]=0xC8 as u8; o=o+3 // add rax, rcx 96 code[o]=0x48 as u8; code[o+1]=0x89 as u8; code[o+2]=0x03 as u8; o=o+3 // mov [rbx], rax 97 code[o]=0xEB as u8; code[o+1]=((loopA-(o+2)) & 0xff) as u8; o=o+2 // jmp loopA 98 let taskB: i64 = o 99 o = pe_mov(code, o, 0xC3, CTR_B) // mov rbx, CTR_B 100 o = pe_mov(code, o, 0xC1, 1) // mov rcx, 1 101 let loopB: i64 = o 102 code[o]=0x48 as u8; code[o+1]=0x8B as u8; code[o+2]=0x03 as u8; o=o+3 // mov rax, [rbx] 103 code[o]=0x48 as u8; code[o+1]=0x01 as u8; code[o+2]=0xC8 as u8; o=o+3 // add rax, rcx 104 code[o]=0x48 as u8; code[o+1]=0x89 as u8; code[o+2]=0x03 as u8; o=o+3 // mov [rbx], rax 105 code[o]=0xEB as u8; code[o+1]=((loopB-(o+2)) & 0xff) as u8; o=o+2 // jmp loopB 106 107 let THRESH: i64 = 6 108 let MAXTICKS: i64 = 60 109 110 // RUN 1: timer enabled -> preemptive round-robin 111 let mem: *u8 = sys_mmap(CTR_MAGIC_65536) 112 var z: i64=0 113 while z<CTR_MAGIC_65536 { mem[z]=0 as u8; z=z+1 } 114 let out: *i64 = sys_mmap(16) as *i64 115 emu_preempt(code, mem, THRESH, MAXTICKS, 1, taskA, taskB, out) 116 let cA: i64 = pe_ld64(mem, CTR_A) 117 let cB: i64 = pe_ld64(mem, CTR_B) 118 let ticks: i64 = out[0] 119 120 // RUN 2 (NEG CONTROL): timer masked (STI is a no-op) -> task B starves 121 let mem2: *u8 = sys_mmap(CTR_MAGIC_65536) 122 var z2: i64=0 123 while z2<CTR_MAGIC_65536 { mem2[z2]=0 as u8; z2=z2+1 } 124 let out2: *i64 = sys_mmap(16) as *i64 125 emu_preempt(code, mem2, THRESH, MAXTICKS, 0, taskA, taskB, out2) 126 let cA2: i64 = pe_ld64(mem2, CTR_A) 127 let cB2: i64 = pe_ld64(mem2, CTR_B) 128 129 pe_puts(" timer ENABLED: task A ran "); pe_num(cA); pe_puts(" times, task B ran "); pe_num(cB); pe_puts(" times ("); pe_num(ticks); pe_puts(" preemptions)\n" as *u8) 130 pe_puts(" timer MASKED: task A ran "); pe_num(cA2); pe_puts(" times, task B ran "); pe_num(cB2); pe_puts(" times (B starved => no preemption)\n" as *u8) 131 132 // fairness: closeness = smaller/larger * 100 (>=50 means within 2x) 133 var lo: i64 = cA; var hi: i64 = cB 134 if cA > cB { lo=cB; hi=cA } 135 var fair: i64 = 0 136 if hi > 0 { fair = (lo*100)/hi } 137 138 var pass: i64=0 139 var ttl: i64=0 140 ttl=ttl+1; pe_puts(" T1 BOTH tasks made real progress (preemptive multitasking, A>5 & B>5): " as *u8); if cA>5 { if cB>5 { pass=pass+1; pe_puts("PASS\n" as *u8) } else { pe_puts("FAIL\n" as *u8) } } else { pe_puts("FAIL\n" as *u8) } 141 ttl=ttl+1; pe_puts(" T2 round-robin fairness (counters within 2x, closeness="); pe_num(fair); pe_puts("%): " as *u8); if fair>=50 { pass=pass+1; pe_puts("PASS\n" as *u8) } else { pe_puts("FAIL\n" as *u8) } 142 ttl=ttl+1; pe_puts(" T3 the timer drove the switching (ticks=="); pe_num(ticks); pe_puts(", want 60): " as *u8); if ticks==60 { pass=pass+1; pe_puts("PASS\n" as *u8) } else { pe_puts("FAIL\n" as *u8) } 143 ttl=ttl+1; pe_puts(" T4 NEG: masked timer -> task B starves (B==0) while A runs (A>0) (liar-kill): " as *u8); if cB2==0 { if cA2>0 { pass=pass+1; pe_puts("PASS\n" as *u8) } else { pe_puts("FAIL\n" as *u8) } } else { pe_puts("FAIL\n" as *u8) } 144 145 pe_puts("X86-USB-PREEMPT-GATE passed "); pe_num(pass); pe_puts("/"); pe_num(ttl) 146 if pass==ttl { pe_puts(" verdict=GREEN (a timer IRQ preempts two non-yielding tasks round-robin -- preemptive multitasking, the kernel runs tasks)\n" as *u8); sys_exit(0); return 0 } 147 pe_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1 148}