Compare commits

...

6 Commits

Author SHA1 Message Date
f8182bd386
init: Use getThreadId() instead of relying on a fixed PID 2025-02-22 23:01:48 +01:00
b4f25a2e1f
system+init: Move userspace virtual memory code to the system library 2025-02-22 23:01:32 +01:00
6e97a89f22
core: Reserve multiboot tags and module memory + load all modules as independent threads instead of just init
Only init is started though, other modules are left in a dormant state so init can do whatever it wants and adjust them according to its needs before running them.
2025-02-22 23:00:53 +01:00
d029d3cf46
core+system: Add a bunch of syscalls related to thread creation 2025-02-22 22:58:56 +01:00
c6df22ee39
core: Allow for non-const pointers in findMultibootTags() 2025-02-22 22:57:33 +01:00
c268c48c8e
system: Fix syscall return value constraint
The previous constraint was making the compiler discard the return value when building in release mode.
2025-02-22 22:57:10 +01:00
12 changed files with 227 additions and 86 deletions

View File

@ -234,13 +234,13 @@ pub fn allocAndMap(allocator: *pmm.FrameAllocator, space: AddressSpace, base: u6
}
}
fn mapPhysicalMemory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, space: AddressSpace, base: usize, flags: u32) !void {
fn mapPhysicalMemory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, space: AddressSpace, base: usize, start_addr: usize, flags: u32) !void {
const address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap;
const address_space_pages = address_space_size / HUGE_PAGE_SIZE;
var index: usize = 0;
while (index < address_space_pages) : (index += 1) {
try map(allocator, space, 0, base + index * HUGE_PAGE_SIZE, pmm.PhysFrame{ .address = index * HUGE_PAGE_SIZE }, flags, true);
try map(allocator, space, base, start_addr + index * HUGE_PAGE_SIZE, pmm.PhysFrame{ .address = index * HUGE_PAGE_SIZE }, flags, true);
}
}
@ -272,12 +272,15 @@ fn setUpKernelPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multi
const space = AddressSpace.create(table, 0);
try lockPageTable(allocator, space);
try mapPhysicalMemory(allocator, tag, space, PHYSICAL_MAPPING_BASE, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.Global));
try mapPhysicalMemory(allocator, tag, space, 0, PHYSICAL_MAPPING_BASE, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.Global));
return table;
}
fn setUpInitialUserPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, kernel_table: *PageTable, user_table: *PageTable) !usize {
pub fn setUpInitialUserPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, user_table: *PageTable) !usize {
const kernel_space = AddressSpace.create(readPageTable(), PHYSICAL_MAPPING_BASE);
const kernel_table = kernel_space.table;
const physical_address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap;
user_table.* = std.mem.zeroes(PageTable);
@ -290,21 +293,18 @@ fn setUpInitialUserPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.
const space = AddressSpace.create(.{ .address = @intFromPtr(user_table) }, 0);
try mapPhysicalMemory(allocator, tag, space, user_physical_address_base, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.User));
try mapPhysicalMemory(allocator, tag, space, PHYSICAL_MAPPING_BASE, user_physical_address_base, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.User));
return user_physical_address_base;
}
pub fn createInitialMappings(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, user_table: *PageTable) !usize {
pub fn createInitialMapping(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t) !void {
const frame = try setUpKernelPageDirectory(allocator, tag);
const space = AddressSpace.create(frame, 0);
const base = try setUpInitialUserPageDirectory(allocator, tag, space.table, user_table);
setPageTable(space.phys);
allocator.bitmap.location = @ptrFromInt(@as(usize, PHYSICAL_MAPPING_BASE) + @intFromPtr(allocator.bitmap.location));
return base;
}
pub fn readPageTable() pmm.PhysFrame {

View File

@ -13,11 +13,49 @@ const elf = @import("elf.zig");
const MultibootInfo = [*c]u8;
const Context = struct {
allocator: *pmm.FrameAllocator,
space: vmm.AddressSpace,
regs: *platform.Registers,
};
fn adjustAddressToPageBoundary(address: *usize, size: *usize) void {
const diff = address.* % platform.PAGE_SIZE;
address.* -= diff;
size.* += diff;
}
fn reserveMultibootMemory(allocator: *pmm.FrameAllocator, info: MultibootInfo) !void {
const info_tag: *easyboot.multiboot_info_t = @alignCast(@ptrCast(info));
var address: usize = @intFromPtr(info);
var size: usize = info_tag.total_size;
adjustAddressToPageBoundary(&address, &size);
debug.print("Locking multiboot memory at {x}, {d} bytes\n", .{ address, size });
try pmm.lockFrames(allocator, address, try std.math.divCeil(usize, size, platform.PAGE_SIZE));
const Context = struct {
allocator: *pmm.FrameAllocator,
};
var ctx = Context{ .allocator = allocator };
multiboot.findMultibootTags(easyboot.multiboot_tag_module_t, @ptrCast(info), struct {
fn reserveMemory(mod: *easyboot.multiboot_tag_module_t, context: *const Context) !void {
var mod_address: usize = mod.mod_start;
var mod_size: usize = mod.mod_end - mod.mod_start;
adjustAddressToPageBoundary(&mod_address, &mod_size);
debug.print("Locking memory for module {s} at address {x}, {d} bytes\n", .{ mod.string(), mod_address, mod_size });
try pmm.lockFrames(context.allocator, mod_address, try std.math.divCeil(usize, mod_size, platform.PAGE_SIZE));
}
fn handler(mod: *easyboot.multiboot_tag_module_t, context: *const anyopaque) void {
reserveMemory(mod, @alignCast(@ptrCast(context))) catch |err| {
debug.print("Error while reserving multiboot memory {s}: {}\n", .{ mod.string(), err });
while (true) {}
};
}
}.handler, &ctx);
}
export fn _start(magic: u32, info: MultibootInfo) callconv(.C) noreturn {
interrupts.disableInterrupts();
@ -42,64 +80,66 @@ export fn _start(magic: u32, info: MultibootInfo) callconv(.C) noreturn {
while (true) {}
};
var table: vmm.PageTable = std.mem.zeroes(vmm.PageTable);
const base: usize = vmm.createInitialMappings(&allocator, tag, &table) catch |err| {
reserveMultibootMemory(&allocator, info) catch |err| {
debug.print("Error while reserving multiboot memory: {}\n", .{err});
while (true) {}
};
vmm.createInitialMapping(&allocator, tag) catch |err| {
debug.print("Error while creating initial mappings: {}\n", .{err});
while (true) {}
};
debug.print("Physical memory base mapping for init: {x}\n", .{base});
const frame = pmm.allocFrame(&allocator) catch |err| {
debug.print("Error while creating frame for user page directory: {}\n", .{err});
while (true) {}
};
// At this point the physical address space is already mapped into kernel virtual memory.
const space = vmm.AddressSpace.create(frame, vmm.PHYSICAL_MAPPING_BASE);
space.table.* = table;
cpu.setupCore(&allocator) catch |err| {
debug.print("Error while setting up core-specific scheduler structures: {}\n", .{err});
while (true) {}
};
const init = thread.createThreadControlBlock(&allocator) catch |err| {
debug.print("Error while creating thread control block for init: {}\n", .{err});
while (true) {}
const Context = struct {
allocator: *pmm.FrameAllocator,
mmap: *easyboot.multiboot_tag_mmap_t,
init: ?*thread.ThreadControlBlock,
};
init.address_space = space;
init.user_priority = 255;
init.tokens = @intFromEnum(system.kernel.Token.Root);
thread.arch.initUserRegisters(&init.regs);
thread.arch.setArguments(&init.regs, base, space.phys.address);
const ctx = Context{ .allocator = &allocator, .space = space, .regs = &init.regs };
var ctx = Context{ .allocator = &allocator, .mmap = tag, .init = null };
multiboot.findMultibootTags(easyboot.multiboot_tag_module_t, @ptrCast(info), struct {
fn handler(mod: *easyboot.multiboot_tag_module_t, c: *const anyopaque) void {
const context: *const Context = @alignCast(@ptrCast(c));
const name = "init";
if (std.mem.eql(u8, mod.string()[0..name.len], name[0..name.len])) {
const phys_frame = pmm.PhysFrame{ .address = mod.mod_start };
debug.print("Loading init from module at address {x}, virtual {x}\n", .{ mod.mod_start, phys_frame.virtualAddress(vmm.PHYSICAL_MAPPING_BASE) });
const entry = elf.loadElf(context.allocator, context.space, pmm.PhysFrame{ .address = mod.mod_start }) catch |err| {
debug.print("Error while loading ELF file for init: {}\n", .{err});
while (true) {}
};
thread.arch.setAddress(context.regs, entry);
}
fn loadModule(mod: *easyboot.multiboot_tag_module_t, context: *Context) !void {
const frame = try pmm.allocFrame(context.allocator);
const space = vmm.AddressSpace.create(frame, vmm.PHYSICAL_MAPPING_BASE);
const base = try vmm.setUpInitialUserPageDirectory(context.allocator, context.mmap, space.table);
const module = try thread.createThreadControlBlock(context.allocator);
module.address_space = space;
module.user_priority = 255;
module.tokens = @intFromEnum(system.kernel.Token.Root);
thread.arch.initUserRegisters(&module.regs);
thread.arch.setArguments(&module.regs, base, space.phys.address);
const mod_start = pmm.PhysFrame{ .address = mod.mod_start };
debug.print("Loading module {s} at address {x}, virtual {x}\n", .{ mod.string(), mod.mod_start, mod_start.virtualAddress(vmm.PHYSICAL_MAPPING_BASE) });
const entry = try elf.loadElf(context.allocator, space, mod_start);
thread.arch.setAddress(&module.regs, entry);
const default_stack_size = 0x40000; // 256 KiB.
const stack = try elf.allocateStack(context.allocator, space, base - platform.PAGE_SIZE, default_stack_size);
thread.arch.setStack(&module.regs, stack);
const init_name = "init";
if (std.mem.eql(u8, init_name[0..init_name.len], mod.string()[0..init_name.len])) context.init = module;
}
fn handler(mod: *easyboot.multiboot_tag_module_t, context: *anyopaque) void {
loadModule(mod, @alignCast(@ptrCast(context))) catch |err| {
debug.print("Error while loading module file {s}: {}\n", .{ mod.string(), err });
while (true) {}
};
}
}.handler, &ctx);
const default_stack_size = 0x80000; // 512 KiB.
const stack = elf.allocateStack(&allocator, space, base - platform.PAGE_SIZE, default_stack_size) catch |err| {
debug.print("Error while creating stack for init: {}\n", .{err});
while (true) {}
};
thread.arch.setStack(&init.regs, stack);
pmm.setGlobalAllocator(&allocator) catch |err| {
debug.print("Error while setting up global frame allocator: {}\n", .{err});
while (true) {}
@ -107,7 +147,12 @@ export fn _start(magic: u32, info: MultibootInfo) callconv(.C) noreturn {
platform.platformEndInit();
thread.enterThread(init);
if (ctx.init) |init| {
thread.enterThread(init);
} else {
debug.print("Error: no init module loaded!\n", .{});
while (true) {}
}
}
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {

View File

@ -68,7 +68,7 @@ pub fn findMultibootTag(comptime Type: type, info: MultibootInfo) ?*Type {
}
/// Find every multiboot tag of the given type.
pub fn findMultibootTags(comptime Type: type, info: MultibootInfo, callback: *const fn (tag: *Type, ctx: *const anyopaque) void, ctx: *const anyopaque) void {
pub fn findMultibootTags(comptime Type: type, info: MultibootInfo, callback: *const fn (tag: *Type, ctx: *anyopaque) void, ctx: *anyopaque) void {
const mb_tag: *easyboot.multiboot_info_t = @alignCast(@ptrCast(info));
const mb_size = mb_tag.total_size;

View File

@ -40,7 +40,7 @@ pub fn lockFrame(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerr
pub fn setAddressSpace(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.VirtualMemory)) return error.NotAuthorized;
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;

View File

@ -41,7 +41,7 @@ pub fn sleep(regs: *platform.Registers, args: *sys.Arguments, _: *isize) anyerro
pub fn setEventQueue(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.EventQueue)) return error.NotAuthorized;
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
if (target.event_queue) |_| return error.ThreadQueueAlreadySet;
@ -54,3 +54,55 @@ pub fn setEventQueue(_: *platform.Registers, args: *sys.Arguments, _: *isize) an
target.event_queue = RingBuffer.init(data, platform.PAGE_SIZE, true);
}
pub fn createThread(_: *platform.Registers, _: *sys.Arguments, result: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const allocator = pmm.lockGlobalAllocator();
defer pmm.unlockGlobalAllocator();
const child = try thread.createThreadControlBlock(allocator);
thread.arch.initUserRegisters(&child.regs);
result.* = @bitCast(child.id);
}
pub fn setThreadEntry(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
thread.arch.setAddress(&target.regs, args.arg1);
}
pub fn setThreadArguments(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
thread.arch.setArguments(&target.regs, args.arg1, args.arg2);
}
pub fn setThreadStack(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
thread.arch.setStack(&target.regs, args.arg1);
}
pub fn startThread(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
const core = cpu.thisCore();
if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
if (target.state != .Inactive) return;
thread.reviveThread(core, target);
}
pub fn getThreadId(_: *platform.Registers, _: *sys.Arguments, result: *isize) anyerror!void {
const core = cpu.thisCore();
result.* = @bitCast(core.current_thread.id);
}

View File

@ -29,6 +29,13 @@ const syscalls = [_]SystemCall{
sched.sleep,
sched.setEventQueue,
tokens.setTokens,
mem.setAddressSpace,
sched.createThread,
sched.setThreadEntry,
sched.setThreadArguments,
sched.setThreadStack,
sched.startThread,
sched.getThreadId,
};
pub fn invokeSyscall(number: usize, frame: *platform.Registers, args: *Arguments, retval: *isize) void {

View File

@ -1,20 +1,18 @@
const system = @import("system");
const vm = @import("arch/vm.zig");
const vm = system.vm;
const syscalls = system.syscalls;
const buffer = system.ring_buffer;
// FIXME: Make arch-specific.
const PAGE_SIZE = 4096;
const SELF_PID = 1;
fn setupKernelRingBuffer(base: u64) !buffer.RingBuffer {
const phys = vm.PhysFrame{ .address = try syscalls.allocFrame() };
const data: [*]u8 = @ptrCast(phys.virtualPointer(u8, base));
try syscalls.setEventQueue(SELF_PID, phys.address);
try syscalls.setEventQueue(syscalls.getThreadId(), phys.address);
return buffer.RingBuffer.init(data, PAGE_SIZE, true);
}
@ -23,9 +21,8 @@ fn setTokens() void {
var tokens: u64 = 0;
tokens |= @intFromEnum(system.kernel.Token.Root);
tokens |= @intFromEnum(system.kernel.Token.PhysicalMemory);
tokens |= @intFromEnum(system.kernel.Token.EventQueue);
tokens |= @intFromEnum(system.kernel.Token.VirtualMemory);
syscalls.setTokens(SELF_PID, tokens) catch {};
tokens |= @intFromEnum(system.kernel.Token.CreateProcess);
syscalls.setTokens(syscalls.getThreadId(), tokens) catch {};
}
export fn _start(base: u64, address: u64) callconv(.C) noreturn {

View File

@ -1,7 +1,5 @@
const std = @import("std");
const system = @import("system");
const syscalls = system.syscalls;
const syscalls = @import("../../syscalls.zig");
const MapError = error{
MemoryAlreadyInUse,

View File

@ -7,17 +7,22 @@ pub const SystemCall = enum(u64) {
SetPriority, // requires Token.ThreadPriority
GetPriority,
Sleep,
SetEventQueue, // requires Token.EventQueue
SetEventQueue, // requires Token.CreateProcess
SetTokens, // requires Token.Root
SetAddressSpace, // requires Token.VirtualMemory
SetAddressSpace, // requires Token.CreateProcess
CreateThread, // requires Token.CreateProcess
SetThreadEntry, // requires Token.CreateProcess
SetThreadArguments, // requires Token.CreateProcess
SetThreadStack, // requires Token.CreateProcess
StartThread, // requires Token.CreateProcess
GetThreadId,
};
pub const Token = enum(u64) {
Root = 1 << 0,
PhysicalMemory = 1 << 1,
ThreadPriority = 1 << 2,
EventQueue = 1 << 3,
VirtualMemory = 1 << 4,
CreateProcess = 1 << 3,
};
pub const SystemError = error{

View File

@ -1,58 +1,94 @@
const kernel = @import("kernel.zig");
const target = @import("builtin").target;
fn syscall(num: kernel.SystemCall, arg0: u64, arg1: u64) i64 {
fn syscall(num: kernel.SystemCall, arg0: u64, arg1: u64, arg2: u64) i64 {
return switch (target.cpu.arch) {
.x86_64 => asm volatile ("int $66"
: [result] "=r" (-> i64),
: [result] "={rax}" (-> i64),
: [num] "{rax}" (@intFromEnum(num)),
[arg0] "{rdi}" (arg0),
[arg1] "{rsi}" (arg1),
[arg2] "{rdx}" (arg2),
),
else => @compileError("unsupported architecture"),
};
}
pub fn print(arg: u64) void {
_ = syscall(.Print, arg, 0);
_ = syscall(.Print, arg, 0, 0);
}
pub fn allocFrame() !usize {
const retval = syscall(.AllocFrame, 0, 0);
const retval = syscall(.AllocFrame, 0, 0, 0);
if (retval < 0) return error.OutOfMemory;
return @bitCast(retval);
}
pub fn lockFrame(address: u64) void {
_ = syscall(.LockFrame, address, 0);
_ = syscall(.LockFrame, address, 0, 0);
}
pub fn freeFrame(address: u64) void {
_ = syscall(.FreeFrame, address, 0);
_ = syscall(.FreeFrame, address, 0, 0);
}
pub fn yield() void {
_ = syscall(.Yield, 0, 0);
_ = syscall(.Yield, 0, 0, 0);
}
pub fn setPriority(priority: u8) void {
_ = syscall(.SetPriority, priority, 0);
_ = syscall(.SetPriority, priority, 0, 0);
}
pub fn getPriority() u8 {
return @truncate(@as(u64, @bitCast(syscall(.GetPriority, 0, 0))));
return @truncate(@as(u64, @bitCast(syscall(.GetPriority, 0, 0, 0))));
}
pub fn sleep(ms: u64) void {
_ = syscall(.Sleep, ms, 0);
_ = syscall(.Sleep, ms, 0, 0);
}
pub fn setEventQueue(pid: u64, address: u64) !void {
const retval = syscall(.SetEventQueue, pid, address);
const retval = syscall(.SetEventQueue, pid, address, 0);
if (retval < 0) return error.NoSuchThread;
}
pub fn setTokens(pid: u64, tokens: u64) !void {
const retval = syscall(.SetTokens, pid, tokens);
const retval = syscall(.SetTokens, pid, tokens, 0);
if (retval < 0) return error.NoSuchThread;
}
pub fn setAddressSpace(pid: u64, address: u64) !void {
const retval = syscall(.SetAddressSpace, pid, address, 0);
if (retval < 0) return error.NoSuchThread;
}
pub fn createThread() !u64 {
const retval = syscall(.CreateThread, 0, 0, 0);
if (retval < 0) return error.NoSuchThread;
return @bitCast(retval);
}
pub fn setThreadEntry(pid: u64, entry: u64) !void {
const retval = syscall(.SetThreadEntry, pid, entry, 0);
if (retval < 0) return error.NoSuchThread;
}
pub fn setThreadArguments(pid: u64, arg0: u64, arg1: u64) !void {
const retval = syscall(.SetThreadArguments, pid, arg0, arg1);
if (retval < 0) return error.NoSuchThread;
}
pub fn setThreadStack(pid: u64, stack: u64) !void {
const retval = syscall(.SetThreadStack, pid, stack, 0);
if (retval < 0) return error.NoSuchThread;
}
pub fn startThread(pid: u64) !void {
const retval = syscall(.StartThread, pid, 0, 0);
if (retval < 0) return error.NoSuchThread;
}
pub fn getThreadId() u64 {
return @bitCast(syscall(.GetThreadId, 0, 0, 0));
}

View File

@ -1,3 +1,4 @@
pub const kernel = @import("kernel.zig");
pub const ring_buffer = @import("ring_buffer.zig");
pub const syscalls = @import("syscalls.zig");
pub const vm = @import("arch/vm.zig");