diff options
| author | user <user@clank> | 2026-07-16 11:15:12 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-16 11:15:12 +0200 |
| commit | bb4f468b73c8a888bec1e5b2ba7f1575ab7f8456 (patch) | |
| tree | 24e681e7ca242f3041f6a3effa197445ada451fb | |
| parent | kill + ps: implement SYS_KILL (pid 1 immortal) and a ps tool (diff) | |
| download | gbos-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].
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README.md | 29 | ||||
| -rw-r--r-- | c/gbos.h | 2 | ||||
| -rw-r--r-- | c/libc.s | 12 | ||||
| -rw-r--r-- | c/sh.c | 25 | ||||
| -rw-r--r-- | c/spin.c | 6 | ||||
| -rw-r--r-- | include/gbos.inc | 3 | ||||
| -rw-r--r-- | src/programs.asm | 7 | ||||
| -rw-r--r-- | src/syscall.asm | 58 |
9 files changed, 135 insertions, 9 deletions
@@ -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 @@ -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 @@ -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 @@ -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 @@ -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 |
