M6: user runtime library rt + C-convention heap
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.
This commit is contained in:
@@ -48,6 +48,43 @@ fn timestamp(b: *std.Build) []const u8 {
|
||||
});
|
||||
}
|
||||
|
||||
/// Build one user-space binary the same way for every program (init, and later
|
||||
/// the VFS server + drivers): freestanding, ReleaseSmall, `.large` code model
|
||||
/// (the image base is above 4 GiB — smaller models emit 32-bit relocations that
|
||||
/// can't reach), linked against the `rt` runtime library with the shared user
|
||||
/// link script. Pinned to LLVM + LLD so the script's PHDRS (segment permissions)
|
||||
/// are authoritative — the kernel's W^X user-ELF loader requires exact perms.
|
||||
fn addUserBinary(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
rt_mod: *std.Build.Module,
|
||||
name: []const u8,
|
||||
root: []const u8,
|
||||
) *std.Build.Step.Compile {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = name,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path(root),
|
||||
.target = target,
|
||||
.optimize = .ReleaseSmall,
|
||||
.code_model = .large,
|
||||
.single_threaded = true,
|
||||
.sanitize_c = .off,
|
||||
.stack_check = false,
|
||||
.stack_protector = false,
|
||||
.imports = &.{
|
||||
.{ .name = "rt", .module = rt_mod },
|
||||
},
|
||||
}),
|
||||
});
|
||||
exe.setLinkerScript(b.path("lib/user.ld"));
|
||||
exe.entry = .{ .symbol_name = "_start" };
|
||||
exe.image_base = 0x7000_0000_0000;
|
||||
exe.use_llvm = true;
|
||||
exe.use_lld = true;
|
||||
return exe;
|
||||
}
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
ensureZigVersion();
|
||||
|
||||
@@ -98,6 +135,18 @@ pub fn build(b: *std.Build) void {
|
||||
},
|
||||
});
|
||||
|
||||
// The user-space runtime library (a nascent libc): syscall wrappers, the
|
||||
// C-convention heap, IPC helpers, the process start shim. Compiled into every
|
||||
// user binary (see addUserBinary), so it inherits each exe's `.large` code
|
||||
// model — do NOT set a target/code_model here. It imports `danos` for the
|
||||
// shared Syscall numbers.
|
||||
const rt_mod = b.addModule("rt", .{
|
||||
.root_source_file = b.path("lib/rt.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "danos", .module = mod },
|
||||
},
|
||||
});
|
||||
|
||||
// Compile-time config the kernel reads as `@import("build_options")`. The
|
||||
// QEMU test harness sets -Dtest-case=<name> to run one self-test at boot.
|
||||
const test_case = b.option([]const u8, "test-case", "Kernel self-test case to run at boot (see src/kernel/tests.zig)");
|
||||
@@ -151,33 +200,10 @@ pub fn build(b: *std.Build) void {
|
||||
b.installArtifact(exe);
|
||||
|
||||
// --- /sbin/init: the first user-space program ---
|
||||
// Its own tiny freestanding binary, linked at a fixed address inside the
|
||||
// kernel's user region (process.zig) and started in ring 3 by the kernel's
|
||||
// user-ELF loader. `.large` because the image base is above 4 GiB — small/
|
||||
// medium code models emit 32-bit absolute relocations that can't reach.
|
||||
// Pinned to ReleaseSmall: the user region gives it a 2 MiB budget and its
|
||||
// size has no reason to track the kernel's optimize mode.
|
||||
const init_exe = b.addExecutable(.{
|
||||
.name = "init",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("sbin/init.zig"),
|
||||
.target = kernel_target,
|
||||
.optimize = .ReleaseSmall,
|
||||
.code_model = .large,
|
||||
.single_threaded = true,
|
||||
.sanitize_c = .off,
|
||||
.stack_check = false,
|
||||
.stack_protector = false,
|
||||
}),
|
||||
});
|
||||
init_exe.setLinkerScript(b.path("sbin/linker.ld"));
|
||||
init_exe.entry = .{ .symbol_name = "_start" };
|
||||
init_exe.image_base = 0x7000_0000_0000;
|
||||
// The self-hosted linker ignores the script's PHDRS (segment permissions),
|
||||
// which the kernel's W^X user-ELF loader requires (code must be R+X). Pin to
|
||||
// LLVM + LLD so the script is authoritative.
|
||||
init_exe.use_llvm = true;
|
||||
init_exe.use_lld = true;
|
||||
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
|
||||
// linked into the kernel's user region against the `rt` runtime library, and
|
||||
// started in ring 3 by the kernel's user-ELF loader.
|
||||
const init_exe = addUserBinary(b, kernel_target, rt_mod, "init", "sbin/init.zig");
|
||||
b.installArtifact(init_exe);
|
||||
|
||||
// Boot methods live in src/boot/, one per way of getting the kernel running.
|
||||
|
||||
Reference in New Issue
Block a user