WIP: test objects

This commit is contained in:
2025-03-27 23:37:11 +02:00
parent d25e1c0346
commit ff2932d088
9 changed files with 383 additions and 117 deletions
+19 -1
View File
@@ -1,3 +1,21 @@
pub const SyscallNumber = enum(usize) { SYS_debug_write = 1, SYS_exit = 2, _ };
pub const SyscallNumber = enum(usize) {
SYS_send = 1,
SYS_recv = 2,
SYS_sendrecv = 3,
_,
};
pub const ProcessObjectAction = enum(usize) {
ZO_process_exit = 1,
_,
};
pub const PhysicalMemoryObjectAction = enum(usize) {
ZO_physical_memory_allocate = 1,
ZO_physical_memory_free = 2,
_,
};
pub const MAX_SYSCALL: usize = 32;
pub const Handle = enum(u32) { _ };
+1 -4
View File
@@ -92,10 +92,7 @@ export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
switch (esr.as_enum()) {
.svc => {
const func = frame.xN[8];
const args = frame.xN[0..6];
const result = syscall.syscall_handler(func, args);
frame.xN[0] = result;
syscall.syscall_handler(frame.xN[8], frame.xN[0..6]);
return;
},
else => {},
+103
View File
@@ -0,0 +1,103 @@
const mem = @import("mem.zig");
const kernel = @import("kernel.zig");
const thread = @import("thread.zig");
const abi = @import("abi");
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
const log = kernel.log;
const Thread = thread.Thread;
pub const PhysicalMemoryObject = struct {
fn send(self: *const PhysicalMemoryObject, caller: *Thread, message: *const [5]usize) usize {
_ = self;
_ = caller;
_ = message;
@panic("TODO: physical memory object messaging");
}
fn sendrecv(self: *const PhysicalMemoryObject, caller: *Thread, message: *[5]usize) usize {
switch (@as(abi.PhysicalMemoryObjectAction, @enumFromInt(message[0]))) {
.ZO_physical_memory_allocate => {
// TODO somehow track ownership of the memory by the process
const pages = mem.phys.alloc_pages(message[1]) orelse {
return 1;
};
log.info("{*}: allocated {} pages: 0x{x}", .{caller, message[1], pages.raw});
message[0] = pages.raw;
return 0;
},
.ZO_physical_memory_free => {
@panic("TODO: ZO_physical_memory_free");
},
else => {
@panic("TODO: invalid message to Physical Memory Object");
}
}
_ = self;
}
};
pub const ProcessObject = struct {
inner: *Thread,
fn send(self: *const ProcessObject, caller: *Thread, message: *const [5]usize) usize {
_ = self;
// TODO define this in IDL/ABI
switch (@as(abi.ProcessObjectAction, @enumFromInt(message[0]))) {
.ZO_process_exit => {
log.info("{*} exited with code 0x{x} ({})", .{caller, message[1], message[1]});
Thread.exit_current();
},
else => {
@panic("TODO: invalid message to Process Object");
}
}
}
fn sendrecv(self: *const ProcessObject, caller: *Thread, message: *[5]usize) usize {
_ = self;
_ = caller;
_ = message;
@panic("TODO: ProcessObject sendrecv()");
}
};
pub const DebugObject = struct {
fn send(self: *const DebugObject, caller: *Thread, message: *const [5]usize) usize {
_ = self;
// Debug has only one message
const text = @as([*]u8, @ptrFromInt(message[0]))[0..message[1]];
log.info("{*}: {s}", .{ caller, text });
return 0;
}
fn sendrecv(self: *const DebugObject, caller: *Thread, message: *[5]usize) usize {
_ = self;
_ = caller;
_ = message;
@panic("TODO: DebugObject sendrecv()");
}
};
pub const Object = union(enum) {
physical_memory: PhysicalMemoryObject,
process: ProcessObject,
debug: DebugObject,
// TODO userspace "object" can be placed here
pub fn send(self: Object, caller: *Thread, message: *const [5]usize) usize {
return switch (self) {
.physical_memory => |physical_memory| physical_memory.send(caller, message),
.process => |process| process.send(caller, message),
.debug => |debug| debug.send(caller, message),
};
}
pub fn sendrecv(self: Object, caller: *Thread, message: *[5]usize) usize {
return switch (self) {
.physical_memory => |physical_memory| physical_memory.sendrecv(caller, message),
.process => |process| process.sendrecv(caller, message),
.debug => |debug| debug.sendrecv(caller, message),
};
}
};
+26 -14
View File
@@ -5,39 +5,51 @@ const thread = kernel.thread;
const Thread = thread.Thread;
const log = kernel.log;
pub const SyscallFn = *const fn (*Thread, []usize) usize;
pub const SyscallFn = *const fn (*Thread, *[6]usize) void;
pub const syscall_table: [abi.MAX_SYSCALL]?SyscallFn = make_syscall_table();
fn make_syscall_table() [abi.MAX_SYSCALL]?SyscallFn {
const SC = abi.SyscallNumber;
var array = [_]?SyscallFn{undefined} ** abi.MAX_SYSCALL;
array[@intFromEnum(SC.SYS_debug_write)] = sys_debug_write;
array[@intFromEnum(SC.SYS_exit)] = sys_exit;
array[@intFromEnum(SC.SYS_send)] = sys_send;
array[@intFromEnum(SC.SYS_recv)] = sys_recv;
array[@intFromEnum(SC.SYS_sendrecv)] = sys_sendrecv;
return array;
}
fn sys_exit(cthread: *Thread, args: []usize) usize {
log.info("{*} exits with code {}", .{ cthread, args[0] });
Thread.exit_current();
fn sys_send(cthread: *Thread, frame: *[6]usize) void {
const handle: abi.Handle = @enumFromInt(frame[0]);
const object = cthread.handle(handle) orelse {
@panic("TODO: userspace invoked non-existent handle");
};
frame[0] = object.send(cthread, frame[1..6]);
}
fn sys_debug_write(cthread: *Thread, args: []usize) usize {
const message = @as([*]const u8, @ptrFromInt(args[0]))[0..args[1]];
log.debug("{*}: {s}", .{ cthread, message });
return 0;
fn sys_recv(cthread: *Thread, frame: *[6]usize) void {
_ = cthread;
_ = frame;
@panic("TODO: SYS_recv()");
}
fn sys_undefined_syscall(cthread: *Thread, args: []usize) usize {
_ = args;
fn sys_sendrecv(cthread: *Thread, frame: *[6]usize) void {
const handle: abi.Handle = @enumFromInt(frame[0]);
const object = cthread.handle(handle) orelse {
@panic("TODO: userspace invoked non-existent handle");
};
frame[0] = object.sendrecv(cthread, frame[1..6]);
}
fn sys_undefined_syscall(cthread: *Thread, frame: *[6]usize) void {
_ = frame;
log.warn("{*} invoked an undefined syscall", .{cthread});
Thread.exit_current();
}
pub fn syscall_handler(func: usize, args: []usize) usize {
pub fn syscall_handler(func: usize, frame: *[6]usize) void {
const cthread = thread.Thread.current();
const handler = if (func >= abi.MAX_SYSCALL) sys_undefined_syscall //
else syscall_table[func] //
orelse sys_undefined_syscall;
return handler(cthread, args);
handler(cthread, frame);
}
+117 -82
View File
@@ -7,8 +7,12 @@ const arch = @import("kernel.zig").arch;
const log = @import("debug.zig").log;
const mem = @import("mem.zig");
const sync = @import("sync.zig");
const object = @import("object.zig");
const abi = @import("abi");
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
const Object = object.Object;
const Handle = abi.Handle;
// TODO: are kernel threads needed at all if we're doing a microkernel?
@@ -27,6 +31,113 @@ pub fn idle_function(arg: usize) callconv(.C) noreturn {
}
}
/// Represents a single execution thread.
pub const Thread = struct {
const MAX_HANDLES: usize = 64;
/// Arena.
allocator: *arena.Arena,
/// Architecture-specific task context.
arch_context: arch.Context,
/// Queue to which this thread belongs
queue: ?*Queue = null,
/// Next thread in the queue.
next: ?*Thread = null,
/// Previous thread in the queue.
prev: ?*Thread = null,
// TODO move to process
address_space: ?ProcessAddressSpace = null,
// TODO move to process
handle_table: [MAX_HANDLES]?Object = [_]?Object{null} ** MAX_HANDLES,
pub const Error = error{out_of_memory} || mem.vmm.AddressSpaceError;
/// Creates a new (kernel) thread with given `function` and `arg`ument.
pub fn create_kernel(a: *arena.Arena, function: *const KernelThreadFn, arg: usize) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.arch_context = arch.Context.kernel(function, arg),
};
return thread;
}
pub fn create_user(
a: *arena.Arena,
address_space: ProcessAddressSpace,
pc: usize,
sp: usize,
arg: usize,
) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.address_space = address_space,
.arch_context = arch.Context.user(&address_space, pc, sp, arg),
};
// "self" process object is granted to all processes
const process_object = Object{ .process = object.ProcessObject {
.inner = thread,
} };
_ = thread.grant(process_object);
return thread;
}
/// Enters the thread, does not return.
pub fn enter(self: *@This()) noreturn {
self.arch_context.enter();
}
/// Switches from `from` to `self` thread.
pub fn switch_from(self: *@This(), from: *@This()) void {
self.arch_context.switch_from(&from.arch_context);
}
pub fn grant(self: *@This(), obj: Object) Handle {
for (0..MAX_HANDLES) |i| {
if (self.handle_table[i] == null) {
self.handle_table[i] = obj;
return @enumFromInt(i + 1);
}
}
@panic("TODO: ran out of objects");
}
pub fn handle(self: *@This(), h: Handle) ?Object {
const index = @as(usize, @intFromEnum(h));
if (index > MAX_HANDLES or index == 0) {
return null;
}
return self.handle_table[index - 1];
}
pub fn dequeue(self: *@This()) void {
// TODO queueing information should be put under a lock for SMP to work properly
if (self.queue) |q| {
q.dequeue(self);
}
}
pub fn current() *@This() {
return Queue.t_this_cpu.?.current.?;
}
pub fn exit_current() noreturn {
// Mask IRQs so they don't break current thread's state
const mask = arch.IrqGuard.acquire();
defer mask.release();
const curr = Thread.current();
curr.dequeue();
yield();
@panic("This code should not be reachable");
}
};
/// Per-CPU thread queue structure.
pub const Queue = struct {
/// Idle task context. Used when there are no other tasks running.
@@ -152,86 +263,6 @@ pub const Queue = struct {
}
};
/// Represents a single execution thread.
pub const Thread = struct {
/// Arena.
allocator: *arena.Arena,
/// Architecture-specific task context.
arch_context: arch.Context,
/// Queue to which this thread belongs
queue: ?*Queue = null,
/// Next thread in the queue.
next: ?*Thread = null,
/// Previous thread in the queue.
prev: ?*Thread = null,
// TODO move to process
address_space: ?ProcessAddressSpace = null,
pub const Error = error{out_of_memory} || mem.vmm.AddressSpaceError;
/// Creates a new (kernel) thread with given `function` and `arg`ument.
pub fn create_kernel(a: *arena.Arena, function: *const KernelThreadFn, arg: usize) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.arch_context = arch.Context.kernel(function, arg),
};
return thread;
}
pub fn create_user(
a: *arena.Arena,
address_space: ProcessAddressSpace,
pc: usize,
sp: usize,
arg: usize,
) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.address_space = address_space,
.arch_context = arch.Context.user(&address_space, pc, sp, arg),
};
return thread;
}
/// Enters the thread, does not return.
pub fn enter(self: *@This()) noreturn {
self.arch_context.enter();
}
/// Switches from `from` to `self` thread.
pub fn switch_from(self: *@This(), from: *@This()) void {
self.arch_context.switch_from(&from.arch_context);
}
pub fn dequeue(self: *@This()) void {
// TODO queueing information should be put under a lock for SMP to work properly
if (self.queue) |q| {
q.dequeue(self);
}
}
pub fn current() *@This() {
return Queue.t_this_cpu.?.current.?;
}
pub fn exit_current() noreturn {
// Mask IRQs so they don't break current thread's state
const mask = arch.IrqGuard.acquire();
defer mask.release();
const curr = Thread.current();
curr.dequeue();
yield();
@panic("This code should not be reachable");
}
};
/// Helper data structure to represent kernel stacks in task contexts.
pub fn KStack(comptime SIZE: usize) type {
return extern struct {
@@ -292,6 +323,7 @@ pub fn yield() void {
pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Error!*Thread {
const L3 = mem.vmm.L3;
const CODE_BASE: usize = 0x200000;
var address_space = try ProcessAddressSpace.init(a);
errdefer {
@@ -302,7 +334,7 @@ pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Erro
const code_page_count = L3.page_count(code.len);
log.info("Code is {} pages", .{code_page_count});
var offset: usize = 0;
var address: usize = 0x200000;
var address: usize = CODE_BASE;
while (offset < code.len) {
const page_offset = address % L3.SIZE;
const amount = @min(L3.SIZE - page_offset, code.len - offset);
@@ -327,7 +359,10 @@ pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Erro
log.info("Enter with sp = 0x{x}", .{sp});
const thread = Thread.create_user(a, address_space, 0x200000, sp, 1234);
const thread = Thread.create_user(a, address_space, CODE_BASE, sp, 1234);
_ = thread.grant(Object { .physical_memory = object.PhysicalMemoryObject {} });
_ = thread.grant(Object { .debug = object.DebugObject {} });
return thread;
}
+7
View File
@@ -10,6 +10,13 @@ SECTIONS {
*(.rodata*)
}
.eh_frame_hdr : {
*(.eh_frame_hdr*)
}
.eh_frame : {
*(.eh_frame*)
}
.data : ALIGN(4K) {
*(.data*)
}
+41 -8
View File
@@ -1,22 +1,55 @@
const abi = @import("abi");
pub const syscall = struct {
pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize {
pub fn send(handle: u32, msg: *const [5]usize) usize {
return asm volatile ("svc #0"
: [result] "={x0}" (-> usize),
: [arg0] "{x0}" (arg0),
[func] "{x8}" (@intFromEnum(func)),
: [a0] "{x0}" (handle),
[a1] "{x1}" (msg[0]),
[a2] "{x2}" (msg[1]),
[a3] "{x3}" (msg[2]),
[a4] "{x4}" (msg[3]),
[a5] "{x5}" (msg[4]),
[func] "{x8}" (@intFromEnum(abi.SyscallNumber.SYS_send)),
: "memory"
);
}
pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize {
return asm volatile ("svc #0"
pub fn sendrecv(handle: u32, msg: *const[5]usize, buffer: *[5]usize) usize {
return asm volatile (
\\ svc #0
\\ stp x1, x2, [%[buf], #16 * 0]
\\ stp x3, x4, [%[buf], #16 * 1]
\\ str x5, [%[buf], #16 * 2]
: [result] "={x0}" (-> usize),
: [arg0] "{x0}" (arg0),
[arg1] "{x1}" (arg1),
[func] "{x8}" (@intFromEnum(func)),
: [a0] "{x0}" (handle),
[a1] "{x1}" (msg[0]),
[a2] "{x2}" (msg[1]),
[a3] "{x3}" (msg[2]),
[a4] "{x4}" (msg[3]),
[a5] "{x5}" (msg[4]),
[func] "{x8}" (@intFromEnum(abi.SyscallNumber.SYS_sendrecv)),
[buf] "r" (buffer),
: "memory"
);
}
// pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize {
// return asm volatile ("svc #0"
// : [result] "={x0}" (-> usize),
// : [arg0] "{x0}" (arg0),
// [func] "{x8}" (@intFromEnum(func)),
// : "memory"
// );
// }
// pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize {
// return asm volatile ("svc #0"
// : [result] "={x0}" (-> usize),
// : [arg0] "{x0}" (arg0),
// [arg1] "{x1}" (arg1),
// [func] "{x8}" (@intFromEnum(func)),
// : "memory"
// );
// }
};
+52 -2
View File
@@ -1,8 +1,58 @@
pub const arch = @import("arch.zig");
pub const syscall = @import("syscall.zig");
pub const log = @import("log.zig");
const abi = @import("abi");
const Handle = abi.Handle;
fn debug_write(handle: Handle, message: []const u8) void {
_ = syscall.send(handle, &.{
@intFromPtr(message.ptr),
message.len,
0,
0,
0,
});
}
fn exit_process(handle: Handle, code: u32) noreturn {
_ = syscall.send(handle, &.{
@intFromEnum(abi.ProcessObjectAction.ZO_process_exit),
code,
0,
0,
0,
});
unreachable;
}
fn allocate_memory(handle: Handle, count: usize) u64 {
var output: [5]usize = undefined;
// TODO error code
_ = syscall.sendrecv(
handle,
&.{
@intFromEnum(abi.PhysicalMemoryObjectAction.ZO_physical_memory_allocate),
count,
0,
0,
0,
},
&output,
);
return output[0];
}
export fn _start(arg: usize) linksection(".text.entry") callconv(.C) noreturn {
log.println("arg=0x{x} ({})", .{ arg, arg });
syscall.exit(0);
// TODO make the kernel provide those dynamically somehow
const SELF_PROCESS_HANDLE: Handle = @enumFromInt(1);
const PHYSICAL_MEMORY_HANDLE: Handle = @enumFromInt(2);
const DEBUG_HANDLE: Handle = @enumFromInt(3);
_ = arg;
const mem = allocate_memory(PHYSICAL_MEMORY_HANDLE, 8);
_ = mem;
debug_write(DEBUG_HANDLE, "Hello!!!");
exit_process(SELF_PROCESS_HANDLE, 4321);
}
+17 -6
View File
@@ -2,13 +2,24 @@ const arch = @import("arch.zig");
const abi = @import("abi");
const sc = arch.syscall;
const SC = abi.SyscallNumber;
pub fn exit(code: u32) noreturn {
_ = sc.syscall1(SC.SYS_exit, code);
unreachable;
pub inline fn send(object: abi.Handle, msg: *const [5]usize) usize {
return sc.send(@intFromEnum(object), msg);
}
pub fn debug_write(text: []const u8) void {
_ = sc.syscall2(SC.SYS_debug_write, @intFromPtr(text.ptr), text.len);
pub inline fn recv(object: abi.Handle, buffer: *[5]usize) usize {
return sc.recv(@intFromEnum(object), buffer);
}
pub inline fn sendrecv(object: abi.Handle, msg: *const [5]usize, buffer: *[5]usize) usize {
return sc.sendrecv(@intFromEnum(object), msg, buffer);
}
// pub fn exit(code: u32) noreturn {
// _ = sc.syscall1(SC.SYS_exit, code);
// unreachable;
// }
//
// pub fn debug_write(text: []const u8) void {
// _ = sc.syscall2(SC.SYS_debug_write, @intFromPtr(text.ptr), text.len);
// }