Add lib/ — the shared user-space runtime every user binary links against (init now, servers/drivers later): syscall wrappers, the heap, IPC stub, and the process start shim. - lib/heap.zig: the kernel first-fit free-list ported to user space, grown via the mmap syscall instead of pmm+mapPage. Dual API over one global free list: extern "C" malloc/free/calloc/realloc (C ABI for future C code) and a std.mem.Allocator adapter (with in-place resize) for Zig std containers. - lib/syscall.zig + sys.zig: raw syscall0..5 (arg3 in r10) and typed yield/write/sleep/exit/mmap/munmap over danos.Syscall. - lib/start.zig: naked _start -> rt_start -> root.main() (SysV realign via call), panic -> exit(127). - lib/user.ld: the user link script, moved from sbin/linker.ld (shared by all user binaries). - build.zig: register the `rt` module; add an addUserBinary() helper that is the one recipe for every user binary (freestanding, .large, use_lld, user.ld, image_base), replacing the bespoke init block. - sbin/init.zig: migrated onto rt; drops its hand-rolled syscall2/shims. Now proves the heap (alloc -> write from a heap pointer -> free) before the heartbeat loop. Serial shows "init: heap ok". Suite 28/28.
194 lines
6.7 KiB
Zig
194 lines
6.7 KiB
Zig
//! The user-space heap: C-convention dynamic allocation (`malloc`/`free`/…) plus
|
|
//! a `std.mem.Allocator` adapter over the same free list, so both C-style code
|
|
//! and Zig `std` containers share one heap.
|
|
//!
|
|
//! The algorithm is a straight port of the kernel's first-fit free list
|
|
//! (src/kernel/heap.zig): an address-ordered singly linked list of free blocks,
|
|
//! split on allocation and coalesced with neighbours on free. The only thing
|
|
//! that changes on this side of the syscall boundary is where memory comes from
|
|
//! — `grow` asks the kernel for pages via `mmap` instead of mapping frames
|
|
//! itself, and the kernel picks the base address.
|
|
//!
|
|
//! Single-threaded and 16-byte max alignment, exactly like the kernel heap; a
|
|
//! lock and larger alignments come when user programs gain threads.
|
|
|
|
const std = @import("std");
|
|
const danos = @import("danos");
|
|
const sys = @import("sys.zig");
|
|
|
|
const page_size = danos.page_size;
|
|
|
|
/// A block header, at the start of every block; while free it also links the
|
|
/// free list via `next`.
|
|
const Block = extern struct {
|
|
size: usize, // total block size in bytes, including this header; a multiple of 16
|
|
next: ?*Block, // free-list link (only meaningful while free)
|
|
};
|
|
|
|
const header_size = @sizeOf(Block); // 16
|
|
const min_block = header_size + 16; // smallest block worth splitting off
|
|
/// Grow granularity: one `mmap` per 64 KiB amortises the syscall.
|
|
const chunk = 64 * 1024;
|
|
|
|
var free_list: ?*Block = null;
|
|
|
|
fn alignUp(value: usize, alignment: usize) usize {
|
|
return (value + alignment - 1) & ~(alignment - 1);
|
|
}
|
|
|
|
fn payloadOf(block: *Block) [*]u8 {
|
|
return @ptrFromInt(@intFromPtr(block) + header_size);
|
|
}
|
|
|
|
/// Ask the kernel for more pages and add them as a free block. Because each
|
|
/// `mmap` is an independent grant, cross-grant coalescing happens only when the
|
|
/// kernel returns adjacent bases (its arena is a bump allocator, so consecutive
|
|
/// grants usually are adjacent). Returns false if the kernel is out of memory.
|
|
fn grow(min_bytes: usize) bool {
|
|
const bytes = alignUp(@max(min_bytes, chunk), page_size);
|
|
const ret = sys.mmap(bytes, sys.PROT_READ | sys.PROT_WRITE);
|
|
if (sys.mmapFailed(ret)) return false;
|
|
|
|
const block: *Block = @ptrFromInt(ret);
|
|
block.size = bytes;
|
|
insertFree(block); // coalesces if this grant is adjacent to a prior one
|
|
return true;
|
|
}
|
|
|
|
/// Insert a block into the address-ordered free list, coalescing with the
|
|
/// physically adjacent free blocks on either side.
|
|
fn insertFree(block: *Block) void {
|
|
var prev: ?*Block = null;
|
|
var cur = free_list;
|
|
while (cur) |c| : (cur = c.next) {
|
|
if (@intFromPtr(c) > @intFromPtr(block)) break;
|
|
prev = c;
|
|
}
|
|
|
|
block.next = cur;
|
|
if (prev) |p| p.next = block else free_list = block;
|
|
|
|
// Merge forward into `cur` if they're contiguous.
|
|
if (cur) |c| {
|
|
if (@intFromPtr(block) + block.size == @intFromPtr(c)) {
|
|
block.size += c.size;
|
|
block.next = c.next;
|
|
}
|
|
}
|
|
// Merge `prev` forward into `block` if they're contiguous.
|
|
if (prev) |p| {
|
|
if (@intFromPtr(p) + p.size == @intFromPtr(block)) {
|
|
p.size += block.size;
|
|
p.next = block.next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Allocate `len` bytes (16-byte aligned), or null if out of memory.
|
|
fn rawAlloc(len: usize) ?[*]u8 {
|
|
const need = alignUp(header_size + len, 16);
|
|
|
|
var attempts: u32 = 0;
|
|
while (attempts < 2) : (attempts += 1) {
|
|
var prev: ?*Block = null;
|
|
var cur = free_list;
|
|
while (cur) |block| : ({
|
|
prev = block;
|
|
cur = block.next;
|
|
}) {
|
|
if (block.size < need) continue;
|
|
|
|
if (block.size >= need + min_block) {
|
|
// Split: carve `need` off the front, leave the rest free.
|
|
const rest: *Block = @ptrFromInt(@intFromPtr(block) + need);
|
|
rest.size = block.size - need;
|
|
rest.next = block.next;
|
|
if (prev) |p| p.next = rest else free_list = rest;
|
|
block.size = need;
|
|
} else {
|
|
// Take the whole block.
|
|
if (prev) |p| p.next = block.next else free_list = block.next;
|
|
}
|
|
return payloadOf(block);
|
|
}
|
|
|
|
// Nothing fit: grow and try once more.
|
|
if (!grow(need)) return null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn rawFree(ptr: [*]u8) void {
|
|
const block: *Block = @ptrFromInt(@intFromPtr(ptr) - header_size);
|
|
insertFree(block);
|
|
}
|
|
|
|
// --- C ABI: the global implicit heap ---------------------------------------
|
|
// `extern "C"` symbols so future C code links the same malloc/free directly.
|
|
|
|
export fn malloc(size: usize) callconv(.c) ?*anyopaque {
|
|
if (size == 0) return null;
|
|
const p = rawAlloc(size) orelse return null;
|
|
return @ptrCast(p);
|
|
}
|
|
|
|
export fn free(ptr: ?*anyopaque) callconv(.c) void {
|
|
const p = ptr orelse return;
|
|
rawFree(@ptrCast(p));
|
|
}
|
|
|
|
export fn calloc(nmemb: usize, size: usize) callconv(.c) ?*anyopaque {
|
|
const total = std.math.mul(usize, nmemb, size) catch return null; // overflow-safe
|
|
if (total == 0) return null;
|
|
const p = rawAlloc(total) orelse return null;
|
|
@memset(p[0..total], 0);
|
|
return @ptrCast(p);
|
|
}
|
|
|
|
export fn realloc(ptr: ?*anyopaque, size: usize) callconv(.c) ?*anyopaque {
|
|
const p = ptr orelse return malloc(size);
|
|
if (size == 0) {
|
|
rawFree(@ptrCast(p));
|
|
return null;
|
|
}
|
|
const block: *Block = @ptrFromInt(@intFromPtr(p) - header_size);
|
|
const old_payload = block.size - header_size;
|
|
if (size <= old_payload) return p; // shrink/same: keep the block
|
|
const np = rawAlloc(size) orelse return null; // grow: alloc + copy + free
|
|
@memcpy(np[0..old_payload], @as([*]u8, @ptrCast(p))[0..old_payload]);
|
|
rawFree(@ptrCast(p));
|
|
return @ptrCast(np);
|
|
}
|
|
|
|
// --- std.mem.Allocator interface (same free list) --------------------------
|
|
|
|
pub fn allocator() std.mem.Allocator {
|
|
return .{ .ptr = undefined, .vtable = &vtable };
|
|
}
|
|
|
|
const vtable = std.mem.Allocator.VTable{
|
|
.alloc = allocImpl,
|
|
.resize = resizeImpl,
|
|
.remap = remapImpl,
|
|
.free = freeImpl,
|
|
};
|
|
|
|
fn allocImpl(_: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize) ?[*]u8 {
|
|
if (alignment.toByteUnits() > 16) return null; // blocks are 16-byte aligned
|
|
return rawAlloc(len);
|
|
}
|
|
|
|
fn resizeImpl(_: *anyopaque, memory: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
|
|
// In-place iff the new payload still fits the current block.
|
|
const block: *Block = @ptrFromInt(@intFromPtr(memory.ptr) - header_size);
|
|
return new_len + header_size <= block.size;
|
|
}
|
|
|
|
fn remapImpl(_: *anyopaque, _: []u8, _: std.mem.Alignment, _: usize, _: usize) ?[*]u8 {
|
|
return null;
|
|
}
|
|
|
|
fn freeImpl(_: *anyopaque, memory: []u8, _: std.mem.Alignment, _: usize) void {
|
|
rawFree(memory.ptr);
|
|
}
|