aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-16 11:15:12 +0200
committeruser <user@clank>2026-07-16 11:15:12 +0200
commitbb4f468b73c8a888bec1e5b2ba7f1575ab7f8456 (patch)
tree24e681e7ca242f3041f6a3effa197445ada451fb
parentkill + ps: implement SYS_KILL (pid 1 immortal) and a ps tool (diff)
downloadgbos-bb4f468b73c8a888bec1e5b2ba7f1575ab7f8456.tar.gz
gbos-bb4f468b73c8a888bec1e5b2ba7f1575ab7f8456.tar.xz
gbos-bb4f468b73c8a888bec1e5b2ba7f1575ab7f8456.zip
jobs: background execution (&), non-blocking reap, spin demo
- sys_reap: nohang reap of a finished zombie child (-> pid or 0). - shell: 'cmd &' launches in the background (prints [pid], no wait); reaps finished jobs at the prompt ([pid done]). Foreground now waits for its own child specifically (loops wait(), reporting bg completions meanwhile) so kill reports the right pid. - spin: a process that yields forever - a target for ps/kill. - verified: spin &; ps shows it; kill <pid> removes just that one; immortal init; self-finishing bg worker reaped with [pid done].
Diffstat (limited to '')
-rw-r--r--Makefile2
-rw-r--r--README.md29
-rw-r--r--c/gbos.h2
-rw-r--r--c/libc.s12
-rw-r--r--c/sh.c25
-rw-r--r--c/spin.c6
-rw-r--r--include/gbos.inc3
-rw-r--r--src/programs.asm7
-rw-r--r--src/syscall.asm58
9 files changed, 135 insertions, 9 deletions
diff --git a/Makefile b/Makefile
index e2a86eb..a3a4b2b 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ $(BUILD)/%.o: src/%.asm | $(BUILD)
# C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm
CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \
c/pid.bin c/cat.bin c/wc.bin c/head.bin c/args.bin \
- c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin
+ c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin
$(BUILD)/programs.o: $(CBLOBS)
# C programs also depend on the shared libc sources
diff --git a/README.md b/README.md
index b0a2edf..fe8a930 100644
--- a/README.md
+++ b/README.md
@@ -263,6 +263,35 @@ so we use SDCC's native asxxxx path and INCBIN the blob instead. String literals
with embedded control chars are also mangled in rgbds mode; C programs pass
explicit lengths and emit newlines via `nl()`.
+## Processes & jobs
+
+Every process has a **pid** (1–255, monotonic) and a parent. `getpid`, `wait`,
+and a full lifecycle (`fork`/`exec`/`exit`/reaping) already existed; on top of
+that:
+
+- **`ps`** lists live processes (`pid state command`); state is `R`eady,
+ `B`locked (in `wait`), or `Z`ombie. The PCB records the running program id
+ (`PROC_PROG`) so `ps` can show names.
+- **`kill <pid>`** terminates a process (`sys_kill`): marks it a zombie,
+ reparents its children to init, wakes a `wait`-blocked parent. **pid 1
+ (init/the shell) is immortal** — `kill 1` is refused.
+- **Background jobs:** `cmd &` runs without waiting; the shell prints `[pid]`
+ and reaps finished jobs (non-blocking `reap`), printing `[pid done]`.
+
+```
+$ spin & # a process that just yields forever
+[2]
+$ ps
+1 B sh
+2 R spin
+3 R ps
+$ kill 2
+[2 done]
+$ ps
+1 B sh
+5 R ps
+```
+
## Filesystem
A tiny **RAM filesystem** lives in **WRAMX (`$D000-$DFFF`, 4 KiB)** — always
diff --git a/c/gbos.h b/c/gbos.h
index 5f22a5a..f76d1e0 100644
--- a/c/gbos.h
+++ b/c/gbos.h
@@ -41,4 +41,6 @@ unsigned char lookup(const char *name); /* command name -> program id */
unsigned char kill(unsigned char pid); /* terminate a pid (1 is immortal)*/
unsigned char psget(unsigned char slot, unsigned char *buf); /* {pid,state,prog}*/
void progname(unsigned char id, char *buf); /* id -> name */
+void yield(void);
+unsigned char reap(void); /* reap a finished bg job, or 0 */
#endif
diff --git a/c/libc.s b/c/libc.s
index bcc8199..2e2516b 100644
--- a/c/libc.s
+++ b/c/libc.s
@@ -187,3 +187,15 @@ _progname:
ld c, #21 ; SYS_PROGNAME
rst #0x30
ret
+
+ .globl _yield, _reap
+ ;; void yield(void)
+_yield:
+ ld c, #11 ; SYS_YIELD
+ rst #0x30
+ ret
+ ;; unsigned char reap(void) -> A (reaped pid, or 0)
+_reap:
+ ld c, #22 ; SYS_REAP
+ rst #0x30
+ ret
diff --git a/c/sh.c b/c/sh.c
index c24ed9f..8aa619e 100644
--- a/c/sh.c
+++ b/c/sh.c
@@ -34,9 +34,10 @@ static void set_cmdline(char **tok, unsigned char start, unsigned char end) {
*p = 0;
}
-/* run tok[start..end): parse its own >/< redirects, honor forced in/out. */
+/* run tok[start..end): parse its own >/< redirects, honor forced in/out.
+ If bg, launch in the background (don't wait; leave fds to the child). */
static void run(char **tok, unsigned char start, unsigned char end,
- unsigned char infd, unsigned char outfd) {
+ unsigned char infd, unsigned char outfd, unsigned char bg) {
unsigned char i, cmdend = end, myin = NOFD, myout = NOFD, id, pid;
for (i = start; i < end; i++) {
if (isop(tok[i], '>')) {
@@ -58,7 +59,13 @@ static void run(char **tok, unsigned char start, unsigned char end,
else if (outfd != NOFD) setout(outfd);
exec(id);
}
- wait();
+ if (bg) { putc('['); putu(pid); puts("]"); nl(); return; } /* background */
+ /* wait for THIS child; report any background jobs that finish meanwhile */
+ for (;;) {
+ unsigned char r = wait();
+ if (r == pid || r == NOFD) break;
+ putc('['); putu(r); puts(" done]"); nl();
+ }
done:
if (myin != NOFD) close(myin);
if (myout != NOFD) close(myout);
@@ -68,7 +75,9 @@ void main(void) {
char line[80];
char *tok[16];
for (;;) {
- unsigned char nt, i, pipepos, c;
+ unsigned char nt, i, pipepos, c, bg;
+ /* reap finished background jobs */
+ { unsigned char p; while ((p = reap())) { putc('['); putu(p); puts(" done]"); nl(); } }
puts("$ ");
/* read a line (echoing), stop at newline; exit on EOF */
i = 0;
@@ -81,19 +90,21 @@ void main(void) {
line[i] = 0;
nt = tokenize(line, tok, 16);
if (nt == 0) continue;
+ bg = 0;
+ if (isop(tok[nt - 1], '&')) { bg = 1; nt--; if (nt == 0) continue; }
pipepos = 0;
for (i = 1; i < nt; i++)
if (isop(tok[i], '|')) { pipepos = i; break; }
if (pipepos) {
unsigned char pw = open("__pipe", O_WRITE);
- run(tok, 0, pipepos, NOFD, pw);
+ run(tok, 0, pipepos, NOFD, pw, 0);
close(pw);
{ unsigned char pr = open("__pipe", O_READ);
- run(tok, pipepos + 1, nt, pr, NOFD);
+ run(tok, pipepos + 1, nt, pr, NOFD, 0);
close(pr); }
fremove("__pipe");
} else {
- run(tok, 0, nt, NOFD, NOFD);
+ run(tok, 0, nt, NOFD, NOFD, bg);
}
}
}
diff --git a/c/spin.c b/c/spin.c
new file mode 100644
index 0000000..2a3d4e8
--- /dev/null
+++ b/c/spin.c
@@ -0,0 +1,6 @@
+#include "gbos.h"
+/* spin: a harmless long-running process that just yields forever.
+ Useful as a target for `ps` and `kill`. */
+void main(void) {
+ for (;;) yield();
+}
diff --git a/include/gbos.inc b/include/gbos.inc
index 30c3a50..329430c 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -101,7 +101,8 @@ DEF SYS_SETOUT EQU 18 ; set current stdout fd (B=fd, $FF=console)
DEF SYS_LOOKUP EQU 19 ; program id for a command name (DE=name -> A=id/$FF)
DEF SYS_PS EQU 20 ; process table entry (B=slot, DE=buf3 -> A=1/0)
DEF SYS_PROGNAME EQU 21 ; program id -> name (B=id, DE=namebuf)
-DEF SYS_MAX EQU 22
+DEF SYS_REAP EQU 22 ; reap a zombie child, nohang (-> A=pid or 0)
+DEF SYS_MAX EQU 23
; SYS_KILL (9) is now implemented (B=pid -> A=0/$FF; pid 1 is immortal)
; (SYS_OPEN=4 / SYS_CLOSE=5 are now implemented by the filesystem)
diff --git a/src/programs.asm b/src/programs.asm
index f21d40b..e83d29e 100644
--- a/src/programs.asm
+++ b/src/programs.asm
@@ -94,6 +94,9 @@ ProgPs:
SECTION "prog_kill", ROMX[$4000], BANK[19]
ProgKill:
INCBIN "c/kill.bin"
+SECTION "prog_spin", ROMX[$4000], BANK[20]
+ProgSpin:
+ INCBIN "c/spin.bin"
; -----------------------------------------------------------------------------
; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and
@@ -172,6 +175,8 @@ ProgramTable::
dw ProgPs
db LOW(BANK(ProgKill)), HIGH(BANK(ProgKill))
dw ProgKill
+ db LOW(BANK(ProgSpin)), HIGH(BANK(ProgSpin))
+ dw ProgSpin
; -----------------------------------------------------------------------------
; NameTable (ROM0) - command name -> program id, searched by sys_lookup.
@@ -214,4 +219,6 @@ NameTable::
db PROG_PS
db "kill", 0
db PROG_KILL
+ db "spin", 0
+ db PROG_SPIN
db 0
diff --git a/src/syscall.asm b/src/syscall.asm
index 0b47c05..2fc66b6 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -56,6 +56,7 @@ SyscallTable:
dw sys_lookup ; 19 LOOKUP
dw sys_ps ; 20 PS
dw sys_progname ; 21 PROGNAME
+ dw sys_reap ; 22 REAP
; -----------------------------------------------------------------------------
sys_nosys:
@@ -554,3 +555,60 @@ sys_progname::
xor a
ld [de], a
ret
+
+; =============================================================================
+; sys_reap() -> A = pid of a reaped zombie child, or 0 (never blocks).
+; Used by the shell to clean up finished background jobs.
+; =============================================================================
+sys_reap::
+ ld a, [wCurProc]
+ add PROC_PID
+ ld l, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld h, a
+ ld a, [hl]
+ ld [wWaitMyPid], a
+ ld c, 0
+.scan
+ ld a, c
+ call PcbPtr ; DE = &PCB[c]
+ ld a, [de]
+ cp PS_ZOMBIE
+ jr nz, .next
+ ld h, d
+ ld l, e
+ ld a, l
+ add PROC_PARENT
+ ld l, a
+ ld a, h
+ adc 0
+ ld h, a
+ ld a, [wWaitMyPid]
+ cp [hl]
+ jr nz, .next
+ ; reap: pid, free bank, free slot
+ ld h, d
+ ld l, e
+ inc hl ; PROC_PID
+ ld a, [hl]
+ ld [wWaitRetPid], a
+ ld a, e
+ add PROC_RAMB
+ ld l, a
+ ld a, d
+ adc 0
+ ld h, a
+ ld a, [hl]
+ call FreeRamBank
+ ld a, PS_FREE
+ ld [de], a
+ ld a, [wWaitRetPid]
+ ret
+.next
+ inc c
+ ld a, c
+ cp MAX_PROCS
+ jr c, .scan
+ xor a
+ ret