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:
@@ -189,8 +189,8 @@ pub fn mapUserDmaInto(root: u64, virtual: u64, physical: u64, len: u64) void {
|
||||
}
|
||||
|
||||
/// Map shared cacheable RAM into address space `root`: write-back cacheable, RW+NX, and
|
||||
/// marked so teardown won't free the frames (they're owned by a refcounted shm object,
|
||||
/// freed when its last capability drops). For shm_create/shm_map.
|
||||
/// marked so teardown won't free the frames (they're owned by a refcounted shared-memory object,
|
||||
/// freed when its last capability drops). For shared_memory_create/shared_memory_map.
|
||||
pub fn mapUserSharedInto(root: u64, virtual: u64, physical: u64, len: u64) void {
|
||||
paging.mapUserSharedInto(root, virtual, physical, len);
|
||||
}
|
||||
|
||||
@@ -153,35 +153,35 @@ pub fn dropRef(endpoint: *Endpoint) void {
|
||||
/// The `kind` tag on a `scheduler.HandleObject` — which capability object a handle names.
|
||||
/// Defined here (not in scheduler) because the meaning is the IPC/capability layer's.
|
||||
pub const handle_kind_endpoint: u8 = 0;
|
||||
pub const handle_kind_shm: u8 = 1;
|
||||
pub const handle_kind_shared_memory: u8 = 1;
|
||||
|
||||
/// A page-aligned block of **shared cacheable RAM** (docs/display-v2.md), referenced by
|
||||
/// capability handles across processes and freed when the last one drops. `phys` is its
|
||||
/// contiguous physical base, `pages` its length. A sharer's address-space teardown never
|
||||
/// reclaims these frames (the mapping carries `device_grant`); this object owns them.
|
||||
pub const ShmObject = struct {
|
||||
pub const SharedMemoryObject = struct {
|
||||
refcount: u32 = 1,
|
||||
phys: u64,
|
||||
pages: usize,
|
||||
};
|
||||
|
||||
/// Wrap `pages` contiguous frames at `phys` (already allocated + zeroed by the caller) in a
|
||||
/// refcounted shm object, or null if the heap is out of room.
|
||||
pub fn createShm(phys: u64, pages: usize) ?*ShmObject {
|
||||
const shm = heap.allocator().create(ShmObject) catch return null;
|
||||
shm.* = .{ .phys = phys, .pages = pages };
|
||||
return shm;
|
||||
/// refcounted shared-memory object, or null if the heap is out of room.
|
||||
pub fn createSharedMemory(phys: u64, pages: usize) ?*SharedMemoryObject {
|
||||
const shared_memory = heap.allocator().create(SharedMemoryObject) catch return null;
|
||||
shared_memory.* = .{ .phys = phys, .pages = pages };
|
||||
return shared_memory;
|
||||
}
|
||||
|
||||
/// Drop a shared-memory reference; when the last one goes, return its frames to the
|
||||
/// allocator and free the object. (The mappings themselves are torn down with each
|
||||
/// sharer's address space; `device_grant` keeps that from freeing the frames early.)
|
||||
pub fn dropShmRef(shm: *ShmObject) void {
|
||||
if (shm.refcount > 1) {
|
||||
shm.refcount -= 1;
|
||||
pub fn dropSharedMemoryReference(shared_memory: *SharedMemoryObject) void {
|
||||
if (shared_memory.refcount > 1) {
|
||||
shared_memory.refcount -= 1;
|
||||
} else {
|
||||
for (0..shm.pages) |i| pmm.free(shm.phys + i * page_size);
|
||||
heap.allocator().destroy(shm);
|
||||
for (0..shared_memory.pages) |i| pmm.free(shared_memory.phys + i * page_size);
|
||||
heap.allocator().destroy(shared_memory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,8 +294,8 @@ fn shareCapability(from: *Task, to: *Task, cap: u64) i64 {
|
||||
const e: *Endpoint = @ptrCast(@alignCast(entry.ptr));
|
||||
e.refcount += 1;
|
||||
},
|
||||
handle_kind_shm => {
|
||||
const s: *ShmObject = @ptrCast(@alignCast(entry.ptr));
|
||||
handle_kind_shared_memory => {
|
||||
const s: *SharedMemoryObject = @ptrCast(@alignCast(entry.ptr));
|
||||
s.refcount += 1;
|
||||
},
|
||||
else => return -EBADF,
|
||||
@@ -512,12 +512,12 @@ pub fn installHandle(t: *Task, endpoint: *Endpoint) i64 {
|
||||
}
|
||||
|
||||
/// Install a shared-memory handle.
|
||||
pub fn installShmHandle(t: *Task, shm: *ShmObject) i64 {
|
||||
return installEntry(t, .{ .kind = handle_kind_shm, .ptr = @ptrCast(shm) });
|
||||
pub fn installSharedMemoryHandle(t: *Task, shared_memory: *SharedMemoryObject) i64 {
|
||||
return installEntry(t, .{ .kind = handle_kind_shared_memory, .ptr = @ptrCast(shared_memory) });
|
||||
}
|
||||
|
||||
/// Resolve a handle to its endpoint, or null if out of range, unused, or a different kind
|
||||
/// (e.g. an shm handle used where an endpoint is expected).
|
||||
/// (e.g. a shared-memory handle used where an endpoint is expected).
|
||||
pub fn resolveHandle(t: *Task, h: u64) ?*Endpoint {
|
||||
if (h >= t.handles.len) return null;
|
||||
const entry = t.handles[@intCast(h)] orelse return null;
|
||||
@@ -526,11 +526,11 @@ pub fn resolveHandle(t: *Task, h: u64) ?*Endpoint {
|
||||
}
|
||||
|
||||
/// Resolve a handle to its shared-memory object, or null if out of range, unused, or not
|
||||
/// an shm handle.
|
||||
pub fn resolveShm(t: *Task, h: u64) ?*ShmObject {
|
||||
/// a shared-memory handle.
|
||||
pub fn resolveSharedMemory(t: *Task, h: u64) ?*SharedMemoryObject {
|
||||
if (h >= t.handles.len) return null;
|
||||
const entry = t.handles[@intCast(h)] orelse return null;
|
||||
if (entry.kind != handle_kind_shm) return null;
|
||||
if (entry.kind != handle_kind_shared_memory) return null;
|
||||
return @ptrCast(@alignCast(entry.ptr));
|
||||
}
|
||||
|
||||
@@ -550,7 +550,7 @@ pub fn closeHandles(t: *Task) void {
|
||||
fn dropEntry(entry: scheduler.HandleObject) void {
|
||||
switch (entry.kind) {
|
||||
handle_kind_endpoint => dropRef(@ptrCast(@alignCast(entry.ptr))),
|
||||
handle_kind_shm => dropShmRef(@ptrCast(@alignCast(entry.ptr))),
|
||||
handle_kind_shared_memory => dropSharedMemoryReference(@ptrCast(@alignCast(entry.ptr))),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
+38
-38
@@ -82,17 +82,17 @@ pub const device_arena_end: u64 = device_arena_base + (4 << 30);
|
||||
pub const dma_arena_base: u64 = 0x0000_7200_0000_0000;
|
||||
pub const dma_arena_end: u64 = dma_arena_base + (256 << 20); // 256 MiB per process
|
||||
|
||||
/// The shared-memory arena: where `shm_create`/`shm_map` place shared cacheable regions, in
|
||||
/// The shared-memory arena: where `shared_memory_create`/`shared_memory_map` place shared cacheable regions, in
|
||||
/// PML4[230] — a user-exclusive region distinct from the DMA arena. The frames are owned by
|
||||
/// a refcounted shm object and freed when its last capability drops, not on teardown, so the
|
||||
/// mapping carries `device_grant`. Per-process cursor in `Task.shm_map_next` (docs/display-v2.md).
|
||||
pub const shm_arena_base: u64 = 0x0000_7300_0000_0000;
|
||||
pub const shm_arena_end: u64 = shm_arena_base + (256 << 20); // 256 MiB per process
|
||||
/// a refcounted shared-memory object and freed when its last capability drops, not on teardown, so the
|
||||
/// mapping carries `device_grant`. Per-process cursor in `Task.shared_memory_map_next` (docs/display-v2.md).
|
||||
pub const shared_memory_arena_base: u64 = 0x0000_7300_0000_0000;
|
||||
pub const shared_memory_arena_end: u64 = shared_memory_arena_base + (256 << 20); // 256 MiB per process
|
||||
|
||||
/// Largest single `shm_create`, in pages (32 MiB) — enough for a 4K framebuffer surface;
|
||||
/// also an overflow guard on the page count. shm frames are contiguous (like DMA), so this
|
||||
/// Largest single `shared_memory_create`, in pages (32 MiB) — enough for a 4K framebuffer surface;
|
||||
/// also an overflow guard on the page count. shared-memory frames are contiguous (like DMA), so this
|
||||
/// bounds the contiguous allocation asked of the frame allocator.
|
||||
const maximum_shm_pages = 8192;
|
||||
const maximum_shared_memory_pages = 8192;
|
||||
|
||||
/// Largest single `mmap` grant, in pages (32 MiB). Big enough for a display service's
|
||||
/// back buffer at up to 4K (3840x2160x4 ≈ 8100 pages); the user heap otherwise grows in
|
||||
@@ -225,9 +225,9 @@ fn system_call(state: *architecture.CpuState) void {
|
||||
.timer_bind => systemTimerBind(state),
|
||||
.klog_read => systemKlogRead(state),
|
||||
.wall_clock => systemWallClock(state),
|
||||
.shm_create => systemShmCreate(state),
|
||||
.shm_map => systemShmMap(state),
|
||||
.shm_physical => systemShmPhysical(state),
|
||||
.shared_memory_create => systemSharedMemoryCreate(state),
|
||||
.shared_memory_map => systemSharedMemoryMap(state),
|
||||
.shared_memory_physical => systemSharedMemoryPhysical(state),
|
||||
.thread_spawn => systemThreadSpawn(state),
|
||||
.current_core => systemCurrentCore(state),
|
||||
.thread_self => systemThreadSelf(state),
|
||||
@@ -513,79 +513,79 @@ fn systemDmaFree(state: *architecture.CpuState) void {
|
||||
architecture.setSystemCallResult(state, 0);
|
||||
}
|
||||
|
||||
/// shm_create(len) -> virtual_address (rax), handle (rdx): grant `len` bytes (rounded up to whole
|
||||
/// shared_memory_create(len) -> virtual_address (rax), handle (rdx): grant `len` bytes (rounded up to whole
|
||||
/// pages) of **shareable, zeroed, cacheable** RAM — contiguous frames mapped into the
|
||||
/// caller's shm arena — and hand back the virtual address plus a capability handle. Unlike
|
||||
/// caller's shared-memory arena — and hand back the virtual address plus a capability handle. Unlike
|
||||
/// `dma_alloc` the memory is write-back cacheable (for CPU compositing, not device DMA) and
|
||||
/// its frames are owned by a refcounted object: the handle is passed to another process as
|
||||
/// an `ipc_call` send_cap, that process `shm_map`s it, and the frames free only when the
|
||||
/// an `ipc_call` send_cap, that process `shared_memory_map`s it, and the frames free only when the
|
||||
/// last capability drops (docs/display-v2.md — the compositor↔native-driver and
|
||||
/// app↔compositor surface path).
|
||||
fn systemShmCreate(state: *architecture.CpuState) void {
|
||||
fn systemSharedMemoryCreate(state: *architecture.CpuState) void {
|
||||
const len = architecture.systemCallArg(state, 0);
|
||||
const t = scheduler.current();
|
||||
if (t.address_space == 0 or len == 0) return fail(state);
|
||||
|
||||
const pages: usize = @intCast((len + page_size - 1) / page_size);
|
||||
if (pages == 0 or pages > maximum_shm_pages) return fail(state);
|
||||
if (pages == 0 or pages > maximum_shared_memory_pages) return fail(state);
|
||||
|
||||
// Reserve arena virtual space up front, so a mapping failure needs no rollback.
|
||||
if (t.shm_map_next == 0) t.shm_map_next = shm_arena_base;
|
||||
const base_v = t.shm_map_next;
|
||||
if (base_v + pages * page_size > shm_arena_end) return fail(state); // arena exhausted
|
||||
if (t.shared_memory_map_next == 0) t.shared_memory_map_next = shared_memory_arena_base;
|
||||
const base_v = t.shared_memory_map_next;
|
||||
if (base_v + pages * page_size > shared_memory_arena_end) return fail(state); // arena exhausted
|
||||
|
||||
const phys = pmm.allocContiguous(pages, ~@as(u64, 0)) orelse return fail(state);
|
||||
// Zero through the physmap (the frames aren't mapped in the caller yet).
|
||||
const kernel_view: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(phys));
|
||||
@memset(kernel_view[0 .. pages * page_size], 0);
|
||||
|
||||
const shm = ipc.createShm(phys, pages) orelse {
|
||||
const shared_memory = ipc.createSharedMemory(phys, pages) orelse {
|
||||
for (0..pages) |i| pmm.free(phys + i * page_size);
|
||||
return fail(state);
|
||||
};
|
||||
const handle = ipc.installShmHandle(t, shm);
|
||||
const handle = ipc.installSharedMemoryHandle(t, shared_memory);
|
||||
if (handle < 0) {
|
||||
ipc.dropShmRef(shm); // last ref: frees the object and its frames
|
||||
ipc.dropSharedMemoryReference(shared_memory); // last ref: frees the object and its frames
|
||||
return fail(state);
|
||||
}
|
||||
|
||||
architecture.mapUserSharedInto(t.address_space, base_v, phys, pages * page_size);
|
||||
t.shm_map_next = base_v + pages * page_size;
|
||||
t.shared_memory_map_next = base_v + pages * page_size;
|
||||
architecture.setSystemCallResult(state, base_v); // virtual_address for the CPU
|
||||
architecture.setSystemCallResult2(state, @intCast(handle)); // capability handle to pass on
|
||||
}
|
||||
|
||||
/// shm_map(cap) -> virtual_address: map the shared region named by a capability handle the caller
|
||||
/// received (via an `ipc_call` send_cap) into its shm arena — the same physical frames the
|
||||
/// shared_memory_map(cap) -> virtual_address: map the shared region named by a capability handle the caller
|
||||
/// received (via an `ipc_call` send_cap) into its shared-memory arena — the same physical frames the
|
||||
/// creator sees — returning the virtual address. The handle already holds a reference (taken
|
||||
/// when the capability was shared), so this only adds a mapping; it never bumps the refcount.
|
||||
fn systemShmMap(state: *architecture.CpuState) void {
|
||||
fn systemSharedMemoryMap(state: *architecture.CpuState) void {
|
||||
const cap = architecture.systemCallArg(state, 0);
|
||||
const t = scheduler.current();
|
||||
if (t.address_space == 0) return fail(state);
|
||||
|
||||
const shm = ipc.resolveShm(t, cap) orelse return fail(state); // not an shm handle we hold
|
||||
if (t.shm_map_next == 0) t.shm_map_next = shm_arena_base;
|
||||
const base_v = t.shm_map_next;
|
||||
const size = shm.pages * page_size;
|
||||
if (base_v + size > shm_arena_end) return fail(state);
|
||||
const shared_memory = ipc.resolveSharedMemory(t, cap) orelse return fail(state); // not a shared-memory handle we hold
|
||||
if (t.shared_memory_map_next == 0) t.shared_memory_map_next = shared_memory_arena_base;
|
||||
const base_v = t.shared_memory_map_next;
|
||||
const size = shared_memory.pages * page_size;
|
||||
if (base_v + size > shared_memory_arena_end) return fail(state);
|
||||
|
||||
architecture.mapUserSharedInto(t.address_space, base_v, shm.phys, size);
|
||||
t.shm_map_next = base_v + size;
|
||||
architecture.mapUserSharedInto(t.address_space, base_v, shared_memory.phys, size);
|
||||
t.shared_memory_map_next = base_v + size;
|
||||
architecture.setSystemCallResult(state, base_v);
|
||||
}
|
||||
|
||||
/// shm_physical(cap) -> physical_address: the guest-physical base of a shared region the caller holds a
|
||||
/// shared_memory_physical(cap) -> physical_address: the guest-physical base of a shared region the caller holds a
|
||||
/// capability for. The frames are contiguous (allocated by `allocContiguous`), so a single
|
||||
/// physical base + length describes the whole region — which is exactly what a driver needs
|
||||
/// to hand a shm surface to a device (virtio-gpu `attach_backing`). Only a holder of the
|
||||
/// to hand a shared-memory surface to a device (virtio-gpu `attach_backing`). Only a holder of the
|
||||
/// capability can ask; there is no ambient way to turn a virtual address into a physical one.
|
||||
fn systemShmPhysical(state: *architecture.CpuState) void {
|
||||
fn systemSharedMemoryPhysical(state: *architecture.CpuState) void {
|
||||
const cap = architecture.systemCallArg(state, 0);
|
||||
const t = scheduler.current();
|
||||
if (t.address_space == 0) return fail(state);
|
||||
const shm = ipc.resolveShm(t, cap) orelse return fail(state); // not an shm handle we hold
|
||||
architecture.setSystemCallResult(state, shm.phys);
|
||||
const shared_memory = ipc.resolveSharedMemory(t, cap) orelse return fail(state); // not a shared-memory handle we hold
|
||||
architecture.setSystemCallResult(state, shared_memory.phys);
|
||||
}
|
||||
|
||||
/// device_register(parent_id, descriptor_ptr) -> id: publish a child device below a device
|
||||
|
||||
@@ -113,7 +113,7 @@ pub const Task = struct {
|
||||
ipc_reply_cap: u64 = 0,
|
||||
ipc_status: i64 = 0, // client: reply length / -errno, written by the replier
|
||||
dma_map_next: u64 = 0, // bump pointer into this task's DMA arena (0 = unseeded)
|
||||
shm_map_next: u64 = 0, // bump pointer into this task's shared-memory arena (0 = unseeded)
|
||||
shared_memory_map_next: u64 = 0, // bump pointer into this task's shared-memory arena (0 = unseeded)
|
||||
ipc_send_cap: u64 = ~@as(u64, 0), // handle to transfer with this message (abi.no_cap = none)
|
||||
ipc_received_cap: u64 = ~@as(u64, 0), // client: handle the reply's transferred cap landed at (abi.no_cap = none)
|
||||
next: ?*Task = null, // ready-queue link (also the endpoint sender-FIFO link)
|
||||
|
||||
+11
-11
@@ -103,8 +103,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
displayDemoTest(boot_information);
|
||||
} else if (eql(case, "display-cursor")) {
|
||||
displayCursorTest(boot_information);
|
||||
} else if (eql(case, "shm")) {
|
||||
shmTest(boot_information);
|
||||
} else if (eql(case, "shared-memory")) {
|
||||
sharedMemoryTest(boot_information);
|
||||
} else if (eql(case, "virtio-gpu")) {
|
||||
virtioGpuTest(boot_information);
|
||||
} else if (eql(case, "display-native")) {
|
||||
@@ -2860,14 +2860,14 @@ 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
|
||||
/// V2 — cross-process shared memory (docs/display-v2.md). Spawn shared-memory-server and shared-memory-client:
|
||||
/// the client shared_memory_creates a region, writes a pattern, and passes the region's capability to
|
||||
/// the server as an ipc_call send_cap; the server shared_memory_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`
|
||||
/// capability-passing (endpoints → memory objects) works. Its `shared-memory: shared 4096 bytes ok`
|
||||
/// heartbeat is the marker.
|
||||
fn shmTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: shm\n", .{});
|
||||
fn sharedMemoryTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: shared-memory\n", .{});
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
@@ -2880,12 +2880,12 @@ fn shmTest(boot_information: *const BootInformation) void {
|
||||
return;
|
||||
};
|
||||
|
||||
if (!spawnNamed(rd, "shm-server")) {
|
||||
log("shm: could not spawn shm-server\n", .{});
|
||||
if (!spawnNamed(rd, "shared-memory-server")) {
|
||||
log("shared-memory: could not spawn shared-memory-server\n", .{});
|
||||
result();
|
||||
return;
|
||||
}
|
||||
_ = spawnNamed(rd, "shm-client");
|
||||
_ = spawnNamed(rd, "shared-memory-client");
|
||||
scheduler.setPriority(1); // below the two, so they run
|
||||
while (true) scheduler.yield();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user