kernel: shm cross-process shared memory capability (v2 V2)

Generalize capability passing from endpoints to memory objects. The per-task
handle table now holds kind-tagged entries (scheduler.HandleObject{kind, ptr});
closeHandles and shareCapability dispatch by kind, so a shared-memory object
rides an ipc_call send_cap exactly like an endpoint and is refcount-freed only
when its last capability drops.

- shm_create(len) -> vaddr, handle: contiguous, zeroed, cacheable frames wrapped
  in a refcounted ShmObject, mapped into the caller's shm arena (PML4[230]).
- shm_map(cap) -> vaddr: map the same physical pages into a receiver that got the
  capability. mapUserSharedInto maps WB-cacheable + device_grant, so a sharer's
  teardown never frees the shared frames — the object owns them.
- runtime.shm: create(len) -> Region{ptr, handle, len}, map(handle) -> ptr.

Gate: qemu_test.py shm — shm-client creates a region, writes a pattern, passes
its capability to shm-server, which maps it and reads the same bytes back
(shm: shared 4096 bytes ok). ipc/ipc-call/ipc-cap/supervision/dma/usermem/
display-service and host tests all still pass — the handle change broke no IPC.
This commit is contained in:
Daniel Samson
2026-07-14 10:57:14 +01:00
parent 9333d0572f
commit 88ad432758
14 changed files with 439 additions and 31 deletions
+32
View File
@@ -101,6 +101,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
displayServiceTest(boot_information);
} else if (eql(case, "display-demo")) {
displayDemoTest(boot_information);
} else if (eql(case, "shm")) {
shmTest(boot_information);
} else if (eql(case, "clock")) {
clockTest();
} else if (eql(case, "smp")) {
@@ -2377,6 +2379,36 @@ fn displayDemoTest(boot_information: *const BootInformation) void {
while (true) scheduler.yield();
}
/// V2 — cross-process shared memory (docs/display-v2.md). Spawn shm-server and shm-client:
/// the client shm_creates a region, writes a pattern, and passes the region's capability to
/// the server as an ipc_call send_cap; the server shm_maps it and confirms the pattern is
/// visible — proving the two processes share the same physical pages, and that the extended
/// capability-passing (endpoints → memory objects) works. Its `shm: shared 4096 bytes ok`
/// heartbeat is the marker.
fn shmTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: shm\n", .{});
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
if (!spawnNamed(rd, "shm-server")) {
log("shm: could not spawn shm-server\n", .{});
result();
return;
}
_ = spawnNamed(rd, "shm-client");
scheduler.setPriority(1); // below the two, so they run
while (true) scheduler.yield();
}
/// Process arguments, end to end: spawn args-echo bare (its argv[0] is the
/// initial-ramdisk name). Instance 1 sees argc == 1 and respawns itself through
/// `system_spawn` with the extra arguments "alpha beta-42" — the syscall argument