C6: update docs for the library/kernel + client split
Rewrote the repository-layout and driver-model docs to describe the new tree — library/kernel (the kernel32-style system library: syscall surface split by concern), library/device (mmio/model/pci/usb/acpi/driver/block), library/client (display, input service clients), library/protocol (wire contracts) — and swept the reference docs off the retired runtime shim: runtime.system.* -> logging.* / time.* / process.* / memory.* runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.* runtime.ipc/process/time/service/Thread/block/display/input -> the module name runtime.device / runtime.device_manager -> driver library/runtime/<file>.zig -> its new home (kernel/ client/ device/) docs/README.md (repository layout + source map), docs/driver-model.md (the module graph + import lists), and the concern/reference docs (ipc, threading, timers, logging, power, process-lifecycle, device-manager, display, vdso, sysv, drivers, input, vfs-protocol, coding-standards, ...) now reflect the split. "runtime" that remains is the userspace-library *concept*, which is still accurate. The historical plan docs (display-plan, display-v2-plan, threading-plan) and the zig-self-hosting design note are left as point-in-time snapshots.
This commit is contained in:
+25
-18
@@ -73,7 +73,7 @@ rather than restate it. Roughly in the order things happen at runtime:
|
|||||||
19. **[process-lifecycle.md](process-lifecycle.md) — the process lifecycle.** Built
|
19. **[process-lifecycle.md](process-lifecycle.md) — the process lifecycle.** Built
|
||||||
(M17): signals over IPC as the one lifecycle vocabulary every process speaks — the
|
(M17): signals over IPC as the one lifecycle vocabulary every process speaks — the
|
||||||
POSIX.1-1990 words with message delivery instead of stack hijack, the stable
|
POSIX.1-1990 words with message delivery instead of stack hijack, the stable
|
||||||
`runtime.process` interface, exit reasons, published exit events any stateful
|
`process` module interface, exit reasons, published exit events any stateful
|
||||||
service can subscribe to (the VFS releasing dead clients' handles), and the two
|
service can subscribe to (the VFS releasing dead clients' handles), and the two
|
||||||
iron rules (cleanup is the kernel's job; kill is not a signal).
|
iron rules (cleanup is the kernel's job; kill is not a signal).
|
||||||
20. **[device-manager.md](device-manager.md) — the device manager.** Built (M18,
|
20. **[device-manager.md](device-manager.md) — the device manager.** Built (M18,
|
||||||
@@ -114,11 +114,11 @@ Start with the north star:
|
|||||||
- **[zig-self-hosting.md](zig-self-hosting.md) — running Zig on danos.** A design note
|
- **[zig-self-hosting.md](zig-self-hosting.md) — running Zig on danos.** A design note
|
||||||
(not built yet) on making danos a real Zig target (`-target x86_64-danos`) and
|
(not built yet) on making danos a real Zig target (`-target x86_64-danos`) and
|
||||||
eventually running the compiler on it. The key realisation: Zig 0.16 reduces an OS
|
eventually running the compiler on it. The key realisation: Zig 0.16 reduces an OS
|
||||||
port to **one seam** (`std.os.danos`), so we build `runtime.os` (→ that seam) plus a
|
port to **one seam** (`std.os.danos`), so we build an `os` seam module (→ that seam) plus
|
||||||
thin `runtime.fs`, retire the `posix` shim, and follow a phased path to
|
the thin `file-system` module, retire the `posix` shim, and follow a phased path to
|
||||||
`zig build-exe hello.zig` running on danos — **not** Linux-ABI emulation.
|
`zig build-exe hello.zig` running on danos — **not** Linux-ABI emulation.
|
||||||
- **[threading.md](threading.md) — threads, the std-shaped way.** **Built** (M1–M6):
|
- **[threading.md](threading.md) — threads, the std-shaped way.** **Built** (M1–M6):
|
||||||
`runtime.Thread` mirrors `std.Thread`'s API (spawn/join/detach, Mutex/Condition/
|
the `thread` module's `Thread` mirrors `std.Thread`'s API (spawn/join/detach, Mutex/Condition/
|
||||||
Semaphore) over a **private** thread ABI — several tasks sharing one address space via
|
Semaphore) over a **private** thread ABI — several tasks sharing one address space via
|
||||||
a `thread_spawn` syscall, futex-backed blocking, address-space refcounting. Why it's the
|
a `thread_spawn` syscall, futex-backed blocking, address-space refcounting. Why it's the
|
||||||
native type and not literal `std.Thread` (the [private ABI](syscall.md)), and why
|
native type and not literal `std.Thread` (the [private ABI](syscall.md)), and why
|
||||||
@@ -220,7 +220,7 @@ addressed as **`system/services/init`** — the repeated leaf resolves away:
|
|||||||
|----------------------------------------|--------------------------------------------|
|
|----------------------------------------|--------------------------------------------|
|
||||||
| `system/services/init/init.zig` | `system/services/init` → `/system/services/init` |
|
| `system/services/init/init.zig` | `system/services/init` → `/system/services/init` |
|
||||||
| `system/drivers/ps2-bus/ps2-bus.zig` | `system/drivers/ps2-bus` → `/system/drivers/ps2-bus` |
|
| `system/drivers/ps2-bus/ps2-bus.zig` | `system/drivers/ps2-bus` → `/system/drivers/ps2-bus` |
|
||||||
| `library/runtime/runtime.zig` | `library/runtime` (the `runtime` module) |
|
| `library/device/pci/pci.zig` | `library/device/pci` (the `pci` module) |
|
||||||
|
|
||||||
In **source**, a sub-project is a directory so it can hold many files — the entry is
|
In **source**, a sub-project is a directory so it can hold many files — the entry is
|
||||||
`fat/fat.zig`, beside it `fat/engine.zig`, `fat/on-disk.zig`, and so on. When
|
`fat/fat.zig`, beside it `fat/engine.zig`, `fat/on-disk.zig`, and so on. When
|
||||||
@@ -234,7 +234,7 @@ A sub-project's extra files are reached through the module, never as separate pa
|
|||||||
```
|
```
|
||||||
system/ → /system danos's own internals (the self-representation)
|
system/ → /system danos's own internals (the self-representation)
|
||||||
boot-handoff.zig the loader↔kernel contract (the `boot-handoff` module)
|
boot-handoff.zig the loader↔kernel contract (the `boot-handoff` module)
|
||||||
abi.zig the private kernel↔runtime syscall ABI (the `abi` module)
|
abi.zig the private kernel↔userspace syscall ABI (the `abi` module)
|
||||||
parameters.zig initial-ramdisk.zig shared contracts
|
parameters.zig initial-ramdisk.zig shared contracts
|
||||||
kernel/ IPC, memory, scheduling, the VFS root, the private syscall dispatch
|
kernel/ IPC, memory, scheduling, the VFS root, the private syscall dispatch
|
||||||
architecture/x86_64/ the `architecture` module (never named by generic code)
|
architecture/x86_64/ the `architecture` module (never named by generic code)
|
||||||
@@ -245,10 +245,16 @@ system/ → /system danos's own internals (the self-representation)
|
|||||||
services/ init/ fat/ device-manager/ system servers → /system/services (fat/ holds
|
services/ init/ fat/ device-manager/ system servers → /system/services (fat/ holds
|
||||||
fat.zig, engine.zig, on-disk.zig)
|
fat.zig, engine.zig, on-disk.zig)
|
||||||
library/ → /lib libraries, one sub-directory each
|
library/ → /lib libraries, one sub-directory each
|
||||||
runtime/ the danos-native runtime + file API (fs) — the stable application ABI
|
kernel/ the danos-native system library (kernel32-style): the syscall
|
||||||
device/ device code by domain — mmio/ model/ pci/ usb/ acpi/ — each a
|
surface split by concern — ipc, memory (heap/dma/shared-memory),
|
||||||
shareable data module (device-abi, pci-class, usb-abi/ids,
|
process, time, logging, file-system, thread, service, plus the
|
||||||
acpi-ids) plus a logic module (mmio, pci, usb, aml)
|
system-call stubs and the start/root entry shim
|
||||||
|
device/ device code by domain — mmio/ model/ pci/ usb/ acpi/ driver/
|
||||||
|
block/ — each a shareable data module (device-abi, pci-class,
|
||||||
|
usb-abi/ids, acpi-ids) plus a logic module (mmio, pci, usb, aml,
|
||||||
|
driver — the device-access + device-manager-hello client)
|
||||||
|
client/ userspace service clients (display, input) — a program's view of
|
||||||
|
a service, layered over that service's protocol
|
||||||
protocol/ driver↔service wire contracts (vfs block display scanout input
|
protocol/ driver↔service wire contracts (vfs block display scanout input
|
||||||
power device-manager usb-transfer), one module per directory
|
power device-manager usb-transfer), one module per directory
|
||||||
boot/ → /boot the loaders
|
boot/ → /boot the loaders
|
||||||
@@ -260,11 +266,11 @@ tools/ test/ host-side build + QEMU test harness
|
|||||||
name. A protocol is the seam between a low-level driver and the higher-level service it
|
name. A protocol is the seam between a low-level driver and the higher-level service it
|
||||||
serves — block ↔ the filesystem, a scanout driver ↔ the compositor — so both sides depend
|
serves — block ↔ the filesystem, a scanout driver ↔ the compositor — so both sides depend
|
||||||
on the contract, not on each other, and the contract belongs to neither sub-project. A
|
on the contract, not on each other, and the contract belongs to neither sub-project. A
|
||||||
`runtime` client may *wrap* one for application convenience (`runtime.fs` over
|
client module may *wrap* one for application convenience (the `file-system` module over
|
||||||
`vfs-protocol`, `runtime.block`, `runtime.display`, `runtime.input`), but the module is the
|
`vfs-protocol`, and the `block`, `display`, `input` clients over theirs), but the protocol
|
||||||
boundary and `runtime` re-exports no protocol. A driver's private wire to its *hardware*
|
module is the boundary — a client re-exports no protocol, it imports it by name. A driver's
|
||||||
(virtio-gpu's command set) is not a service seam and stays a driver-private file, beside
|
private wire to its *hardware* (virtio-gpu's command set) is not a service seam and stays a
|
||||||
the transport that reaches the same device.
|
driver-private file, beside the transport that reaches the same device.
|
||||||
|
|
||||||
**Device code lives in `library/device/<domain>/`**, grouped by what it is about (pci, usb,
|
**Device code lives in `library/device/<domain>/`**, grouped by what it is about (pci, usb,
|
||||||
acpi, and the cross-cutting device model) and split by dependency weight: a data module of
|
acpi, and the cross-cutting device model) and split by dependency weight: a data module of
|
||||||
@@ -275,7 +281,7 @@ marshals across the syscall boundary), and nothing with logic or a taxonomy in i
|
|||||||
lone pure-data import is the only edge from `system/kernel/` into `library/`.
|
lone pure-data import is the only edge from `system/kernel/` into `library/`.
|
||||||
|
|
||||||
There is **no POSIX/C compatibility layer today**: danos programs do file I/O through the
|
There is **no POSIX/C compatibility layer today**: danos programs do file I/O through the
|
||||||
danos-native `runtime.fs` (open/read/write/list over the VFS). A hand-rolled POSIX shim
|
danos-native `file-system` module (open/read/write/list over the VFS). A hand-rolled POSIX shim
|
||||||
(`library/posix/`) was retired as premature — the real POSIX/C surface will come later
|
(`library/posix/`) was retired as premature — the real POSIX/C surface will come later
|
||||||
from the `std.os.danos` seam (and, eventually, musl) when danos becomes a Zig target (see
|
from the `std.os.danos` seam (and, eventually, musl) when danos becomes a Zig target (see
|
||||||
[zig-self-hosting.md](zig-self-hosting.md)). When it does, the foreign-ABI naming
|
[zig-self-hosting.md](zig-self-hosting.md)). When it does, the foreign-ABI naming
|
||||||
@@ -288,7 +294,7 @@ exception in [coding-standards.md](coding-standards.md) applies to that seam.
|
|||||||
| Boot methods (one per way of booting the kernel) | `boot/` — `efi.zig` (UEFI) → `BOOTX64.efi` |
|
| Boot methods (one per way of booting the kernel) | `boot/` — `efi.zig` (UEFI) → `BOOTX64.efi` |
|
||||||
| Kernel entry, panic, bring-up | `system/kernel/kernel.zig` |
|
| Kernel entry, panic, bring-up | `system/kernel/kernel.zig` |
|
||||||
| Loader↔kernel handoff (`BootInformation`, `Framebuffer`, `MemoryMap`, VM layout) | `system/boot-handoff.zig` |
|
| Loader↔kernel handoff (`BootInformation`, `Framebuffer`, `MemoryMap`, VM layout) | `system/boot-handoff.zig` |
|
||||||
| Private kernel↔runtime syscall ABI (`SystemCall`, mmap prot flags, `page_size`) — the runtime speaks it, not apps | `system/abi.zig` |
|
| Private kernel↔userspace syscall ABI (`SystemCall`, mmap prot flags, `page_size`) — the system library speaks it, not apps | `system/abi.zig` |
|
||||||
| Device wire types (`DeviceDescriptor`, `DeviceClass`, …) | `library/device/model/device-abi.zig` |
|
| Device wire types (`DeviceDescriptor`, `DeviceClass`, …) | `library/device/model/device-abi.zig` |
|
||||||
| Physical frame allocator | `system/kernel/pmm.zig` |
|
| Physical frame allocator | `system/kernel/pmm.zig` |
|
||||||
| Kernel heap (`std.mem.Allocator`) | `system/kernel/heap.zig` |
|
| Kernel heap (`std.mem.Allocator`) | `system/kernel/heap.zig` |
|
||||||
@@ -304,7 +310,8 @@ exception in [coding-standards.md](coding-standards.md) applies to that seam.
|
|||||||
| Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` |
|
| Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` |
|
||||||
| In-kernel test cases | `system/kernel/tests.zig` |
|
| In-kernel test cases | `system/kernel/tests.zig` |
|
||||||
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |
|
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |
|
||||||
| danos-native runtime (`runtime`): syscall wrappers, heap, IPC, device access, the file API (`fs`) — the stable application ABI | `library/runtime/` |
|
| danos-native system library (kernel32-style): the syscall surface by concern — `ipc`, `memory`, `process`, `time`, `logging`, `file-system`, `thread`, `service` — the stable application ABI | `library/kernel/` |
|
||||||
|
| Service clients (a program's view of a service) and device clients | `library/client/` (display, input), `library/device/driver` |
|
||||||
| System services (init, the `fat` filesystem, the device-manager) | `system/services/` |
|
| System services (init, the `fat` filesystem, the device-manager) | `system/services/` |
|
||||||
| Device drivers, one sub-project each (`pci-bus`, `ps2-bus`, `usb-xhci-bus` bus drivers) | `system/drivers/` |
|
| Device drivers, one sub-project each (`pci-bus`, `ps2-bus`, `usb-xhci-bus` bus drivers) | `system/drivers/` |
|
||||||
| Build + `run-x86-64` (QEMU/OVMF) + `release-x86-64` (the flashable ISO) | `build.zig` |
|
| Build + `run-x86-64` (QEMU/OVMF) + `release-x86-64` (the flashable ISO) | `build.zig` |
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Three, and only three.
|
|||||||
|
|
||||||
**This exception is scoped to a file that *is* a foreign ABI, and nothing else.**
|
**This exception is scoped to a file that *is* a foreign ABI, and nothing else.**
|
||||||
danos has no such file today: the old `library/posix/` compatibility shim was retired
|
danos has no such file today: the old `library/posix/` compatibility shim was retired
|
||||||
once its callers moved to the danos-native `runtime.fs`, since a hand-rolled POSIX
|
once its callers moved to the danos-native `file_system`, since a hand-rolled POSIX
|
||||||
layer is premature until danos actually needs it (see
|
layer is premature until danos actually needs it (see
|
||||||
[zig-self-hosting.md](zig-self-hosting.md)). The exception will apply again to the
|
[zig-self-hosting.md](zig-self-hosting.md)). The exception will apply again to the
|
||||||
`std.os.danos` seam when danos becomes a real Zig target — that module *is* the C-ABI
|
`std.os.danos` seam when danos becomes a real Zig target — that module *is* the C-ABI
|
||||||
@@ -141,8 +141,8 @@ single word or acronym needs no hyphen: `scheduler.zig`, `paging.zig`, `apic.zig
|
|||||||
conventions above — `snake_case` — because it's an identifier, not a filename.)
|
conventions above — `snake_case` — because it's an identifier, not a filename.)
|
||||||
|
|
||||||
**A sub-project's entry point repeats its directory's name** — `init/init.zig`,
|
**A sub-project's entry point repeats its directory's name** — `init/init.zig`,
|
||||||
`runtime/runtime.zig`, `ps2-bus/ps2-bus.zig` — and the sub-project is addressed by the
|
`pci/pci.zig`, `ps2-bus/ps2-bus.zig` — and the sub-project is addressed by the
|
||||||
*directory* (`system/services/init`, `library/runtime`), with the repeated leaf
|
*directory* (`system/services/init`, `library/device/pci`), with the repeated leaf
|
||||||
resolving away. See the repository-layout section of [README.md](README.md).
|
resolving away. See the repository-layout section of [README.md](README.md).
|
||||||
|
|
||||||
## Named values, not magic numbers
|
## Named values, not magic numbers
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ for drivers.
|
|||||||
How processes stop, reload, and report their deaths is deliberately **not** in this
|
How processes stop, reload, and report their deaths is deliberately **not** in this
|
||||||
document: that is the universal lifecycle every danos process speaks —
|
document: that is the universal lifecycle every danos process speaks —
|
||||||
[process-lifecycle.md](process-lifecycle.md), signals over IPC and the stable
|
[process-lifecycle.md](process-lifecycle.md), signals over IPC and the stable
|
||||||
`runtime.process` interface. The device manager is that design's first serious
|
`process` interface. The device manager is that design's first serious
|
||||||
customer, not its owner. Its own protocol contains nothing lifecycle-shaped; a
|
customer, not its owner. Its own protocol contains nothing lifecycle-shaped; a
|
||||||
driver is stopped, health-checked, and buried exactly like any other process.
|
driver is stopped, health-checked, and buried exactly like any other process.
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ way.
|
|||||||
|
|
||||||
Increments 1–4 are the lifecycle prerequisites and live in
|
Increments 1–4 are the lifecycle prerequisites and live in
|
||||||
[process-lifecycle.md](process-lifecycle.md) (claim cleanup on death, exit reasons,
|
[process-lifecycle.md](process-lifecycle.md) (claim cleanup on death, exit reasons,
|
||||||
published exit events, signals + `runtime.process`). On top of those:
|
published exit events, signals + `process`). On top of those:
|
||||||
|
|
||||||
5. **device-manager-protocol**: `hello`, supervised spawn with restart policy;
|
5. **device-manager-protocol**: `hello`, supervised spawn with restart policy;
|
||||||
usb-xhci-bus becomes the first conforming driver.
|
usb-xhci-bus becomes the first conforming driver.
|
||||||
|
|||||||
+7
-7
@@ -96,7 +96,7 @@ rest of the system hasn't had to face:
|
|||||||
▼ reached by name (ipc_lookup); clients drive it over the display protocol
|
▼ reached by name (ipc_lookup); clients drive it over the display protocol
|
||||||
┌────────────────────────────────────┬──────────────────────────────────────┐
|
┌────────────────────────────────────┬──────────────────────────────────────┐
|
||||||
drawing clients (v1) surface clients (deferred)
|
drawing clients (v1) surface clients (deferred)
|
||||||
runtime.display commands: runtime.display surfaces:
|
display commands: display surfaces:
|
||||||
create_layer / configure_layer shared_memory_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
|
fill_rect / blit_tile / damage the compositor maps & composites the
|
||||||
present client-rendered bitmap directly
|
present client-rendered bitmap directly
|
||||||
@@ -106,7 +106,7 @@ The bring-up sequence mirrors a hardware driver's — it is the
|
|||||||
[`usb-xhci-bus` `initialise`](../system/drivers/usb-xhci-bus/usb-xhci-bus.zig) shape
|
[`usb-xhci-bus` `initialise`](../system/drivers/usb-xhci-bus/usb-xhci-bus.zig) shape
|
||||||
(claim → `mmio_map` → run loop) — and the request/reply service shell is the
|
(claim → `mmio_map` → run loop) — and the request/reply service shell is the
|
||||||
[FAT](../system/services/fat/fat.zig) / [input](../system/services/input/input.zig) shape
|
[FAT](../system/services/fat/fat.zig) / [input](../system/services/input/input.zig) shape
|
||||||
([`runtime.service.run`](../library/runtime/service.zig) with a `protocol.zig` of
|
([`service.run`](../library/kernel/service.zig) with a `protocol.zig` of
|
||||||
`extern struct` messages and an `Operation` tag).
|
`extern struct` messages and an `Operation` tag).
|
||||||
|
|
||||||
**One process, for now.** v1 is a *single* service that both owns the framebuffer and
|
**One process, for now.** v1 is a *single* service that both owns the framebuffer and
|
||||||
@@ -222,13 +222,13 @@ pacing on backends that have none (all of them today; see
|
|||||||
[display-v2.md](display-v2.md), "Fenced is not vsync"). Bring-up paths that must put
|
[display-v2.md](display-v2.md), "Fenced is not vsync"). Bring-up paths that must put
|
||||||
pixels on screen synchronously (initialisation, the self-checks) bypass the clock.
|
pixels on screen synchronously (initialisation, the self-checks) bypass the clock.
|
||||||
|
|
||||||
## `runtime.display`
|
## `display`
|
||||||
|
|
||||||
Clients speak the protocol through a new [`library/runtime/display.zig`](../library/runtime/runtime.zig),
|
Clients speak the protocol through a new [`library/client/display/display.zig`](../library/client/display/display.zig),
|
||||||
the [`runtime.block`](../library/runtime/block.zig) shape (a cached `.display` lookup
|
the [`block`](../library/device/block/block.zig) shape (a cached `.display` lookup
|
||||||
with a boot-race retry): `display.info()`, a `Layer` handle with `fill` / `blitTile` /
|
with a boot-race retry): `display.info()`, a `Layer` handle with `fill` / `blitTile` /
|
||||||
`damage`, and `present()`. Application code never issues the raw syscalls — it calls the
|
`damage`, and `present()`. Application code never issues the raw syscalls — it calls the
|
||||||
runtime, as with every other danos service.
|
client module, as with every other danos service.
|
||||||
|
|
||||||
## The cursor: a mouse-listener thread feeding the compositor
|
## The cursor: a mouse-listener thread feeding the compositor
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ thread** beside the compositor loop.
|
|||||||
the compositor — so no lock guards the framebuffer. A parked `next()` leaves its core
|
the compositor — so no lock guards the framebuffer. A parked `next()` leaves its core
|
||||||
free to halt ([halting.md](halting.md)).
|
free to halt ([halting.md](halting.md)).
|
||||||
- **The channel.** A single-slot *latest-value* cell (`CursorChannel`) guarded by a
|
- **The channel.** A single-slot *latest-value* cell (`CursorChannel`) guarded by a
|
||||||
`runtime.Thread.Mutex`: the renderer wants where the cursor *is now*, not a replay of
|
`Thread.Mutex`: the renderer wants where the cursor *is now*, not a replay of
|
||||||
every delta, so a new position overwrites the old. The listener also **pokes** the
|
every delta, so a new position overwrites the old. The listener also **pokes** the
|
||||||
compositor awake — the main loop is parked in `replyWait`, so the listener posts a
|
compositor awake — the main loop is parked in `replyWait`, so the listener posts a
|
||||||
zero-payload `ipc.send` to the compositor's endpoint, which arrives as a
|
zero-payload `ipc.send` to the compositor's endpoint, which arrives as a
|
||||||
|
|||||||
+16
-9
@@ -105,7 +105,9 @@ module outlived it, which is rather the point.) The pattern generalises directly
|
|||||||
|
|
||||||
```
|
```
|
||||||
library/
|
library/
|
||||||
runtime/ module "runtime" — syscalls, ipc, lifecycle, memory, threads, log, fs
|
kernel/ the system library (kernel32-style): the syscall surface split by concern
|
||||||
|
— ipc, memory (heap/dma/shared-memory), process, time, logging,
|
||||||
|
file-system, thread, service, plus system-call stubs + start/root
|
||||||
device/ device code grouped by domain; each domain splits into a shareable
|
device/ device code grouped by domain; each domain splits into a shareable
|
||||||
data module (enums/wire types, std-only) and a logic module (mmio/IPC)
|
data module (enums/wire types, std-only) and a logic module (mmio/IPC)
|
||||||
mmio/ module "mmio" — typed volatile register access + barriers [M14]
|
mmio/ module "mmio" — typed volatile register access + barriers [M14]
|
||||||
@@ -113,13 +115,16 @@ library/
|
|||||||
pci/ "pci-class" (data) + "pci" — config/BAR/capability walk (Function)
|
pci/ "pci-class" (data) + "pci" — config/BAR/capability walk (Function)
|
||||||
usb/ "usb-abi" + "usb-ids" (data) + "usb" — descriptors, control/interrupt/bulk client
|
usb/ "usb-abi" + "usb-ids" (data) + "usb" — descriptors, control/interrupt/bulk client
|
||||||
acpi/ "acpi-ids" (data) + "aml" — _HID names, the AML interpreter
|
acpi/ "acpi-ids" (data) + "aml" — _HID names, the AML interpreter
|
||||||
|
driver/ module "driver" — device-access syscalls + device-manager hello
|
||||||
|
block/ module "block" — the block-device client (a device type)
|
||||||
|
client/ userspace service clients — display, input (a program's view of a service)
|
||||||
protocol/ driver <-> service wire contracts, one module per directory
|
protocol/ driver <-> service wire contracts, one module per directory
|
||||||
vfs/ block/ display/ scanout/ input/ power/ device-manager/ usb-transfer/
|
vfs/ block/ display/ scanout/ input/ power/ device-manager/ usb-transfer/
|
||||||
|
|
||||||
system/drivers/ one sub-project each → /system/drivers (no `d` suffix)
|
system/drivers/ one sub-project each → /system/drivers (no `d` suffix)
|
||||||
usb-xhci-bus/ HCD + bus driver imports runtime, usb, mmio, usb-transfer-protocol
|
usb-xhci-bus/ HCD + bus driver imports usb, mmio, usb-transfer-protocol (+ kernel modules)
|
||||||
usb-hid/ class driver imports runtime, usb, input-protocol
|
usb-hid/ class driver imports usb, input-protocol (+ kernel modules)
|
||||||
virtio-gpu/ scanout driver imports runtime, pci, mmio, display-/scanout-protocol
|
virtio-gpu/ scanout driver imports pci, mmio, display-/scanout-protocol (+ kernel modules)
|
||||||
```
|
```
|
||||||
|
|
||||||
The split by *dependency weight* is what lets the microkernel stay out of device
|
The split by *dependency weight* is what lets the microkernel stay out of device
|
||||||
@@ -134,7 +139,9 @@ private wire to its *hardware* — virtio-gpu's command set — is not that; it
|
|||||||
driver-private file, like the virtio-pci transport beside it.
|
driver-private file, like the virtio-pci transport beside it.
|
||||||
|
|
||||||
The build side of this has since landed: [`addUserBinary`](build.zig) injects the
|
The build side of this has since landed: [`addUserBinary`](build.zig) injects the
|
||||||
default modules (`runtime`, `mmio`, `xkeyboard-config`, `acpi-ids`) into every user
|
default modules — the library/kernel concern modules (`ipc`, `memory`, `process`, `time`,
|
||||||
|
`logging`, `file-system`, `thread`, `service`), the device/service clients (`driver`,
|
||||||
|
`block`, `display`, `input`), plus `mmio`, `xkeyboard-config`, `acpi-ids` — into every user
|
||||||
binary, and per-binary extras — protocol modules, bus logic — are added with
|
binary, and per-binary extras — protocol modules, bus logic — are added with
|
||||||
`programModule(exe).addImport(...)`. That's the *entire* mechanism — Zig modules
|
`programModule(exe).addImport(...)`. That's the *entire* mechanism — Zig modules
|
||||||
already give you everything else.
|
already give you everything else.
|
||||||
@@ -156,10 +163,10 @@ class driver, the device manager, or the kernel may share them freely.
|
|||||||
and a `received_cap` return (r8): an endpoint travels with a message, installed into
|
and a `received_cap` return (r8): an endpoint travels with a message, installed into
|
||||||
the receiver's handle table (shared, refcount-bumped — a copy, not a move). A full
|
the receiver's handle table (shared, refcount-bumped — a copy, not a move). A full
|
||||||
table fails `-ENOSPC` and does not half-deliver. This is the "open" primitive — a bus
|
table fails `-ENOSPC` and does not half-deliver. This is the "open" primitive — a bus
|
||||||
driver mints a per-device endpoint and hands it to a class driver. The runtime exposes
|
driver mints a per-device endpoint and hands it to a class driver. The `ipc` module
|
||||||
`callCap` and `replyWait(..., send_cap)`, and class drivers consume them now: the
|
exposes `callCap` and `replyWait(..., send_cap)`, and class drivers consume them now: the
|
||||||
PS/2 keyboard and mouse drivers attach to ps2-bus this way, and `runtime.usb` /
|
PS/2 keyboard and mouse drivers attach to ps2-bus this way, and the `usb` / `input`
|
||||||
`runtime.input` open their per-device and subscription channels with `callCap`.
|
client modules open their per-device and subscription channels with `callCap`.
|
||||||
- **M14** — DMA memory + the memory-ordering layer. `/lib/device/mmio` gives drivers typed
|
- **M14** — DMA memory + the memory-ordering layer. `/lib/device/mmio` gives drivers typed
|
||||||
volatile access and `memoryBarrier`/`readMemoryBarrier`/`writeMemoryBarrier` (per-arch); `dma_alloc`/`dma_free` grant
|
volatile access and `memoryBarrier`/`readMemoryBarrier`/`writeMemoryBarrier` (per-arch); `dma_alloc`/`dma_free` grant
|
||||||
physically-contiguous, pinned, uncacheable, reclaim-on-teardown buffers with the
|
physically-contiguous, pinned, uncacheable, reclaim-on-teardown buffers with the
|
||||||
|
|||||||
+1
-1
@@ -391,7 +391,7 @@ the first DMA driver to protect and test against) and these smaller items:
|
|||||||
Claiming and mapping is half of being a danos driver; the other half is the
|
Claiming and mapping is half of being a danos driver; the other half is the
|
||||||
**lifecycle and protocol contract**, and the runtime makes it nearly free:
|
**lifecycle and protocol contract**, and the runtime makes it nearly free:
|
||||||
|
|
||||||
- Build on `runtime.service.run` — one replyWait loop folding protocol
|
- Build on `service.run` — one replyWait loop folding protocol
|
||||||
requests, signals, and notifications into callbacks. The harness answers the
|
requests, signals, and notifications into callbacks. The harness answers the
|
||||||
universal zero-length ping and turns `terminate` into a clean exit for you
|
universal zero-length ping and turns `terminate` into a clean exit for you
|
||||||
([process-lifecycle.md](process-lifecycle.md)).
|
([process-lifecycle.md](process-lifecycle.md)).
|
||||||
|
|||||||
+1
-1
@@ -89,7 +89,7 @@ This is the async counterpart of `ipc_call`, and the input service is its first
|
|||||||
- A **subscriber** calls `input.subscribe(mask)` — or a typed helper: `subscribeKeyboard()`,
|
- A **subscriber** calls `input.subscribe(mask)` — or a typed helper: `subscribeKeyboard()`,
|
||||||
`subscribeMouse()`, `subscribeJoystick()` (one class, `next()` returns the decoded event),
|
`subscribeMouse()`, `subscribeJoystick()` (one class, `next()` returns the decoded event),
|
||||||
or `subscribeAll()` (every class, `next()` returns a tagged `InputEvent`)
|
or `subscribeAll()` (every class, `next()` returns a tagged `InputEvent`)
|
||||||
([library/runtime/input.zig](../library/runtime/input.zig)). It creates its own endpoint
|
([library/client/input/input.zig](../library/client/input/input.zig)). It creates its own endpoint
|
||||||
and hands it to the service as a **capability** (M13 capability passing — the input
|
and hands it to the service as a **capability** (M13 capability passing — the input
|
||||||
service is that feature's first real user), along with its `device_mask`. Then it loops on
|
service is that feature's first real user), along with its `device_mask`. Then it loops on
|
||||||
`next()`, a `replyWait` on that endpoint returning each pushed event.
|
`next()`, a `replyWait` on that endpoint returning each pushed event.
|
||||||
|
|||||||
+4
-4
@@ -118,15 +118,15 @@ Three conventions from [process-lifecycle.md](process-lifecycle.md) ride the
|
|||||||
notification mechanism:
|
notification mechanism:
|
||||||
|
|
||||||
- **Signals** arrive as notifications on the endpoint a process nominated with
|
- **Signals** arrive as notifications on the endpoint a process nominated with
|
||||||
`signal_bind` (`runtime.process.bindSignals`): badge = the signal bit plus the
|
`signal_bind` (`process.bindSignals`): badge = the signal bit plus the
|
||||||
coalesced pending mask (`runtime.process.signalsFrom` decodes). Statements,
|
coalesced pending mask (`process.signalsFrom` decodes). Statements,
|
||||||
never questions; no payload, no reply.
|
never questions; no payload, no reply.
|
||||||
- **One-shot timers** (`timer_bind`, `runtime.system.timerOnce`) land as a
|
- **One-shot timers** (`timer_bind`, `time.timerOnce`) land as a
|
||||||
timer-bit notification — the timed wait: a service arms a deadline and keeps
|
timer-bit notification — the timed wait: a service arms a deadline and keeps
|
||||||
serving, instead of blocking in sleep.
|
serving, instead of blocking in sleep.
|
||||||
- **The universal ping**: a **zero-length request is the liveness probe**,
|
- **The universal ping**: a **zero-length request is the liveness probe**,
|
||||||
answered with a zero-length reply by the service harness itself
|
answered with a zero-length reply by the service harness itself
|
||||||
(`runtime.service.run`). No protocol's requests start at length zero, so the
|
(`service.run`). No protocol's requests start at length zero, so the
|
||||||
encoding cannot collide, and a wedged service simply fails to answer — which
|
encoding cannot collide, and a wedged service simply fails to answer — which
|
||||||
is the diagnosis. Deep health ("can I reach my hardware?") stays a per-service
|
is the diagnosis. Deep health ("can I reach my hardware?") stays a per-service
|
||||||
protocol message.
|
protocol message.
|
||||||
|
|||||||
+2
-2
@@ -21,9 +21,9 @@ kernel log.print ─┘ │
|
|||||||
|
|
||||||
1. **Emit.** A program calls `std.log.info("mounted {s}", .{path})` — the
|
1. **Emit.** A program calls `std.log.info("mounted {s}", .{path})` — the
|
||||||
runtime's `logFn` (installed for every binary by the root shim,
|
runtime's `logFn` (installed for every binary by the root shim,
|
||||||
`library/runtime/log.zig`) formats one line and issues one `debug_write`
|
`library/kernel/logging.zig`) formats one line and issues one `debug_write`
|
||||||
carrying the level. The payload does NOT contain the process's name.
|
carrying the level. The payload does NOT contain the process's name.
|
||||||
`runtime.system.write` remains as the raw/bring-up path (panics, test
|
`logging.write` remains as the raw/bring-up path (panics, test
|
||||||
fixtures); raw bytes ride the same ring, attributed all the same.
|
fixtures); raw bytes ride the same ring, attributed all the same.
|
||||||
|
|
||||||
2. **Stamp.** The kernel wraps every payload LINE in a record stamped with the
|
2. **Stamp.** The kernel wraps every payload LINE in a record stamped with the
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ What subscribers want is not: *the lid closed* means the same thing regardless o
|
|||||||
who noticed. So the surface is **domain-named**. There is a `power-protocol`
|
who noticed. So the surface is **domain-named**. There is a `power-protocol`
|
||||||
module and a well-known `ServiceId.power = 5`; on x86 the **acpi service**
|
module and a well-known `ServiceId.power = 5`; on x86 the **acpi service**
|
||||||
registers it, and on ARM a PSCI/mailbox service will register the *same* id.
|
registers it, and on ARM a PSCI/mailbox service will register the *same* id.
|
||||||
Subscribers call `runtime.ipc.lookup(.power)` and never learn which firmware they
|
Subscribers call `ipc.lookup(.power)` and never learn which firmware they
|
||||||
are on — the neutrality the whole [discovery](discovery.md) migration exists to
|
are on — the neutrality the whole [discovery](discovery.md) migration exists to
|
||||||
preserve, carried one layer up into a running-system surface.
|
preserve, carried one layer up into a running-system surface.
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ notifications, the lifecycle **signals** it can receive (`terminate`), and the
|
|||||||
On a `power_button` event or a `terminate` signal, init:
|
On a `power_button` event or a `terminate` signal, init:
|
||||||
|
|
||||||
1. logs that it is shutting down,
|
1. logs that it is shutting down,
|
||||||
2. runs the standard stop sequence — `runtime.process.stop(child, deadline,
|
2. runs the standard stop sequence — `process.stop(child, deadline,
|
||||||
endpoint)` — over its children **in reverse spawn order**, so the VFS stops
|
endpoint)` — over its children **in reverse spawn order**, so the VFS stops
|
||||||
last (other services may flush through it), each child getting the
|
last (other services may flush through it), each child getting the
|
||||||
*terminate → deadline → kill* escalation from
|
*terminate → deadline → kill* escalation from
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ harness are all in — the interface below is as-built. The primitives underneat
|
|||||||
predate this design ([process-management.md](process-management.md):
|
predate this design ([process-management.md](process-management.md):
|
||||||
spawn, the supervision link, kill, child-exit notifications); this document designs
|
spawn, the supervision link, kill, child-exit notifications); this document designs
|
||||||
the layer above them — the standard vocabulary a danos process speaks about its own
|
the layer above them — the standard vocabulary a danos process speaks about its own
|
||||||
life, and the stable `runtime.process` interface that carries it. Nothing here is
|
life, and the stable `process` interface that carries it. Nothing here is
|
||||||
device- or driver-specific: a driver, the VFS, and a user application all stop,
|
device- or driver-specific: a driver, the VFS, and a user application all stop,
|
||||||
reload, and die the same way. The device manager is simply this design's first
|
reload, and die the same way. The device manager is simply this design's first
|
||||||
serious customer ([device-manager.md](device-manager.md)).
|
serious customer ([device-manager.md](device-manager.md)).
|
||||||
@@ -184,7 +184,7 @@ zombie state or privileged snooping:
|
|||||||
same encoding supervisors already decode, the IRQ-as-IPC pattern once more). The
|
same encoding supervisors already decode, the IRQ-as-IPC pattern once more). The
|
||||||
subscriber filters for ids it holds state for and releases what the dead client
|
subscriber filters for ids it holds state for and releases what the dead client
|
||||||
held. Correlating is free of bookkeeping: an IPC sender's badge already *is* its
|
held. Correlating is free of bookkeeping: an IPC sender's badge already *is* its
|
||||||
task id (`runtime.ipc.Received`), so the id a service has been keying client
|
task id (`ipc.Received`), so the id a service has been keying client
|
||||||
state by all along is the id the exit event carries.
|
state by all along is the id the exit event carries.
|
||||||
|
|
||||||
Subscription, not broadcast-to-everyone: only processes that asked receive
|
Subscription, not broadcast-to-everyone: only processes that asked receive
|
||||||
@@ -200,9 +200,9 @@ its clients cleaning up after themselves.** Handle release on client death is th
|
|||||||
service's job, triggered by the published exit event — never by a courtesy
|
service's job, triggered by the published exit event — never by a courtesy
|
||||||
"closing now" message that a crashed client will never send.
|
"closing now" message that a crashed client will never send.
|
||||||
|
|
||||||
## The stable interface: `runtime.process`
|
## The stable interface: `process`
|
||||||
|
|
||||||
`runtime.process` already owns what a process receives at birth (`Init`, the
|
`process` already owns what a process receives at birth (`Init`, the
|
||||||
argv contract). It grows to own the other end of life.
|
argv contract). It grows to own the other end of life.
|
||||||
|
|
||||||
**The runtime is the stable interface; the numbers are not.** danos applications do
|
**The runtime is the stable interface; the numbers are not.** danos applications do
|
||||||
@@ -283,7 +283,7 @@ callbacks (`on_terminate`, `on_reload`) for programs that want defaults.
|
|||||||
|
|
||||||
### The service harness
|
### The service harness
|
||||||
|
|
||||||
`runtime.service` owns the `replyWait` loop and folds every event source — signals,
|
`service` owns the `replyWait` loop and folds every event source — signals,
|
||||||
child exits, protocol messages — into callbacks, with the vocabulary's defaults:
|
child exits, protocol messages — into callbacks, with the vocabulary's defaults:
|
||||||
`terminate` returns from the loop (clean exit), the common `ping` is answered automatically,
|
`terminate` returns from the loop (clean exit), the common `ping` is answered automatically,
|
||||||
`reload` is ignored unless overridden. One loop, no locking, nothing reentrant. A
|
`reload` is ignored unless overridden. One loop, no locking, nothing reentrant. A
|
||||||
@@ -312,12 +312,12 @@ get POSIX; danos-native programs never pay for it.
|
|||||||
kill a claiming driver, spawn it again, the claim succeeds.
|
kill a claiming driver, spawn it again, the claim succeeds.
|
||||||
2. **Exit reason in the death notification** (`ExitReason` above).
|
2. **Exit reason in the death notification** (`ExitReason` above).
|
||||||
3. **Exit events**: `process_subscribe` in the kernel (bounded subscriber table,
|
3. **Exit events**: `process_subscribe` in the kernel (bounded subscriber table,
|
||||||
publishes on every death), `runtime.process.subscribeExits`; the userspace VFS
|
publishes on every death), `process.subscribeExits`; the userspace VFS
|
||||||
router was the first subscriber — releasing a dead client's handles was its
|
router was the first subscriber — releasing a dead client's handles was its
|
||||||
proof test — and the FAT server inherited the role when the router moved into
|
proof test — and the FAT server inherited the role when the router moved into
|
||||||
the kernel (clients now hold the filesystem server's node ids directly).
|
the kernel (clients now hold the filesystem server's node ids directly).
|
||||||
4. **Signals**: `signal_bind` + `process_signal` + the pending mask in the kernel;
|
4. **Signals**: `signal_bind` + `process_signal` + the pending mask in the kernel;
|
||||||
`runtime.process` grows the interface above; the service harness handles
|
`process` grows the interface above; the service harness handles
|
||||||
`terminate` and answers the common `ping`; `stop()` for supervisors.
|
`terminate` and answers the common `ping`; `stop()` for supervisors.
|
||||||
|
|
||||||
[device-manager.md](device-manager.md) builds directly on all four.
|
[device-manager.md](device-manager.md) builds directly on all four.
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ the architecture layer calls up into `tick`.
|
|||||||
- ~~There is no exit status in the notification~~ Closed (M17.2): the kernel
|
- ~~There is no exit status in the notification~~ Closed (M17.2): the kernel
|
||||||
records how every process ends — exited, a fault class, or killed — before it
|
records how every process ends — exited, a fault class, or killed — before it
|
||||||
posts the exit notification, and the supervisor reads it with
|
posts the exit notification, and the supervisor reads it with
|
||||||
`process_exit_reason` (`runtime.process.exitReason`). This is the input to
|
`process_exit_reason` (`process.exitReason`). This is the input to
|
||||||
restart policy ([process-lifecycle.md](process-lifecycle.md)); an exit *code*
|
restart policy ([process-lifecycle.md](process-lifecycle.md)); an exit *code*
|
||||||
for the clean case can still ride alongside later.
|
for the clean case can still ride alongside later.
|
||||||
- Enumerate writes through the caller's raw pointer under the bring-up trust
|
- Enumerate writes through the caller's raw pointer under the bring-up trust
|
||||||
|
|||||||
+3
-3
@@ -90,9 +90,9 @@ process) instead of silently corrupting the image
|
|||||||
(`buildEntryStack` in `system/kernel/process.zig`); `argv[0]` is always the path
|
(`buildEntryStack` in `system/kernel/process.zig`); `argv[0]` is always the path
|
||||||
or initial-ramdisk name the process was spawned as, and `system_spawn`'s optional
|
or initial-ramdisk name the process was spawned as, and `system_spawn`'s optional
|
||||||
argument blob becomes `argv[1..]`. The runtime's `_start`
|
argument blob becomes `argv[1..]`. The runtime's `_start`
|
||||||
(`library/runtime/start.zig`) hands the block to `rt_start`, which builds a
|
(`library/kernel/start.zig`) hands the block to `rt_start`, which builds a
|
||||||
`runtime.process.Init` from it and passes that to the program's `main`
|
`process.Init` from it and passes that to the program's `main`
|
||||||
(`pub fn main(init: runtime.process.Init)`; a parameterless `main()` is also
|
(`pub fn main(init: process.Init)`; a parameterless `main()` is also
|
||||||
accepted). A C runtime's `crt0` would walk
|
accepted). A C runtime's `crt0` would walk
|
||||||
the identical layout unmodified — that's the compatibility being bought. The
|
the identical layout unmodified — that's the compatibility being bought. The
|
||||||
`args` test proves the round trip.
|
`args` test proves the round trip.
|
||||||
|
|||||||
+17
-17
@@ -1,8 +1,8 @@
|
|||||||
# Threading: `runtime.Thread`, a std-shaped API over a private thread ABI
|
# Threading: `Thread`, a std-shaped API over a private thread ABI
|
||||||
|
|
||||||
A note on danos **threads** — several tasks sharing one address space — provided by a
|
A note on danos **threads** — several tasks sharing one address space — provided by a
|
||||||
`runtime.Thread` type that mirrors the shape of Zig's `std.Thread` while keeping every
|
`Thread` type that mirrors the shape of Zig's `std.Thread` while keeping every
|
||||||
kernel entry behind the [runtime](../library/runtime). **Built** (M1–M11, see
|
kernel entry behind the [runtime](../library/kernel). **Built** (M1–M11, see
|
||||||
[threading-plan.md](threading-plan.md)): `spawn`/`join`/`detach`, cross-core parallelism,
|
[threading-plan.md](threading-plan.md)): `spawn`/`join`/`detach`, cross-core parallelism,
|
||||||
a futex, `Mutex`/`Condition`/`Semaphore`/`RwLock`/`WaitGroup`, `getCurrentId`/`currentCore`,
|
a futex, `Mutex`/`Condition`/`Semaphore`/`RwLock`/`WaitGroup`, `getCurrentId`/`currentCore`,
|
||||||
per-thread thread-pointer TLS, thread-safe allocation, and a task reaper that reclaims dead
|
per-thread thread-pointer TLS, thread-safe allocation, and a task reaper that reclaims dead
|
||||||
@@ -17,13 +17,13 @@ treat upstream shapes as "0.16.x."
|
|||||||
A danos service can write
|
A danos service can write
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const t = try runtime.Thread.spawn(.{}, worker, .{ctx});
|
const t = try Thread.spawn(.{}, worker, .{ctx});
|
||||||
// ... do other work concurrently ...
|
// ... do other work concurrently ...
|
||||||
t.join();
|
t.join();
|
||||||
```
|
```
|
||||||
|
|
||||||
and get real parallelism across cores — with `runtime.Thread.Mutex`,
|
and get real parallelism across cores — with `Thread.Mutex`,
|
||||||
`runtime.Thread.Condition`, and `runtime.Thread.Semaphore` available for
|
`Thread.Condition`, and `Thread.Semaphore` available for
|
||||||
coordination — **without any code path reaching the kernel except through the
|
coordination — **without any code path reaching the kernel except through the
|
||||||
runtime**. The call sites read exactly like `std.Thread`, so the day danos becomes a
|
runtime**. The call sites read exactly like `std.Thread`, so the day danos becomes a
|
||||||
real Zig target (see [self-hosting](#the-self-hosting-endgame)) we swap the
|
real Zig target (see [self-hosting](#the-self-hosting-endgame)) we swap the
|
||||||
@@ -31,7 +31,7 @@ implementation underneath, not the API above.
|
|||||||
|
|
||||||
## Locked decisions (do not relitigate)
|
## Locked decisions (do not relitigate)
|
||||||
|
|
||||||
- **We build `runtime.Thread`, not literal `std.Thread`.** It mirrors std's *API and
|
- **We build `Thread`, not literal `std.Thread`.** It mirrors std's *API and
|
||||||
features*; the implementation underneath is danos-native. See
|
features*; the implementation underneath is danos-native. See
|
||||||
[Why not literal std.Thread](#why-not-literal-stdthread).
|
[Why not literal std.Thread](#why-not-literal-stdthread).
|
||||||
- **Threads are a narrow, opt-in capability — not the default concurrency tool.** The
|
- **Threads are a narrow, opt-in capability — not the default concurrency tool.** The
|
||||||
@@ -42,7 +42,7 @@ implementation underneath, not the API above.
|
|||||||
- **Per-binary opt-in to multi-threaded codegen.** Only a service that asks for
|
- **Per-binary opt-in to multi-threaded codegen.** Only a service that asks for
|
||||||
threads is built `single_threaded = false`; the rest stay lean and single-threaded.
|
threads is built `single_threaded = false`; the rest stay lean and single-threaded.
|
||||||
- **The thread ABI is private.** New syscalls extend [abi.zig](../system/abi.zig)
|
- **The thread ABI is private.** New syscalls extend [abi.zig](../system/abi.zig)
|
||||||
`SystemCall` and are reached only through `library/runtime` wrappers, exactly like
|
`SystemCall` and are reached only through `library/kernel` wrappers, exactly like
|
||||||
every other danos syscall ([syscall.md](syscall.md)) — numbers stay renumberable.
|
every other danos syscall ([syscall.md](syscall.md)) — numbers stay renumberable.
|
||||||
|
|
||||||
## Why not literal `std.Thread`
|
## Why not literal `std.Thread`
|
||||||
@@ -99,7 +99,7 @@ processes. The isolation boundary stays at process granularity.
|
|||||||
|
|
||||||
## The API surface (mirrors `std.Thread`)
|
## The API surface (mirrors `std.Thread`)
|
||||||
|
|
||||||
Lives in `library/runtime/thread.zig`, re-exported as `runtime.Thread`.
|
Lives in `library/kernel/thread.zig`, re-exported as `Thread`.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
pub const Thread = struct {
|
pub const Thread = struct {
|
||||||
@@ -129,14 +129,14 @@ Deviations from `std.Thread`, called out honestly:
|
|||||||
`void`). Return data through shared state or a `Semaphore`/`Condition`, not the
|
`void`). Return data through shared state or a `Semaphore`/`Condition`, not the
|
||||||
return.
|
return.
|
||||||
- No `getCpuCount()` (a service rarely needs it) and no `Thread.yield()` — `yield`
|
- No `getCpuCount()` (a service rarely needs it) and no `Thread.yield()` — `yield`
|
||||||
lives in `runtime.system`. Instead `currentCore()` exposes the calling core's dense
|
lives in the `process` module. Instead `currentCore()` exposes the calling core's dense
|
||||||
index ([smp.md](smp.md)), used to observe genuine cross-core parallelism.
|
index ([smp.md](smp.md)), used to observe genuine cross-core parallelism.
|
||||||
|
|
||||||
## Kernel primitives (new private syscalls)
|
## Kernel primitives (new private syscalls)
|
||||||
|
|
||||||
Five core entries extend [abi.zig](../system/abi.zig) `SystemCall` after
|
Five core entries extend [abi.zig](../system/abi.zig) `SystemCall` after
|
||||||
`shared_memory_physical = 36` (plus small helpers `current_core`, `thread_self`, and
|
`shared_memory_physical = 36` (plus small helpers `current_core`, `thread_self`, and
|
||||||
`set_thread_pointer`), each with a `library/runtime` wrapper:
|
`set_thread_pointer`), each with a `library/kernel` wrapper:
|
||||||
|
|
||||||
| Syscall | Signature | Purpose |
|
| Syscall | Signature | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -239,7 +239,7 @@ see the intro). Two scoped pieces, as built:
|
|||||||
A binary opts in by being added with `addThreadedUserBinary` — as `addUserBinary`,
|
A binary opts in by being added with `addThreadedUserBinary` — as `addUserBinary`,
|
||||||
but the shared implementation builds it `single_threaded = false` — so atomics and
|
but the shared implementation builds it `single_threaded = false` — so atomics and
|
||||||
(later) TLS are real. Threads and atomics are unsound in a `single_threaded` image,
|
(later) TLS are real. Threads and atomics are unsound in a `single_threaded` image,
|
||||||
so a binary must opt in **before** it may call `runtime.Thread.spawn`. Everyone else
|
so a binary must opt in **before** it may call `Thread.spawn`. Everyone else
|
||||||
stays single-threaded and lean.
|
stays single-threaded and lean.
|
||||||
|
|
||||||
## Interaction with the rest of the kernel
|
## Interaction with the rest of the kernel
|
||||||
@@ -313,9 +313,9 @@ a verifiable gate (`python3 test/qemu_test.py <case>`, asserting serial markers;
|
|||||||
|
|
||||||
Follow [coding-standards.md](coding-standards.md): spell out non-acronym
|
Follow [coding-standards.md](coding-standards.md): spell out non-acronym
|
||||||
abbreviations, kebab-case file names, no `Co-Authored-By` trailers. New syscalls
|
abbreviations, kebab-case file names, no `Co-Authored-By` trailers. New syscalls
|
||||||
extend [abi.zig](../system/abi.zig) `SystemCall` + a `library/runtime` wrapper
|
extend [abi.zig](../system/abi.zig) `SystemCall` + a `library/kernel` wrapper
|
||||||
([syscall.md](syscall.md)). `runtime.Thread` is a first-class runtime module, the same
|
([syscall.md](syscall.md)). `Thread` is a first-class runtime module, the same
|
||||||
way `runtime.process` ([process-lifecycle.md](process-lifecycle.md)) and `runtime.ipc`
|
way `process` ([process-lifecycle.md](process-lifecycle.md)) and `ipc`
|
||||||
are — user code never names a syscall.
|
are — user code never names a syscall.
|
||||||
|
|
||||||
## Non-goals
|
## Non-goals
|
||||||
@@ -333,8 +333,8 @@ are — user code never names a syscall.
|
|||||||
When danos becomes a real Zig target and we (eventually) add a danos backend to std
|
When danos becomes a real Zig target and we (eventually) add a danos backend to std
|
||||||
([zig-self-hosting.md](zig-self-hosting.md)), `std.Thread` can sit *on top of* these
|
([zig-self-hosting.md](zig-self-hosting.md)), `std.Thread` can sit *on top of* these
|
||||||
same kernel primitives — the danos `std.Thread.Impl` would call the very
|
same kernel primitives — the danos `std.Thread.Impl` would call the very
|
||||||
`thread_spawn`/`futex_*` wrappers `runtime.Thread` already uses. Because
|
`thread_spawn`/`futex_*` wrappers `Thread` already uses. Because
|
||||||
`runtime.Thread` was built API-compatible from day one, that transition swaps the
|
`Thread` was built API-compatible from day one, that transition swaps the
|
||||||
implementation, not a single call site. Designing to the std shape now is what makes
|
implementation, not a single call site. Designing to the std shape now is what makes
|
||||||
the later self-hosting lift cheap.
|
the later self-hosting lift cheap.
|
||||||
|
|
||||||
|
|||||||
+8
-7
@@ -61,10 +61,10 @@ The kernel's own scheduling timer (the LAPIC, vector 32) is never exposed to use
|
|||||||
programs read the TSC through `clock` and get timed wakeups through `sleep`/`timer_bind`,
|
programs read the TSC through `clock` and get timed wakeups through `sleep`/`timer_bind`,
|
||||||
both riding the scheduler tick.
|
both riding the scheduler tick.
|
||||||
|
|
||||||
## `runtime.time` — the generic interface
|
## `time` — the generic interface
|
||||||
|
|
||||||
Applications don't call the syscalls directly; they use `runtime.time`
|
Applications don't call the syscalls directly; they use `time`
|
||||||
(`library/runtime/time.zig`), a thin `Instant`/`Duration` layer over them — an ergonomic
|
(`library/kernel/time.zig`), a thin `Instant`/`Duration` layer over them — an ergonomic
|
||||||
front door, not new mechanism.
|
front door, not new mechanism.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
@@ -91,8 +91,9 @@ _ = time.after(endpoint, time.Duration.fromMillis(200));
|
|||||||
- `sleep(d)` wraps `sleep`; `spin(d)` busy-polls `now()` for the sub-millisecond delays
|
- `sleep(d)` wraps `sleep`; `spin(d)` busy-polls `now()` for the sub-millisecond delays
|
||||||
the millisecond tick can't express; `after(endpoint, d)` wraps `timer_bind`.
|
the millisecond tick can't express; `after(endpoint, d)` wraps `timer_bind`.
|
||||||
|
|
||||||
The raw wrappers (`system.clock`, `system.sleep`, `system.timerOnce`) stay in
|
The raw wrappers (`clock`, `sleepMillis`, `timerOnce`) and the ergonomic
|
||||||
`library/runtime/system.zig`; `runtime.time` is the layer meant for everyday use.
|
`Instant`/`Duration` layer both live in the `time` module
|
||||||
|
(`library/kernel/time.zig`); the latter is what everyday code uses.
|
||||||
|
|
||||||
## Wall-clock time (not built)
|
## Wall-clock time (not built)
|
||||||
|
|
||||||
@@ -106,10 +107,10 @@ owns covers every current use.
|
|||||||
|
|
||||||
## Verifying it
|
## Verifying it
|
||||||
|
|
||||||
`runtime.time`'s `Instant`/`Duration` arithmetic has unit tests that run on the host:
|
`time`'s `Instant`/`Duration` arithmetic has unit tests that run on the host:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ zig build test # includes library/runtime/time.zig
|
$ zig build test # includes library/kernel/time.zig
|
||||||
```
|
```
|
||||||
|
|
||||||
End to end, the proof the clock is real is that it *advances*: read `now()`, `sleep` a
|
End to end, the proof the clock is real is that it *advances*: read `now()`, `sleep` a
|
||||||
|
|||||||
+9
-9
@@ -1,7 +1,7 @@
|
|||||||
# The vDSO — the public system-call boundary
|
# The vDSO — the public system-call boundary
|
||||||
|
|
||||||
> **Status:** design note, not built. The runtime today issues raw `syscall`
|
> **Status:** design note, not built. The runtime today issues raw `syscall`
|
||||||
> instructions from `library/runtime/system-call.zig` using the numbers in
|
> instructions from `library/kernel/system-call.zig` using the numbers in
|
||||||
> `system/abi.zig`. This note designs the layer that replaces that arrangement:
|
> `system/abi.zig`. This note designs the layer that replaces that arrangement:
|
||||||
> a **kernel-supplied, C-ABI entry library** mapped into every process — the
|
> a **kernel-supplied, C-ABI entry library** mapped into every process — the
|
||||||
> only supported way into the kernel — so the raw numbers can stay private,
|
> only supported way into the kernel — so the raw numbers can stay private,
|
||||||
@@ -25,8 +25,8 @@ ourselves:
|
|||||||
this mistake: it issued XNU syscalls directly instead of going through
|
this mistake: it issued XNU syscalls directly instead of going through
|
||||||
libSystem, and macOS updates repeatedly broke every Go binary until Go
|
libSystem, and macOS updates repeatedly broke every Go binary until Go
|
||||||
switched to the library like everyone else.
|
switched to the library like everyone else.
|
||||||
2. **Not everything is Zig.** A Rust or C program can't import the `runtime`
|
2. **Not everything is Zig.** A Rust or C program can't import the danos Zig
|
||||||
module. The public boundary has to be expressible in the one calling
|
modules. The public boundary has to be expressible in the one calling
|
||||||
convention every language speaks: the C ABI.
|
convention every language speaks: the C ABI.
|
||||||
3. **Randomised syscall numbers** — a hardening option we want open — only
|
3. **Randomised syscall numbers** — a hardening option we want open — only
|
||||||
work if no user binary anywhere knows a number at build time. The binding
|
work if no user binary anywhere knows a number at build time. The binding
|
||||||
@@ -49,10 +49,10 @@ The public danos ABI then has exactly two layers, neither of which is
|
|||||||
|
|
||||||
| Layer | Contract | Spoken by |
|
| Layer | Contract | Spoken by |
|
||||||
|-------|----------|-----------|
|
|-------|----------|-----------|
|
||||||
| **vDSO** | C-ABI functions, this note | every language's thin shim (`runtime.system` for Zig, a `-sys` crate for Rust, a header for C) |
|
| **vDSO** | C-ABI functions, this note | every language's thin shim (the `system-call` module for Zig, a `-sys` crate for Rust, a header for C) |
|
||||||
| **IPC wire protocols** | byte layouts over `ipc_call` ([vfs-protocol.md](vfs-protocol.md) is the first one documented) | any client that can lay out bytes |
|
| **IPC wire protocols** | byte layouts over `ipc_call` ([vfs-protocol.md](vfs-protocol.md) is the first one documented) | any client that can lay out bytes |
|
||||||
|
|
||||||
Everything above those — the heap, `runtime.fs`, the service harness — is
|
Everything above those — the heap, `file_system`, the service harness — is
|
||||||
per-language convenience, compiled into each binary from source, exactly as
|
per-language convenience, compiled into each binary from source, exactly as
|
||||||
today. Nothing about the Zig runtime's shape changes; it just stops being the
|
today. Nothing about the Zig runtime's shape changes; it just stops being the
|
||||||
*only* door.
|
*only* door.
|
||||||
@@ -107,7 +107,7 @@ convenience, not a requirement.)
|
|||||||
|
|
||||||
The kernel already builds a System V entry block — argc, argv, envp
|
The kernel already builds a System V entry block — argc, argv, envp
|
||||||
terminator, **auxiliary vector** — on every new process's stack
|
terminator, **auxiliary vector** — on every new process's stack
|
||||||
(`buildEntryStack`, read by `runtime.start`). The vDSO base rides in a new
|
(`buildEntryStack`, read by the `start` module). The vDSO base rides in a new
|
||||||
auxv entry, exactly Linux's `AT_SYSINFO_EHDR` move. No new syscall, no magic
|
auxv entry, exactly Linux's `AT_SYSINFO_EHDR` move. No new syscall, no magic
|
||||||
address, and a language shim finds it the same portable way on every
|
address, and a language shim finds it the same portable way on every
|
||||||
architecture.
|
architecture.
|
||||||
@@ -183,11 +183,11 @@ second — but the design should never be sold as more than that.
|
|||||||
Phased so every step ships alone (the M-milestone discipline):
|
Phased so every step ships alone (the M-milestone discipline):
|
||||||
|
|
||||||
1. **The blob + the table.** Build the vDSO, map it at spawn, deliver the
|
1. **The blob + the table.** Build the vDSO, map it at spawn, deliver the
|
||||||
base via auxv. `runtime.system-call.zig` binds through the table when the
|
base via auxv. `library/kernel/system-call.zig` binds through the table when the
|
||||||
auxv entry is present, falls back to raw `syscall` when absent — the whole
|
auxv entry is present, falls back to raw `syscall` when absent — the whole
|
||||||
tree keeps booting during the transition.
|
tree keeps booting during the transition.
|
||||||
2. **Cut the runtime over.** Delete the raw stubs; `runtime` no longer
|
2. **Cut the system library over.** Delete the raw stubs; the `system-call`
|
||||||
imports the `SystemCall` numbers at all (`abi.zig`'s enum becomes
|
module no longer imports the `SystemCall` numbers at all (`abi.zig`'s enum becomes
|
||||||
kernel-internal). The QEMU suite passing proves the table carries the
|
kernel-internal). The QEMU suite passing proves the table carries the
|
||||||
whole system.
|
whole system.
|
||||||
3. **Enforce + randomise.** Add the `rip`-range check, then per-boot number
|
3. **Enforce + randomise.** Add the `rip`-range check, then per-boot number
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# The VFS wire protocol
|
# The VFS wire protocol
|
||||||
|
|
||||||
> **Status:** built and spoken today between `runtime.fs` (the client) and the
|
> **Status:** built and spoken today between `file_system` (the client) and the
|
||||||
> filesystem BACKENDS (the FAT server). The mount router lives in the
|
> filesystem BACKENDS (the FAT server). The mount router lives in the
|
||||||
> **kernel** (`system/kernel/vfs.zig`): `fs_resolve` routes a path and either
|
> **kernel** (`system/kernel/vfs.zig`): `fs_resolve` routes a path and either
|
||||||
> serves it directly (the read-only /system initrd mount, via `fs_node`) or
|
> serves it directly (the read-only /system initrd mount, via `fs_node`) or
|
||||||
@@ -113,7 +113,7 @@ Notes per operation:
|
|||||||
prefix maps a mount into the backend's namespace (fat serves `/mnt/usb`
|
prefix maps a mount into the backend's namespace (fat serves `/mnt/usb`
|
||||||
from its volume root and `/var` from its `/var` subtree).
|
from its volume root and `/var` from its `/var` subtree).
|
||||||
- **rename** — same-directory rename only: the backend compares the old and
|
- **rename** — same-directory rename only: the backend compares the old and
|
||||||
new parent paths and refuses a mismatch. The client (`runtime.fs`) refuses
|
new parent paths and refuses a mismatch. The client (`file_system`) refuses
|
||||||
earlier when the two paths resolve to different backend endpoints, but that
|
earlier when the two paths resolve to different backend endpoints, but that
|
||||||
check is coarser than "one mount" — one endpoint can serve several mounts
|
check is coarser than "one mount" — one endpoint can serve several mounts
|
||||||
(fat serves `/mnt/usb` and `/var`), so a cross-mount rename reaches the
|
(fat serves `/mnt/usb` and `/var`), so a cross-mount rename reaches the
|
||||||
|
|||||||
Reference in New Issue
Block a user