SchOS/.claude/memory/schunix-shm-and-mmap.md
Nexoniarz 3182da3965 Carry project memory in the repo, with tools/memory-sync.sh
Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:

  - D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
    invisible until moved across by hand.
  - Development happens from Windows and from Linux, and each looks in a
    different place for the same checkout.

Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.

The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
2026-08-09 14:27:22 +02:00

2.5 KiB

name description metadata
schunix-shm-and-mmap Shared memory and file-backed mmap work now; the rule is that pages a process does not own must never reach kfree
node_type type originSessionId modified
memory project a665e14b-86bd-4b41-a228-51377f3e9225 2026-08-04T18:45:47.215Z

Shared memory and file-backed mmap were implemented on 2026-08-04, in kernel/sys/sys_mem.c. These were the two syscall gaps blocking a display server: without shm, every frame is a copy through a pipe.

  • shmget/shmat/shmdt — up to 16 segments of 2 MB. Pages are allocated up front and zeroed (a fresh segment handing out the last tenant's bytes would leak them between processes). A segment lives until its last attachment goes. Their table.inc entries were NO_LINUX, so a musl-linked program never reached them at all; they now carry Linux's numbers (29/30/67).
  • mmap of a regular file reads the contents in. It used to silently return zeroed anonymous pages -- success-shaped, and wrong. Writable MAP_SHARED on a file returns -ENODEV rather than faking it: its whole contract is that stores reach the file, and there is no page cache that could make that true.

The rule that matters: pages a process does not own must never reach kfree(). Three places had to learn it, and each was a real hazard:

  • vm_unmap_range() kfree's everything it unmaps. Use vm_unmap_norelease() for device memory and shared segments.
  • freevm() frees any mapped page below PHYSTOP. The framebuffer survived only by accident (0xfd000000 is above PHYSTOP); shm pages are kalloc'd and sit below it. proc_release_shared() runs before freevm in both exit() and exec() -- and in exec it must run before the memset(proc->vma), which erases the record of which mappings were shared.
  • fork() must map the same physical pages and bump the attachment count. copy_vma_range()'s share branch now copies the parent's PTEs rather than special-casing the framebuffer address.

struct vma's is_fb became no_free + shmid for this reason.

Test with shmtest (cmd/shmtest, nine checks) — it forks and has the child write where the parent reads, which is the only convincing proof. Run via pwsh -File tools/bootcheck.ps1 -Tty.

Still missing for a GUI: raw keyboard/mouse events that bypass the tty line discipline, and unix domain sockets (socket() is still a NO_LINUX stub). See schunix-console-tty and schunix-framebuffer-device.