aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-15 23:54:13 +0200
committeruser <user@clank>2026-07-15 23:54:13 +0200
commitab3ebbe227c72b745a46f9233fbfbb27477586b8 (patch)
treed71f87605d17c5653f34ebcdef3941f898cd5b91 /src
parentgbos scaffold: working cooperative microkernel (verified ABAB in emulator) (diff)
downloadgbos-ab3ebbe227c72b745a46f9233fbfbb27477586b8.tar.gz
gbos-ab3ebbe227c72b745a46f9233fbfbb27477586b8.tar.xz
gbos-ab3ebbe227c72b745a46f9233fbfbb27477586b8.zip
kernel: implement fork()
- sys_fork: switch to a kernel stack, bump-allocate a cart-RAM bank, copy the parent's 8 KiB bank via a WRAM bounce buffer, plant a switch-in frame so the child returns 0 to the post-fork PC, fill the child PCB (shared ROM text) - add AllocRamBank (bump allocator) + FindFreeSlot helpers - demo: init forks; parent prints P, child prints C (PCPC...); nested fork gives three distinct pids (123...) - README: document the fork mechanism, mark syscall status
Diffstat (limited to '')
-rw-r--r--src/boot.asm12
-rw-r--r--src/kdata.asm11
-rw-r--r--src/proc.asm211
-rw-r--r--src/syscall.asm2
-rw-r--r--src/tasks.asm40
5 files changed, 249 insertions, 27 deletions
diff --git a/src/boot.asm b/src/boot.asm
index 513c392..73ea860 100644
--- a/src/boot.asm
+++ b/src/boot.asm
@@ -50,18 +50,12 @@ KernelInit:
call CopyHramCode ; move context-switch trampoline into HRAM
call ProcInit ; wipe process table
- ; --- spawn the two demo tasks (text-in-ROM model) ---
- ; task A: entry TaskA, RAM bank 0, u-area WRAM bank 1
- ld hl, TaskA
- ld b, 0 ; RAM bank
+ ; --- spawn the init task (pid 1); it fork()s the rest ---
+ ld hl, TaskInit
+ ld b, 0 ; cart-RAM bank 0
ld c, 1 ; WRAM u-area bank
call ProcCreate
- ld hl, TaskB
- ld b, 1
- ld c, 2
- call ProcCreate
-
call SchedInit ; pick first task, set wCurProc
; interrupts: enable timer for preemption
diff --git a/src/kdata.asm b/src/kdata.asm
index efc780a..ba395f1 100644
--- a/src/kdata.asm
+++ b/src/kdata.asm
@@ -20,3 +20,14 @@ wTmpWramB:: DS 1
wTmpSlot:: DS 1
wTmpPid:: DS 1
wTmpSP:: DS 2
+
+; cart-RAM bank allocator (bump; bank 0 taken by the init task)
+wNextRamBank:: DS 1
+
+; scratch used by sys_fork
+wForkUserSP:: DS 2 ; parent user SP at fork entry (= Uafter)
+wForkChildSlot:: DS 1
+wForkChildPid:: DS 1
+wForkParentBank::DS 1
+wForkChildBank:: DS 1
+wCopyBuf:: DS 256 ; bank-copy bounce buffer (WRAM0, always mapped)
diff --git a/src/proc.asm b/src/proc.asm
index 762b30e..058881f 100644
--- a/src/proc.asm
+++ b/src/proc.asm
@@ -11,6 +11,48 @@ SECTION "proc", ROM0
ProcInit::
ld a, 1
ld [wNextPid], a
+ ld a, 1 ; bank 0 is the init task's; allocate from 1 up
+ ld [wNextRamBank], a
+ ret
+
+; -----------------------------------------------------------------------------
+; AllocRamBank - bump-allocate a cart-RAM bank. out: A=bank, CF set if none.
+; -----------------------------------------------------------------------------
+AllocRamBank::
+ ld a, [wNextRamBank]
+ cp NUM_RAM_BANKS
+ jr nc, .none
+ ld b, a
+ inc a
+ ld [wNextRamBank], a
+ ld a, b
+ and a ; clear carry
+ ret
+.none
+ scf
+ ret
+
+; -----------------------------------------------------------------------------
+; FindFreeSlot - scan the proc table for a PS_FREE slot.
+; out: A=slot, DE=&PCB, CF set if the table is full.
+; -----------------------------------------------------------------------------
+FindFreeSlot::
+ ld c, 0
+.l
+ ld a, c
+ call PcbPtr ; DE = &PCB[c]
+ ld a, [de]
+ cp PS_FREE
+ jr z, .ok
+ inc c
+ ld a, c
+ cp MAX_PROCS
+ jr c, .l
+ scf
+ ret
+.ok
+ ld a, c
+ and a ; clear carry
ret
; -----------------------------------------------------------------------------
@@ -157,3 +199,172 @@ ProcSetupStack::
; exec: point PROC_ROMB at a flashed program image, reset RAM-bank layout
; (copy .data from ROM, zero .bss, fresh heap/stack), longjmp in.
; -----------------------------------------------------------------------------
+
+; =============================================================================
+; sys_fork() - duplicate the current process.
+; out: A = child pid in the parent, 0 in the child, $FF on failure.
+;
+; Runs on the parent's user stack at entry (SP = Uafter, pointing at the rst
+; return address). We switch to a kernel stack in WRAM0 so we're free to swap
+; the $A000 cart-RAM window (the parent's user stack lives there), copy the
+; parent's 8 KiB bank into a freshly allocated child bank via a WRAM bounce
+; buffer, then plant a switch-in frame in the child so it "returns from fork"
+; with A=0 to the same user PC.
+; =============================================================================
+sys_fork::
+ ; capture parent user SP (= Uafter)
+ ld hl, sp+0
+ ld a, l
+ ld [wForkUserSP], a
+ ld a, h
+ ld [wForkUserSP+1], a
+ ld sp, KSTACK_TOP2 ; move off the cart-RAM stack
+
+ call FindFreeSlot ; A=slot, DE=&child PCB
+ jp c, .fail
+ ld [wForkChildSlot], a
+ call AllocRamBank ; A=child cart-RAM bank
+ jp c, .fail
+ ld [wForkChildBank], a
+
+ ; remember the parent's bank so we can restore the window afterwards
+ ld a, [wCurProc]
+ add PROC_RAMB
+ ld l, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld h, a
+ ld a, [hl]
+ ld [wForkParentBank], a
+
+ ; ---- copy parent bank -> child bank, 32 pages of 256 bytes ----
+ ld c, 0 ; page index 0..31
+.page
+ ld a, [wForkParentBank]
+ ld [MBC5_RAMB], a ; map parent bank
+ ld a, $A0
+ add c
+ ld h, a
+ ld l, 0 ; HL = $A000 + c*256
+ ld de, wCopyBuf
+ ld b, 0 ; 256 iterations (b wraps 0->255..->0)
+.rd
+ ld a, [hl+]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .rd
+ ld a, [wForkChildBank]
+ ld [MBC5_RAMB], a ; map child bank
+ ld a, $A0
+ add c
+ ld h, a
+ ld l, 0
+ ld de, wCopyBuf
+ ld b, 0
+.wr
+ ld a, [de]
+ ld [hl+], a
+ inc de
+ dec b
+ jr nz, .wr
+ inc c
+ ld a, c
+ cp 32
+ jr c, .page
+ ; child bank is currently mapped
+
+ ; ---- plant the child's switch-in frame at Uafter-8 (in child bank) ----
+ ; hSwitchTo restore pops hl,de,bc,af then rets; we zero those 8 bytes and
+ ; leave the copied return address (retU) at Uafter untouched, so the child
+ ; resumes at the post-fork PC with A=0.
+ ld a, [wForkUserSP]
+ sub 8
+ ld l, a
+ ld a, [wForkUserSP+1]
+ sbc 0
+ ld h, a ; HL = Uafter-8
+ xor a
+ ld b, 8
+.zframe
+ ld [hl+], a
+ dec b
+ jr nz, .zframe
+
+ ; restore the parent's bank into the window (parent stack valid again)
+ ld a, [wForkParentBank]
+ ld [MBC5_RAMB], a
+
+ ; ---- fill the child PCB ----
+ ld a, [wForkChildSlot]
+ call PcbPtr ; DE = &child PCB
+ ld h, d
+ ld l, e ; HL walks child PCB
+ ld a, PS_READY
+ ld [hl+], a ; STATE
+ ld a, [wNextPid]
+ ld [hl+], a ; PID
+ ld [wForkChildPid], a
+ inc a
+ ld [wNextPid], a
+ ld a, [wForkUserSP] ; PROC_SP = Uafter-8
+ sub 8
+ ld [hl+], a
+ ld a, [wForkUserSP+1]
+ sbc 0
+ ld [hl+], a
+ ; PROC_ROMB = parent ROMB (shared read-only text)
+ ld a, [wCurProc]
+ add PROC_ROMB
+ ld e, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld d, a ; DE = &parent.PROC_ROMB
+ ld a, [de]
+ ld [hl+], a
+ inc de
+ ld a, [de]
+ ld [hl+], a
+ ; PROC_RAMB = child bank
+ ld a, [wForkChildBank]
+ ld [hl+], a
+ ; PROC_WRAMB = parent WRAMB (u-area shared for now)
+ ld a, [wCurProc]
+ add PROC_WRAMB
+ ld e, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld d, a
+ ld a, [de]
+ ld [hl+], a
+ ; PROC_PARENT = parent pid
+ ld a, [wCurProc]
+ add PROC_PID
+ ld e, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld d, a
+ ld a, [de]
+ ld [hl+], a
+ ; PROC_EXIT = 0
+ xor a
+ ld [hl], a
+
+ ; ---- return to the parent with child pid ----
+ ld a, [wForkUserSP]
+ ld l, a
+ ld a, [wForkUserSP+1]
+ ld h, a
+ ld sp, hl ; back on the parent's user stack
+ ld a, [wForkChildPid]
+ ret
+
+.fail
+ ; restore parent user stack and report failure
+ ld a, [wForkUserSP]
+ ld l, a
+ ld a, [wForkUserSP+1]
+ ld h, a
+ ld sp, hl
+ ld a, $FF
+ ret
diff --git a/src/syscall.asm b/src/syscall.asm
index 71e1f2b..ca2857c 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -35,7 +35,7 @@ SyscallTrap::
; -----------------------------------------------------------------------------
SyscallTable:
dw sys_exit ; 0
- dw sys_nosys ; 1 FORK (TODO)
+ dw sys_fork ; 1 FORK
dw sys_nosys ; 2 READ (TODO)
dw sys_write ; 3 WRITE
dw sys_nosys ; 4 OPEN (TODO)
diff --git a/src/tasks.asm b/src/tasks.asm
index 1f84142..459ccb6 100644
--- a/src/tasks.asm
+++ b/src/tasks.asm
@@ -1,33 +1,39 @@
; =============================================================================
-; tasks.asm - two demo userland tasks (text-in-ROM model).
-; Each loops: write its letter to the console, then yield. This exercises the
-; whole pipeline: syscall trap, console, and the HRAM context switch swapping
-; RAM banks / SVBK between tasks.
+; tasks.asm - the init task demonstrates fork().
+; init forks once; the parent loops printing 'P', the child loops printing 'C'.
+; Both run the same ROM text, diverging on fork()'s return value (0 in child).
+; Exercises: syscall trap, fork (8 KiB bank copy + child frame), scheduler,
+; and the HRAM context switch swapping cart-RAM banks per task.
; =============================================================================
INCLUDE "include/gbos.inc"
SECTION "tasks", ROM0
-TaskA::
-.loop
- ld de, .msg
- ld b, 1 ; len
+TaskInit::
+ ld c, SYS_FORK
+ rst $30 ; A = child pid (parent) or 0 (child)
+ or a
+ jr z, .child
+
+.parent
+ ld de, .pmsg
+ ld b, 1
ld c, SYS_WRITE
rst $30
ld c, SYS_YIELD
rst $30
- jr .loop
-.msg
- db "A"
+ jr .parent
-TaskB::
-.loop
- ld de, .msg
+.child
+ ld de, .cmsg
ld b, 1
ld c, SYS_WRITE
rst $30
ld c, SYS_YIELD
rst $30
- jr .loop
-.msg
- db "B"
+ jr .child
+
+.pmsg
+ db "P"
+.cmsg
+ db "C"