Pass argv to processes on a SysV entry stack; grow the user stack to 32 KiB
Processes now start with C-compatible arguments: the kernel builds the System V AMD64 entry block (argc, argv, empty envp, auxiliary vector) at the top of the stack, argv[0] is the path or initial-ramdisk name the process was spawned as, and system_spawn carries an optional NUL-separated blob that becomes argv[1..]. The runtime parses the block (runtime.argumentCount/argument) and its spawn wrappers pass arguments through. The name is also recorded on the task, so a fault report says which binary died, not just its id. The user stack grows from one page to eight (32 KiB, parameters.user_stack_pages), with the page below left unmapped as a guard so an overflow faults into a clean process kill rather than corrupting the image. Task.name_buffer is zero-initialised, not undefined: an undefined default is materialised as a 0xAA fill that moved the static task pool out of .bss and made the whole kernel ~7x slower under QEMU TCG (caught by the affinity test). Proven end to end by the new args test: args-echo respawns itself with arguments via the syscall blob, burns more stack than one page could hold, and echoes its argv intact. Full suite: 44/44.
This commit is contained in:
@@ -164,8 +164,11 @@ If a class driver needs `mmio`, it has become an HCD and should be one.
|
||||
DMA is still unprotected** (the caveat below). Enforcement lands with the first DMA
|
||||
driver, which is what there is to protect and test against. Proven in the `iommu` test,
|
||||
booted with an emulated `intel-iommu`.
|
||||
- **`system_spawn`** — a user-space supervisor starts a driver: `system_spawn(name)`
|
||||
loads a binary bundled in the initial-ramdisk as a fresh ring-3 process. This is what
|
||||
- **`system_spawn`** — a user-space supervisor starts a driver:
|
||||
`system_spawn(name, arguments)` loads a binary bundled in the initial-ramdisk as a
|
||||
fresh ring-3 process; `name` becomes the child's argv[0] and the optional
|
||||
NUL-separated `arguments` blob its argv[1..], delivered on a SysV entry stack
|
||||
([sysv.md](sysv.md)). This is what
|
||||
turned the device manager from "log the match" into "run the driver": the kernel now
|
||||
spawns only `init`, `init` spawns the services, and the **device-manager** discovers
|
||||
the hardware and spawns each driver ([drivers.md](drivers.md)). Ungated for now — a
|
||||
|
||||
+5
-3
@@ -37,8 +37,9 @@ kernel ──spawns──► init (PID 1) ──spawns──► device-manag
|
||||
```
|
||||
|
||||
The kernel launches exactly one process — `init` — and hands it nothing but the raw
|
||||
ability to start more (`system_spawn(name)`, which loads a binary bundled in the
|
||||
initial-ramdisk as a fresh ring-3 process). Everything else is a user-space decision:
|
||||
ability to start more (`system_spawn(name, arguments)`, which loads a binary bundled
|
||||
in the initial-ramdisk as a fresh ring-3 process — `name` becoming its argv[0],
|
||||
the optional arguments its argv[1..], on a SysV entry stack, see sysv.md). Everything else is a user-space decision:
|
||||
|
||||
- **init** ([system/services/init](system/services/init/init.zig)) is the **service
|
||||
supervisor**. It spawns the system services danos brings up at boot — today `vfs` and
|
||||
@@ -52,7 +53,8 @@ initial-ramdisk as a fresh ring-3 process). Everything else is a user-space deci
|
||||
is a table (`driverFor`): today a static `timer → hpet` map; a fuller system reads
|
||||
what each driver *binds* (a manifest under `/system/drivers`, or the driver
|
||||
describing its own match).
|
||||
3. **Spawn** — `system_spawn(driver_name)` starts the matched driver, which then claims
|
||||
3. **Spawn** — `system_spawn(driver_name, arguments)` starts the matched driver (the
|
||||
arguments can carry *which* device it matched), which then claims
|
||||
its device and runs the event loop below.
|
||||
|
||||
So "how is a driver discovered and configured" has two halves: **discovery** is the
|
||||
|
||||
@@ -65,6 +65,36 @@ function-pointer type and the kernel's `_start` both carry
|
||||
whole reason `kernel_abi` lives in the shared contract — see [efi.md](efi.md) for
|
||||
the handoff it governs.
|
||||
|
||||
## The process-entry stack (argc/argv)
|
||||
|
||||
The SysV ABI also fixes what a *fresh process* finds on its stack — and danos
|
||||
follows it, so its own runtime and any future C libc read arguments the same way.
|
||||
At the first user instruction, `rsp` is 16-byte aligned and points at (addresses
|
||||
growing upward):
|
||||
|
||||
```
|
||||
rsp → argc u64
|
||||
argv[0] … argv[argc-1] pointers into the strings area below
|
||||
NULL argv terminator
|
||||
NULL envp terminator (no environment yet)
|
||||
{AT_PAGESZ, page size} auxiliary vector
|
||||
{AT_NULL, 0} auxiliary-vector terminator
|
||||
argv string bytes NUL-terminated
|
||||
───────────────────────── stack top (stack_top_virtual)
|
||||
```
|
||||
|
||||
The kernel builds this block at the top of the process's stack — 8 pages (32 KiB,
|
||||
`parameters.user_stack_pages`) mapped RW+NX below a fixed top, with the page below
|
||||
them left unmapped as a **guard**, so a stack overflow faults (killing only that
|
||||
process) instead of silently corrupting the image
|
||||
(`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
|
||||
argument blob becomes `argv[1..]`. The runtime's `_start`
|
||||
(`library/runtime/start.zig`) hands the block to `rt_start`, which exposes it as
|
||||
`runtime.argumentCount()` / `runtime.argument(i)`. A C runtime's `crt0` would walk
|
||||
the identical layout unmodified — that's the compatibility being bought. The
|
||||
`args` test proves the round trip.
|
||||
|
||||
## Where else it surfaces
|
||||
|
||||
- **The red zone → `red_zone = false`.** `build.zig` disables the red zone for the
|
||||
|
||||
Reference in New Issue
Block a user