summaryrefslogtreecommitdiffstats
path: root/rsnix/src/sched.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-21 21:17:44 +0200
committersalmonik <salmonik@tuta.io>2026-08-21 21:17:44 +0200
commit71b6445b048456bf198d8b622570db95f4665cb9 (patch)
tree808e73fa95709ed9c2d409aecaf275fd70365293 /rsnix/src/sched.rs
parentinput: total keyboard routing + per-terminal discipline (ADR 0006 ph1) (diff)
downloadrs-dos-71b6445b048456bf198d8b622570db95f4665cb9.tar.gz
rs-dos-71b6445b048456bf198d8b622570db95f4665cb9.zip
exec: PE32+ loader, versioned ABI, first external binaries (v0.6.0)
rsapi: the kernel<->binary ABI crate (no_std, no deps). Versioned Exports table (magic + version header), EntryPoint = extern "C" on x86_64-unknown-uefi (Win64: RCX/RDX/R8), Line/Arg/StreamResult types. Binaries link only this crate. rsnix::load: PE32+ parser — DOS/COFF/optional headers, section copy, BSS zero-fill, .reloc base relocations (DIR64) via rva_to_file mapping (the reloc directory is an RVA, not a file offset). Images are allocated at LOAD_BASE=192MiB inside the page allocator's conventional span and identity-mapped user+writable+nx. rsnix::exec: resolution name -> /disk/<UID>/bin/<kind>/<name>.efi per mounted volume; spawn reserves a sched slot, stores a per-task ExecCtx (exports + argv block + image) before launch_at installs the frame — no adopt race. Trampoline calls entry(&Exports, argc, argv), posts TaskMsg::User(exit_code) to the shell. abort_current notifies the shell with u64::MAX when an exec'd binary dies, so a faulting command cannot leave the shell blocked forever. sched: reserve_slot/launch_at split out of spawn; task_exit made pub. shell: exec_external runs before non-frozen built-ins (ADR 0006 resolution rule); frozen core (help/clear/echo/halt/poweroff/exit/ rescan) is exempt. Exports v1 wires put_line to the terminal and get_line to EOF; stream injection lands in Phase 3. ^C while waiting kills the command task. bins/{hello,boom}: gate binaries. hello prints through the exports table; boom faults on 0x400000000000 to prove containment. The image entry point IS the ABI entry (efi_main carries the Exports signature). Version 0.6.0: exec exists end-to-end. Gates verified in QEMU: 'hello' loads from \\bin\\cli\\hello.efi, calls put_line across the ABI, exits 0, shell unblocks; 'boom' faults, task dies gracefully with full report, shell survives, jobs shows the corpse; built-in fallback works when the file is absent; test-disk PASS.
Diffstat (limited to 'rsnix/src/sched.rs')
-rw-r--r--rsnix/src/sched.rs46
1 files changed, 45 insertions, 1 deletions
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs
index 0c4938e..107dba6 100644
--- a/rsnix/src/sched.rs
+++ b/rsnix/src/sched.rs
@@ -396,6 +396,31 @@ pub fn spawn(entry: fn(), name: &str) -> Option<usize> {
396 if ok { Some(id) } else { None } 396 if ok { Some(id) } else { None }
397} 397}
398 398
399/// Reserve an empty task slot without installing a frame. The slot is
400/// marked Ready-with-no-frame is NOT valid to schedule — callers must
401/// follow with `launch_at` before yielding (exec does both back-to-back
402/// under the preemption lock window of the caller).
403pub fn reserve_slot() -> Option<usize> {
404 lock();
405 let id = unsafe {
406 let mut slot = None;
407 for i in 2..MAX_TASKS {
408 if TASKS[i].state == TaskState::Done && TASKS[i].stack != 0 {
409 reap(i);
410 slot = Some(i);
411 break;
412 }
413 if TASKS[i].state == TaskState::Done && TASKS[i].stack == 0 && TASKS[i].name_len == 0 {
414 slot = Some(i);
415 break;
416 }
417 }
418 slot
419 };
420 unlock();
421 id
422}
423
399unsafe fn reap(id: usize) { 424unsafe fn reap(id: usize) {
400 unsafe { 425 unsafe {
401 let t = &mut TASKS[id]; 426 let t = &mut TASKS[id];
@@ -408,6 +433,19 @@ unsafe fn reap(id: usize) {
408 } 433 }
409} 434}
410 435
436/// Install the initial frame for a reserved slot and mark it Ready.
437/// The slot must have come from `reserve_slot` and must not have been
438/// scheduled in between (exec stores its context between the two calls).
439pub fn launch_at(id: usize, entry: fn(), name: &str) -> bool {
440 if id >= MAX_TASKS {
441 return false;
442 }
443 lock();
444 let ok = spawn_at(id, entry, name);
445 unlock();
446 ok
447}
448
411fn spawn_at(id: usize, entry: fn(), name: &str) -> bool { 449fn spawn_at(id: usize, entry: fn(), name: &str) -> bool {
412 let Some(base) = memory::alloc_pages(STACK_PAGES) else { 450 let Some(base) = memory::alloc_pages(STACK_PAGES) else {
413 return false; 451 return false;
@@ -483,7 +521,7 @@ unsafe extern "C" fn trampoline(entry: usize) -> ! {
483 task_exit() 521 task_exit()
484} 522}
485 523
486fn task_exit() -> ! { 524pub fn task_exit() -> ! {
487 lock(); 525 lock();
488 unsafe { 526 unsafe {
489 TASKS[CURRENT].state = TaskState::Done; 527 TASKS[CURRENT].state = TaskState::Done;
@@ -648,6 +686,12 @@ pub fn abort_current(msg: &str) -> ! {
648 TASKS[id].stack, 686 TASKS[id].stack,
649 TASKS[id].guard 687 TASKS[id].guard
650 ); 688 );
689 // An exec'd binary died: unblock the waiting spawner with a
690 // completion message (exit code -1 encoded as u64::MAX). The
691 // shell's queue is the only waiter; best-effort by design.
692 if id >= 2 {
693 let _ = push_msg(SHELL_TID, TaskMsg::User(u64::MAX));
694 }
651 PREEMPT_DEPTH.store(0, Ordering::Relaxed); 695 PREEMPT_DEPTH.store(0, Ordering::Relaxed);
652 } 696 }
653 yield_int(); 697 yield_int();