naming: shm → shared_memory — 'shm' is a Unix clipping, not an acronym

Per docs/coding-standards.md (no Unix-abbreviation exception): syscalls
shared_memory_create/map/physical, kernel SharedMemoryObject + handlers,
runtime.shared_memory (library/runtime/shared-memory.zig), the
shared-memory-server/-client test services, the shared-memory QEMU case,
and docs incl. vdso.md's danos_shared_memory_*. 87/87 QEMU tests pass.
This commit is contained in:
Daniel Samson
2026-07-21 17:26:15 +01:00
parent ec6e888076
commit 0045fd87ba
23 changed files with 192 additions and 192 deletions
+2 -2
View File
@@ -134,7 +134,7 @@ Prove the pipeline end-to-end from a separate process.
every IPC message at `MESSAGE_MAXIMUM` = 256 — so `replyWait` rejected the oversized
receive buffer with `-E2BIG` and the serve loop had been *spinning* since D2 (unseen,
as D2/D3 matched init-time heartbeats). Set it to 256; `blit_tile` is now explicitly
a small-tile path (≤ 54 px inline), larger bitmaps being the deferred shm surface.
a small-tile path (≤ 54 px inline), larger bitmaps being the deferred shared-memory surface.
**Gate (met):** `python3 test/qemu_test.py display-demo` spawns the service + `display-demo`;
the demo drives a run of frames of motion through the layer client API and logs
@@ -172,7 +172,7 @@ documented (docs/display.md): no runtime mode-setting (native backend) and no tr
## Deferred (explicitly not in this plan)
- **Shared-memory surfaces** — generalize M13 capability passing to memory objects
(`shm_create`/`shm_map`), so bitmap clients hand the compositor a rendered surface
(`shared_memory_create`/`shared_memory_map`), so bitmap clients hand the compositor a rendered surface
instead of drawing commands. The compositor's layer model already anticipates it.
- **Native backend (Bochs DISPI, then virtio-gpu)** — behind the same internal backend
interface as the dumb framebuffer: EDID mode list + runtime resolution/bpp change +
+16 -16
View File
@@ -10,7 +10,7 @@ lands on its own and ends in a **verifiable gate** — shaped for a `/loop` run,
- **Dynamic hot-attach**: boot on GOP, upgrade to native when the driver **announces**
(push, not polling); re-attach across driver restarts; GOP is the floor for "no driver
ever," not a live fall-back after a reprogram.
- **v2 builds the `shm` capability** (endpoints → memory objects), shared with the future
- **v2 builds the shared-memory capability** (endpoints → memory objects), shared with the future
client-surface path.
- The compositor's layers/back-buffer/damage are **unchanged**; only scanout is pluggable.
@@ -26,7 +26,7 @@ kebab-case file names, no `Co-Authored-By` trailers. New user binaries go throug
**Every gate is serial-checkable — no screenshots** (this plan is built to run unattended).
Where "does it actually display" would otherwise need a human eyeball, the code **reads its
own pixels back**: the scanout resource is CPU-visible RAM (shm-backed) and the back buffer
own pixels back**: the scanout resource is CPU-visible RAM (shared-memory-backed) and the back buffer
is cacheable, so a driver/compositor can write a known value, read it back, and log a
pass/fail — and a virtio `resource_flush` is confirmed by the device **acking it on the
used ring**. Those two together (pixel-readback + flush-ack) are the automated stand-in for
@@ -57,25 +57,25 @@ Extract scanout from the compositor so today's path becomes one backend among fu
**Gate (met):** `display-service` + `display-demo` pass **unchanged** (pure refactor; GOP
is the only backend), and `zig build test` stays green.
## V2 — The `shm` cross-process memory capability (kernel) ✅
## V2 — The shared-memory cross-process capability (kernel) ✅
- [x] [abi.zig](../system/abi.zig): `shm_create` (34) / `shm_map` (35) syscalls + a
`shm_test` service id. Handlers in process.zig: `shm_create(len)` allocates contiguous,
- [x] [abi.zig](../system/abi.zig): `shared_memory_create` (34) / `shared_memory_map` (35) syscalls + a
`shared_memory_test` service id. Handlers in process.zig: `shared_memory_create(len)` allocates contiguous,
zeroed, **cacheable** frames, wraps them in a refcounted object, installs a capability
handle, maps them into the caller's shm arena → returns virtual_address + handle; `shm_map(cap)`
handle, maps them into the caller's shared-memory arena → returns virtual_address + handle; `shared_memory_map(cap)`
maps the same physical pages into the receiver. Reclaimed on death (see below).
- [x] The capability core (ipc-synchronous.zig) is now **kind-tagged**: `scheduler.Task`'s
handle table holds `HandleObject{kind, ptr}`; `closeHandles` and `shareCapability`
dispatch by kind, so an `ShmObject` rides an `ipc_call` `send_cap` exactly like an
dispatch by kind, so a `SharedMemoryObject` rides an `ipc_call` `send_cap` exactly like an
endpoint and frees only when its last capability drops. `mapUserSharedInto` (paging)
maps WB-cacheable + `device_grant`, so a sharer's teardown never frees the shared
frames — the object owns them.
- [x] `library/runtime/shm.zig` (+ barrel export): `create(len) -> Region{ptr, handle, len}`,
- [x] `library/runtime/shared-memory.zig` (+ barrel export): `create(len) -> Region{ptr, handle, len}`,
`map(handle) -> ptr`.
**Gate (met):** `python3 test/qemu_test.py shm` — `shm-client` creates a region, writes a
pattern, and passes its capability to `shm-server` as an `ipc_call` send_cap; the server
`shm_map`s it and reads the **same bytes** back → `shm: shared 4096 bytes ok`. Guardrail:
**Gate (met):** `python3 test/qemu_test.py shared-memory` — `shared-memory-client` creates a region, writes a
pattern, and passes its capability to `shared-memory-server` as an `ipc_call` send_cap; the server
`shared_memory_map`s it and reads the **same bytes** back → `shared-memory: shared 4096 bytes ok`. Guardrail:
`ipc`/`ipc-call`/`ipc-cap`, `supervision`, `dma`, `usermem`, `display-service`, and host
tests all still pass — the handle-table change broke no existing IPC.
@@ -88,7 +88,7 @@ tests all still pass — the handle-table change broke no existing IPC.
VERSION_1, and stand up the control virtqueue in coherent DMA. `virtio-gpu-protocol.zig`
+ `virtio-pci.zig` for the control/transport structs (host-tested sizes).
- [x] Create a 2D scanout resource backed by a coherent DMA region (V4 swaps this for the
shm-shared surface), `attach_backing`, `set_scanout` to scanout 0, `transfer_to_host_2d`
shared-memory surface), `attach_backing`, `set_scanout` to scanout 0, `transfer_to_host_2d`
+ `resource_flush` of a test pattern, and wait on the used ring.
- [x] Register a `scanout` service (`ServiceId.scanout` = 11).
@@ -102,7 +102,7 @@ end without a screenshot (the used-ring ack is the device confirming it consumed
## V4 — The native backend + hot-attach ✅
- [x] `backend.VirtioGpu` in the compositor: `surface()` = the shared `shm` scanout surface
- [x] `backend.VirtioGpu` in the compositor: `surface()` = the shared-memory scanout surface
(the compositor composes straight into the device's resource backing; x86 DMA is
coherent, so the cacheable shared pages need no flush), `present(damage)` = a `present`
request over the driver's `.scanout` endpoint (→ transfer-to-host + resource flush).
@@ -112,7 +112,7 @@ end without a screenshot (the used-ring ack is the device confirming it consumed
driver registered it), switches backend, and re-composites the current frame full-screen.
The present is deferred to a one-shot timer so it runs *after* the reply unblocks the
driver and it serves `.scanout` — presenting inline would deadlock.
- [x] Boot still starts on `backend.Gop`; the upgrade happens on announce. `shm_physical` (a
- [x] Boot still starts on `backend.Gop`; the upgrade happens on announce. `shared_memory_physical` (a
new syscall) gives the driver the guest-physical of the shared surface for `attach_backing`.
**Gate (met):** the `display-native` case (QEMU `-device virtio-gpu-pci`, `mem` bumped since it
@@ -159,14 +159,14 @@ kills the virtio-gpu driver once after it hellos; the restart policy respawns it
re-announces, and the compositor logs `display: scanout re-attached` after the initial
`display: scanout upgraded to virtio-gpu`, with no CPU exception / panic (the compositor
survives) — passing 3/3. All v1 + v2 cases (host tests, `ipc`/`ipc-call`/`ipc-cap`,
`supervision`, `shm`, `display-service`, `display-demo`, `virtio-gpu`, `display-native`,
`supervision`, `shared-memory`, `display-service`, `display-demo`, `virtio-gpu`, `display-native`,
`display-modeset`) pass; default `zig build` is clean.
---
## Deferred (explicitly not in this plan)
- **Client-rendered surfaces** — now unblocked by the `shm` capability (V2): an app renders
- **Client-rendered surfaces** — now unblocked by the shared-memory capability (V2): an app renders
its own bitmap and hands the compositor a reference. A natural follow-on.
- **Bochs DISPI backend** — a simpler second native backend (mode-set only, dumb scanout);
slots behind the same interface if wanted.
+5 -5
View File
@@ -1,7 +1,7 @@
# The display service v2: a pluggable scanout backend
**Status: complete (V1–V6).** The compositor boots on the GOP framebuffer and, when a
virtio-gpu driver announces itself, hot-attaches a native backend over the shared `shm`
virtio-gpu driver announces itself, hot-attaches a native backend over the shared-memory
scanout surface — with runtime mode-setting, EDID, and fenced presents, and it
re-attaches across driver restarts. All serial-gated (see [display-v2-plan.md](display-v2-plan.md)).
@@ -77,9 +77,9 @@ deferred (docs/display.md, "What v1 does not do"). v2 builds it: the natural gen
of M13 capability-passing from *endpoints* to *memory objects* —
```
shm_create(len) -> {handle, virtual_address} // a shareable, page-aligned RAM region
shared_memory_create(len) -> {handle, virtual_address} // a shareable, page-aligned RAM region
… pass `handle` as the send_cap on an ipc_call …
shm_map(cap) -> virtual_address // the receiver maps the same physical pages
shared_memory_map(cap) -> virtual_address // the receiver maps the same physical pages
```
The payoff is leverage: the **same** primitive unlocks **both** native GPU drivers *and*
@@ -92,7 +92,7 @@ A new ring-3 driver process (the topology v1 anticipated — "split the driver f
compositor when a second backend arrives"). It claims the virtio-gpu PCI function, and:
- sets up the **virtqueues** (control + cursor) and the device's config space,
- creates a **2D scanout resource** backed by an `shm` region, `attach_backing`s it,
- creates a **2D scanout resource** backed by a shared-memory region, `attach_backing`s it,
`set_scanout`s it to a CRTC, and `resource_flush`es damaged rectangles,
- reads **EDID** (the `GET_EDID` control command) for the mode list, and `set_scanout`
at a chosen mode for **runtime mode-setting**,
@@ -128,7 +128,7 @@ path in VMs**, where danos development happens. The framebuffer floor never goes
re-attach across driver restarts; GOP is the floor for "no driver ever," not a live
fall-back after a reprogram.
- **Detection = push** (the driver announces to `.display`), not compositor polling.
- **v2 builds the `shm` capability** (endpoints → memory objects), shared with the future
- **v2 builds the shared-memory capability** (endpoints → memory objects), shared with the future
client-surface path.
## See also
+3 -3
View File
@@ -95,7 +95,7 @@ rest of the system hasn't had to face:
┌────────────────────────────────────┬──────────────────────────────────────┐
drawing clients (v1) surface clients (deferred)
runtime.display commands: runtime.display surfaces:
create_layer / configure_layer shm_create → pass as a capability →
create_layer / configure_layer shared_memory_create → pass as a capability →
fill_rect / blit_tile / damage the compositor maps & composites the
present client-rendered bitmap directly
```
@@ -260,8 +260,8 @@ both are clean additions behind the interfaces v1 establishes.
to render into its *own* buffer and hand the compositor a *reference*, not a stream of
commands. That needs the missing cross-process shared-memory primitive — best built as
the natural generalization of the existing M13 [capability passing](driver-model.md)
from *endpoints* to *memory objects* (`shm_create(len) → {cap, virtual_address}`, pass `cap` on
an `ipc_call`, receiver `shm_map(cap) → virtual_address`). v1 avoids it because server-owned
from *endpoints* to *memory objects* (`shared_memory_create(len) → {cap, virtual_address}`, pass `cap` on
an `ipc_call`, receiver `shared_memory_map(cap) → virtual_address`). v1 avoids it because server-owned
surfaces already prove the whole pipeline.
- **Runtime mode-setting (a native backend).** Detecting the EDID mode list and changing
+2 -2
View File
@@ -14,7 +14,7 @@ lands on its own and ends in a **verifiable gate** — shaped for a `/loop` run,
- **Blocking is futex-backed, never spin-backed** — waiters park in the kernel so an
idle core still halts ([halting.md](halting.md)).
- **New syscalls are private**: extend [abi.zig](../system/abi.zig) `SystemCall` after
`shm_physical = 36` (`thread_spawn = 37`, `thread_exit = 38`, `current_core = 39`,
`shared_memory_physical = 36` (`thread_spawn = 37`, `thread_exit = 38`, `current_core = 39`,
`futex_wait = 40`, `futex_wake = 41`) + a `library/runtime` wrapper; user code never names a number.
- **Restart granularity stays the process** — a faulting thread kills its process; the
supervisor restarts the process, which respawns its threads.
@@ -457,7 +457,7 @@ clean.
## Deferred (explicitly not in this plan)
- **Cross-process shared-memory futex** — the `(address_space, virtual_address)` key can become a
physical-address key so two processes share a futex through an [shm](display-v2.md)
physical-address key so two processes share a futex through a [shared-memory](display-v2.md)
region. Not needed for intra-process threads.
- **Per-thread priorities / affinity distinct from the process** — threads inherit the
process priority ([scheduling.md](scheduling.md)); revisit only if it earns its keep.
+2 -2
View File
@@ -133,7 +133,7 @@ Deviations from `std.Thread`, called out honestly:
## Kernel primitives (new private syscalls)
Four new entries extend [abi.zig](../system/abi.zig) `SystemCall` after
`shm_physical = 36`, each with a `library/runtime` wrapper:
`shared_memory_physical = 36`, each with a `library/runtime` wrapper:
| Syscall | Signature | Purpose |
|---|---|---|
@@ -210,7 +210,7 @@ Keying: threads share an address space, so a **virtual address within that addre
identifies a futex uniquely; the kernel keys its wait queue by `(address_space_root, virtual_address)`.
Keying by the **physical** address instead (translate `virtual_address -> physical_address` on entry) is a
deliberate forward door: it lets two *processes* share a futex through an
[shm](display-v2.md) region later, without changing the API. We start with the
[shared-memory](display-v2.md) region later, without changing the API. We start with the
private-per-address-space key and note the physical-key upgrade.
No spinning: a contended lock parks the task in the kernel and the core is free to run
+2 -2
View File
@@ -119,7 +119,7 @@ One table entry per kernel call, C ABI (System V AMD64), names prefixed
returns are `u64`, errors return as negative values exactly as today.
The calls that return two values in `rax:rdx` today — `dma_alloc`
(virtual_address + physical_address), `msi_bind` (address + data), `shm_create` (virtual_address + handle) —
(virtual_address + physical_address), `msi_bind` (address + data), `shared_memory_create` (virtual_address + handle) —
become functions returning a two-`u64` struct. The System V ABI returns a
16-byte struct in `rax:rdx`, so the stub is a plain `syscall; ret` — the
C-ABI spelling of the existing convention, at zero cost.
@@ -129,7 +129,7 @@ Grouped as `abi.zig` groups them:
| Group | Functions |
|-------|-----------|
| process | `danos_exit`, `danos_yield`, `danos_sleep`, `danos_spawn`, `danos_process_enumerate`, `danos_process_kill`, `danos_process_exit_reason`, `danos_process_subscribe`, `danos_process_signal`, `danos_signal_bind` |
| memory | `danos_mmap`, `danos_munmap`, `danos_dma_alloc`, `danos_dma_free`, `danos_shm_create`, `danos_shm_map`, `danos_shm_physical` |
| memory | `danos_mmap`, `danos_munmap`, `danos_dma_alloc`, `danos_dma_free`, `danos_shared_memory_create`, `danos_shared_memory_map`, `danos_shared_memory_physical` |
| ipc | `danos_endpoint_create`, `danos_ipc_register`, `danos_ipc_lookup`, `danos_ipc_call`, `danos_ipc_reply_wait`, `danos_ipc_send` |
| devices | `danos_device_enumerate`, `danos_device_claim`, `danos_device_register`, `danos_mmio_map`, `danos_irq_bind`, `danos_irq_ack`, `danos_msi_bind`, `danos_io_read`, `danos_io_write` |
| time | `danos_clock`, `danos_wall_clock`, `danos_timer_bind` |