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}