code wiki / (root) / nx_kernel_sched_test.nx

nx_kernel_sched_test.nx source

↩ module page · 102 lines · 7117 B

1// nx_kernel_sched_test.nx -- x86 ladder R-kernel rung-1: a SCHEDULER (the heart of an OS). 2// A boot+loader gets code running; what makes it an OPERATING SYSTEM is multitasking: more than one 3// task, with a real CONTEXT SWITCH between them. This builds two tiny x86 tasks and a cooperative 4// round-robin scheduler. The emu is made RESUMABLE: emu_resume runs a task from its saved state until 5// it executes YIELD (0xF1), saving its registers + pc into a per-task control block, so the scheduler 6// can switch to the other task and come back exactly where it left off. 7// The proof of a REAL context switch: each task sets rdx=0x3F8 (the UART port) ONCE before its loop; 8// on every resume it re-enters mid-loop WITHOUT re-setting rdx, so each of its prints only works if the 9// scheduler RESTORED rdx across the switch. Task A prints 'A' then yields; task B prints 'B' then 10// yields. Round-robin for 4 rounds must produce exactly "ABABABAB" -- fair, no starvation, context 11// preserved. 12// KAT: console == "ABABABAB", both tasks alive (not halted), equal progress (4 each). 13// HONEST SCOPE: COOPERATIVE (yield-based) on one core. PREEMPTIVE scheduling (timer IRQ0 via the PIC + 14// IDT) is the next rung; then memory manager, syscalls, userspace. No hw writes (Rule 26). 15// expect_exit: 0 license_tier: ORIGINAL 16import "nx_syscalls.nx" 17 18func sc_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19func sc_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 } 20func sc_beq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 } 21func sc_i32(c: *u8, o: i64) -> i64 { return (c[o] as i64) | ((c[o+1] as i64)<<8) | ((c[o+2] as i64)<<16) | ((c[o+3] as i64)<<24) } 22func sc_b(c: *u8, o: i64, b: i64) -> i64 { c[o]=(b & 0xff) as u8; return o+1 } 23 24// RESUMABLE emu: run a task from st[0]=pc with regs in st[1..8] until YIELD(0xF1) or HLT(0xF4). 25// st[9] = status (0 runnable, 1 halted, 2 fault). returns 1=yielded, 0=halted, -1=fault. 26// This save/restore of (pc, regs) across the boundary IS the context switch. 27func emu_resume(code: *u8, len: i64, st: *i64, console: *u8, clen: *i64) -> i64 { 28 let reg: *i64 = sys_mmap(8*8) as *i64 29 var i: i64=0 30 while i<8 { reg[i]=st[1+i]; i=i+1 } 31 var pc: i64 = st[0] 32 var guard: i64=0 33 while guard<100000 { 34 guard=guard+1 35 let b: i64 = code[pc] as i64 36 if b==0xF4 { var s: i64=0; while s<8 { st[1+s]=reg[s]; s=s+1 } st[0]=pc+1; st[9]=1; return 0 } // hlt -> task done 37 if b==0xF1 { var s2: i64=0; while s2<8 { st[1+s2]=reg[s2]; s2=s2+1 } st[0]=pc+1; return 1 } // yield -> save context, back to scheduler 38 var h: i64=0 39 if h==0 { if b==0x48 { let op: i64=code[pc+1] as i64; if op==0xC7 { reg[(code[pc+2] as i64)&7]=sc_i32(code,pc+3); pc=pc+7; h=1 } if op==0x01 { let m: i64=code[pc+2] as i64; reg[m&7]=reg[m&7]+reg[(m>>3)&7]; pc=pc+3; h=1 } } } // mov r,imm32 ; add r,r 40 if h==0 { if b==0xEE { let port: i64=reg[2]&0xFFFF; if port==0x3F8 { console[clen[0]]=(reg[0]&0xFF) as u8; clen[0]=clen[0]+1 } pc=pc+1; h=1 } } // out dx,al -> UART 41 if h==0 { if b==0xEB { var r: i64=code[pc+1] as i64; if r>127 {r=r-256} pc=pc+2+r; h=1 } } // jmp rel8 42 if h==0 { var s3: i64=0; while s3<8 { st[1+s3]=reg[s3]; s3=s3+1 } st[0]=pc; st[9]=2; return 0-1 } // unknown 43 } 44 return 0-2 45} 46 47// build a task: set rdx=0x3F8 once, then loop { mov rax,ch ; out dx,al ; yield ; jmp loop } 48func build_task(c: *u8, ch: i64) -> i64 { 49 var o: i64 = 0 50 o=sc_b(c,o,0x48); o=sc_b(c,o,0xC7); o=sc_b(c,o,0xC2); o=sc_b(c,o,0xF8); o=sc_b(c,o,0x03); o=sc_b(c,o,0); o=sc_b(c,o,0) // mov rdx,0x3F8 51 let lp: i64 = o 52 o=sc_b(c,o,0x48); o=sc_b(c,o,0xC7); o=sc_b(c,o,0xC0); o=sc_b(c,o,ch); o=sc_b(c,o,0); o=sc_b(c,o,0); o=sc_b(c,o,0) // mov rax,ch 53 o=sc_b(c,o,0xEE) // out dx,al 54 o=sc_b(c,o,0xF1) // yield 55 o=sc_b(c,o,0xEB); o=sc_b(c,o,(lp-(o+1)) & 0xFF) // jmp lp 56 return o 57} 58 59func main() -> i64 { 60 sc_puts("x86 ladder R-kernel rung-1: a SCHEDULER (2 tasks, real context switch, round-robin)\n" as *u8) 61 let codeA: *u8 = sys_mmap(256) 62 let codeB: *u8 = sys_mmap(256) 63 let lenA: i64 = build_task(codeA, 0x41) // 'A' 64 let lenB: i64 = build_task(codeB, 0x42) // 'B' 65 66 // per-task control blocks (the "process table"): [pc, r0..r7, status] 67 let stA: *i64 = sys_mmap(16*8) as *i64 68 let stB: *i64 = sys_mmap(16*8) as *i64 69 var z: i64=0 70 while z<11 { stA[z]=0; stB[z]=0; z=z+1 } 71 72 let console: *u8 = sys_mmap(64) 73 let clen: *i64 = sys_mmap(8) as *i64 74 clen[0]=0 75 76 // the scheduler: round-robin, 4 quanta each 77 var round: i64=0 78 while round<4 { 79 emu_resume(codeA, lenA, stA, console, clen) // run A until it yields (prints one 'A') 80 emu_resume(codeB, lenB, stB, console, clen) // context-switch to B (prints one 'B') 81 round=round+1 82 } 83 84 // count progress per task 85 var na: i64=0 86 var nb: i64=0 87 var ci: i64=0 88 while ci<clen[0] { if console[ci]==(0x41 as u8) { na=na+1 } if console[ci]==(0x42 as u8) { nb=nb+1 } ci=ci+1 } 89 90 sc_puts(" scheduler ran 4 round-robin quanta -> console: " as *u8); sys_write(1, console, clen[0]); sc_puts(" [" as *u8); sc_num(clen[0]); sc_puts(" chars]\n" as *u8) 91 sc_puts(" task A progress=" as *u8); sc_num(na); sc_puts(" task B progress=" as *u8); sc_num(nb); sc_puts(" (rdx survived every switch => context restored)\n" as *u8) 92 93 var pass: i64=0 94 var ttl: i64=0 95 ttl=ttl+1; sc_puts(" T1 fair round-robin interleave (console == 'ABABABAB'): " as *u8); if clen[0]==8 { if sc_beq(console, "ABABABAB\x00" as *u8, 8)==1 { pass=pass+1; sc_puts("PASS\n" as *u8) } else { sc_puts("FAIL\n" as *u8) } } else { sc_puts("FAIL\n" as *u8) } 96 ttl=ttl+1; sc_puts(" T2 equal progress, no starvation (A==4, B==4): " as *u8); if na==4 { if nb==4 { pass=pass+1; sc_puts("PASS\n" as *u8) } else { sc_puts("FAIL\n" as *u8) } } else { sc_puts("FAIL\n" as *u8) } 97 ttl=ttl+1; sc_puts(" T3 both tasks still alive (yielded, not halted): " as *u8); if stA[9]==0 { if stB[9]==0 { pass=pass+1; sc_puts("PASS\n" as *u8) } else { sc_puts("FAIL\n" as *u8) } } else { sc_puts("FAIL\n" as *u8) } 98 99 sc_puts("X86-KERNEL-SCHED-GATE passed " as *u8); sc_num(pass); sc_puts("/" as *u8); sc_num(ttl) 100 if pass==ttl { sc_puts(" verdict=GREEN (a real cooperative scheduler w/ context switch runs on the sovereign machine; preemption[timer IRQ]->MM->syscalls->userspace = the ladder)\n" as *u8); sys_exit(0); return 0 } 101 sc_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1 102}