diff options
| author | user <user@clank> | 2026-09-22 17:12:52 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-22 17:12:52 +0200 |
| commit | 30e67fe0f4e66e2161ca61f870f8947d50287995 (patch) | |
| tree | a04189f34687a4d600903100feca18aedc5d1604 /docs/internals.md | |
| parent | net: passive open (listen/accept) + own sockets + close promptly (diff) | |
| download | gbos-30e67fe0f4e66e2161ca61f870f8947d50287995.tar.gz gbos-30e67fe0f4e66e2161ca61f870f8947d50287995.tar.xz gbos-30e67fe0f4e66e2161ca61f870f8947d50287995.zip | |
usr: httpd - the Game Boy serves its filesystem over HTTP
/# httpd &
host$ curl http://10.0.0.2/ -> a listing of the cart RAM
host$ curl http://10.0.0.2/readme -> the file, from battery-backed SRAM
GET only. "/" serves /index.html if present, else a generated listing; any
directory lists with every entry linked, so you can click around a 1997
handheld's disk from a browser. Content type from the extension. No
Content-Length: HTTP/1.0 + Connection: close delimits the body by closing,
which suits a stack with one in-flight segment.
All output goes through one 200-byte buffer, so each net_send is a full
segment instead of one tiny segment per string.
It drains the whole request, not just the request line. That is not
politeness: leaving the rest unread means those segments are never
acknowledged, so the client retransmits headers for a minute, never sends
its FIN, and its dead connection keeps arriving at port 80 long after we
re-listen. One curl with 1.4 KB of headers stalled every following request
for 30-120s.
It deliberately does NOT read the keyboard either. Backgrounded - the useful
way to run it - every key it polled would be a key stolen from the shell's
prompt. It is a daemon: kill(1) stops it, and the kernel reclaims its socket.
Verified with curl: index, byte-exact file bodies, nested paths, directory
listings, 404, 501, a 1.4 KB request spanning segments, browser user-agents,
three parallel clients (0.1/1.1/2.1s), the shell staying usable alongside it,
and six kill/restart cycles - more than there are sockets.
Diffstat (limited to 'docs/internals.md')
| -rw-r--r-- | docs/internals.md | 40 |
1 files changed, 37 insertions, 3 deletions
diff --git a/docs/internals.md b/docs/internals.md index 8ee7816..62167f4 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -149,6 +149,17 @@ Userland networking is plain C on top of that: `dhcp` (DISCOVER→ACK, then pump stdin and print the reply), `irc` (a full client: channels, /join /msg /me, nick colors, flood-safe rendering) and `chat`. +**Listening** (`net_op_listen` / `net_op_accept`): sockets demux on the local +port *alone*, so a listener and its connection are the same socket - the SYN +fills in the peer and the socket becomes the connection. That means one +connection at a time, re-armed by listening again after close; a second client +knocking is dropped in silence and its SYN retransmit is taken when we are +free again (~1s later), which is why `httpd` serves three parallel clients in +0.1/1.1/2.1s. `accept` never blocks: one RX pump per call, like `recv_nb`. +Since SYNs reach the same socket as the live connection, `tcp_peer_is_current` +is what tells a peer's retransmitted SYN apart from a newcomer's - without it +a second client silently *hijacks* the connection mid-request. + **Receive-path invariants** (all three were bugs once, and each one is invisible until a peer sends more than a toy reply): @@ -165,6 +176,22 @@ invisible until a peer sends more than a toy reply): that sits past a gap marks the socket `TCP_DONE`, and `recv` then reports EOF at the hole — truncating the transfer just before the last segments. +`tcp_close` FINs and then pumps for the peer's FIN/ACK, but **stops as soon as +it arrives** instead of burning its whole budget: the socket otherwise sits in +`TCP_FINWAIT` for about a second, and a server that closes and re-listens drops +the next client's SYN in that window, so every request after the first paid a +SYN retransmit (1.1s → 0.1s per request). + +A server must also **drain the request it was sent**. Reading only the request +line leaves the rest unacknowledged: the client retransmits headers for a +minute, never sends its FIN, and its dead connection keeps arriving at the port +long after the server re-listens. One 1.4 KB request stalled every following +connection for 30-120s. + +Sockets are owned by the pid that created them (`SK_OWNER`) and released by +`SockReleaseOwner` on exit *and* on kill — the table has only 4 entries, and a +killed `irc`/`nc`/`httpd` used to hold its slot until reboot. + Bytes that arrive **outside** SLIP frames land in a console ring instead — that's how the host can type into the shell remotely (`tools/gbtype`). `sys_pollcon` pumps the link itself before draining that ring, so a program @@ -185,13 +212,20 @@ A small **block-based, persistent filesystem** on battery-backed cart RAM ``` block 0 superblock (magic + version) block 1 bitmaps (free blocks + free inodes) -blocks 2-3 inode table (32 inodes x 16 B: type, size, 8 direct block ptrs) +blocks 2-3 inode table (32 inodes x 16 B: type, nlink, size, 12 block ptrs) blocks 4.. data blocks ``` - **Inodes + nested directories.** A directory is a file of 16-byte entries - (inode# + name), each carrying `.`/`..`. Files are up to **2 KiB** (8 direct - blocks); **32** inodes, 16 entries per directory. + (inode# + name), each carrying `.`/`..`. Files are up to **3 KiB** + (`NDIRECT` = 12 direct blocks, the whole tail of the inode); **32** inodes, + 16 entries per directory. `sys_getb`/`sys_putb` **bounds-check the block + index** - they did not, so a file that grew past the pointers simply kept + walking into the *next inode*, reading its fields as block numbers. A file + could round-trip fine (reads and writes aliased identically) and only show + the damage once something else touched the neighbouring inode. +- **One-block data cache.** Two files open at once thrash it: `cp` of a 3 KiB + file alternates read/write blocks per byte and takes ~25s. Correct, slow. - **Paths + per-process cwd.** `open`/`mkdir`/`chdir`/`remove`/`ls` take paths (absolute `/a/b` or relative `a/b`); each process has a cwd inode (`PROC_CWD`, inherited on fork). Shell builtins: `cd`, and a cwd-aware prompt. |
