aboutsummaryrefslogtreecommitdiffstats
path: root/src/syscall.asm
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 17:12:32 +0200
committeruser <user@clank>2026-09-22 17:12:32 +0200
commitbb55479e693a04df0da8320adfa10a6b8188b8da (patch)
tree9b3736cff55112982d1722bc4c1a9630785aeda9 /src/syscall.asm
parentfs: bounds-check direct blocks - a file over 2 KiB ate the next inode (diff)
downloadgbos-bb55479e693a04df0da8320adfa10a6b8188b8da.tar.gz
gbos-bb55479e693a04df0da8320adfa10a6b8188b8da.tar.xz
gbos-bb55479e693a04df0da8320adfa10a6b8188b8da.zip
kernel: per-process sleep - two sleepers used to deadlock each other
sys_sleep kept its entire state in three globals (wSleepTarget, wSleepAcc, wSleepLastDiv) and yielded inside its own loop. With two processes sleeping at once, each zeroed the other's accumulator on entry, so neither ever reached its target: both slept ~forever. Nothing hit it while only one program at a time paced itself. `httpd &` (polling accept) plus a foreground `ping` is enough: ping wedged with its echo reply already sitting in the socket buffer, unread. Now the deadline lives in registers - i.e. on the caller's own stack across SchedYield, which makes it per-process by construction - and it counts the 64 Hz tick (wTicks, via TimerISR) instead of accumulating DIV deltas. ticks16 re-reads the counter until the high byte is stable, so a carry between the two byte reads can't report a 256-unit jump.
Diffstat (limited to '')
-rw-r--r--src/syscall.asm79
1 files changed, 46 insertions, 33 deletions
diff --git a/src/syscall.asm b/src/syscall.asm
index cab3e79..e0961bc 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -278,45 +278,56 @@ ENDR
ret
; -----------------------------------------------------------------------------
-; sys_sleep(B = 1/64-second units) - cooperative delay driven by the DIV timer.
-; DIV free-runs at 16384 Hz - 32768 Hz in double-speed mode, where 256 ticks
-; = 1/128 s - so the accumulated high byte is halved before comparing against
-; the target. We accumulate DIV deltas across SchedYields (other procs run)
-; until enough 1/64-second units have elapsed.
+; ticks16 -> HL = low 16 bits of the 64 Hz tick counter, read consistently
+; (TimerISR can carry between the two byte reads, which would otherwise read
+; $01FF out of a counter going $00FF -> $0100).
+; -----------------------------------------------------------------------------
+ticks16:
+ ld a, [wTicks+1]
+ ld h, a
+ ld a, [wTicks]
+ ld l, a
+ ld a, [wTicks+1]
+ cp h
+ jr nz, ticks16
+ ret
+
+; -----------------------------------------------------------------------------
+; sys_sleep(B = 1/64-second units) - cooperative delay against the system tick.
+;
+; Every piece of state lives in registers, i.e. on the CALLER'S stack across
+; SchedYield, which makes it per-process. The old version accumulated DIV
+; deltas into three globals (wSleepTarget/wSleepAcc/wSleepLastDiv) and yielded
+; mid-loop: two processes sleeping at the same time each zeroed the other's
+; accumulator on entry, so neither ever reached its target and both slept
+; forever. That needs only a background daemon plus any foreground tool that
+; paces itself - `httpd &` then `ping`.
; -----------------------------------------------------------------------------
sys_sleep:
ld a, b
or a
ret z ; sleep 0 = no-op
- ld [wSleepTarget], a
- xor a
- ld [wSleepAcc], a
- ld [wSleepAcc+1], a
- ld a, [rDIV]
- ld [wSleepLastDiv], a
+ ld c, a ; C = units requested
+ call ticks16
+ ld d, h
+ ld e, l ; DE = tick at entry
.loop
- call SchedYield
- ld a, [rDIV]
- ld c, a ; current DIV
- ld a, [wSleepLastDiv]
- ld b, a ; previous DIV
- ld a, c
- ld [wSleepLastDiv], a
- sub b ; A = DIV delta (mod 256)
- ld e, a
- ld a, [wSleepAcc] ; acc += delta
- add e
- ld [wSleepAcc], a
- ld a, [wSleepAcc+1]
- adc 0
- ld [wSleepAcc+1], a ; acc high byte = elapsed 1/128-second units
- srl a ; /2: double-speed DIV -> 1/64-second units
+ push bc
+ push de
+ call SchedYield ; other processes run here
+ pop de
+ pop bc
+ call ticks16 ; HL = tick now
+ ld a, l
+ sub e
ld b, a
- ld a, [wSleepTarget]
- cp b
- jr z, .done ; elapsed == target
- jr c, .done ; elapsed > target
- jr .loop
+ ld a, h
+ sbc d ; BA... = now - start (mod 65536)
+ or a
+ jr nz, .done ; elapsed >= 256 units: long past any target
+ ld a, b
+ cp c
+ jr c, .loop ; elapsed < requested
.done
ret
@@ -378,8 +389,9 @@ sys_exit::
ld h, a
ld a, [hl]
ld [wExitParentPid], a
- ; hand any children to init
+ ; hand any children to init; free any sockets we still hold
call ReparentToInit
+ call SockReleaseOwner
; wake our parent if it's blocked in wait()
ld a, [wExitParentPid]
call FindPcbByPid ; DE=&parent PCB, CF if gone
@@ -532,6 +544,7 @@ sys_kill::
ld a, b
ld [wExitMyPid], a
call ReparentToInit
+ call SockReleaseOwner ; the victim cannot close() them itself
; wake victim's parent if it is blocked in wait()
ld a, [wKillParent]
call FindPcbByPid