Files
zing/src/thread.zig
T
2025-03-27 23:37:11 +02:00

369 lines
10 KiB
Zig

//! Thread management utilities and data structures.
const std = @import("std");
const arena = @import("arena.zig");
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?
/// Signature for kernel thread entry
pub const KernelThreadFn = fn (usize) callconv(.C) void;
pub fn kernel_return() callconv(.C) noreturn {
Thread.exit_current();
}
/// Task to run when there are no real threads in the queue
pub fn idle_function(arg: usize) callconv(.C) noreturn {
_ = arg;
while (true) {
arch.wait_for_interrupt();
}
}
/// 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.
idle: arch.Context,
/// Current thread pointer.
current: ?*Thread = null,
/// Thread queue head pointer.
head: ?*Thread = null,
/// Queue's lock
lock: sync.Spinlock = .{},
/// Pointer to this CPU's thread queue.
pub threadlocal var t_this_cpu: ?*Queue = null;
/// Sets up a thread queue for the current CPU.
pub fn init_this_cpu(a: *arena.Arena) void {
const idle = arch.Context.idle();
const q = a.create(Queue);
q.* = .{ .idle = idle };
t_this_cpu = q;
}
/// Enters a task on this CPU.
pub fn enter(self: *@This()) noreturn {
if (self.head) |gt| {
self.current = gt;
gt.enter();
} else {
self.current = null;
self.idle.enter();
}
}
/// Yields CPU to the next available task.
pub fn yield(self: *@This()) void {
// TODO locking here
if (self.current) |curr| {
// Switching from thread
if (curr.next) |next| {
// ... to thread
if (next != curr) {
self.current = next;
next.switch_from(curr);
}
} else if (self.head) |h| {
// ... to thread (head)
if (h != curr) {
self.current = h;
h.switch_from(curr);
}
} else {
// ... to idle
self.current = null;
self.idle.switch_from(&curr.arch_context);
}
} else {
// Switching from idle
if (self.head) |gt| {
// ... to thread
self.current = gt;
gt.arch_context.switch_from(&self.idle);
return;
}
// ... back to idle
}
}
/// Adds an available task to this queue.
pub fn enqueue(self: *@This(), t: *Thread) void {
var guard = self.lock.lock_irqsave();
defer guard.release();
t.queue = self;
if (self.head) |gt| {
t.next = gt;
t.prev = gt.prev;
gt.prev.?.next = t;
gt.prev = t;
} else {
self.head = t;
t.next = t;
t.prev = t;
}
}
/// # Invariants
///
/// `t` must be a thread within the `self` queue.
fn dequeue(self: *@This(), t: *Thread) void {
var guard = self.lock.lock_irqsave();
defer guard.release();
t.queue = null;
if (t == self.head) {
if (t.next == t) {
self.head = null;
t.next = null;
t.prev = null;
return;
}
if (t.next) |tn| {
tn.prev = t.prev;
}
if (t.prev) |tp| {
tp.next = t.next;
}
self.head = t.next;
} else {
if (t.next) |tn| {
tn.prev = t.prev;
}
if (t.prev) |tp| {
tp.next = t.next;
}
}
t.next = null;
t.prev = null;
}
};
/// Helper data structure to represent kernel stacks in task contexts.
pub fn KStack(comptime SIZE: usize) type {
return extern struct {
// Has to be at exactly offset 0x00, used in assembly
/// Stack pointer. Aliases `data`.
sp: *usize,
/// Stack data represented as a slice of `SIZE` machine-sized words.
data: *[SIZE]usize,
/// Physical base address at which the stack is allocated.
physical_base: mem.PhysicalAddress,
/// Allocates a new kernel stack.
///
/// # Panics
///
/// Panics on Out-of-Memory condition. TODO Fix this.
pub fn create() @This() {
const physical_base = mem.phys.alloc_pages(SIZE * @sizeOf(usize) / 0x1000) orelse @panic("OOM");
const ptr = @as(*[SIZE]usize, @ptrFromInt(physical_base.virtualize()));
return .{
.data = ptr,
.physical_base = physical_base,
.sp = @ptrFromInt(@intFromPtr(&ptr[0]) + SIZE * @sizeOf(usize)),
};
}
/// Pushes a machine-sized word onto the stack.
///
/// # Panics
///
/// Panics if a push would overflow the stack.
pub fn push(self: *@This(), value: usize) void {
if (self.sp == &self.data[0]) {
@panic("KStack overflow");
}
self.sp = @ptrFromInt(@intFromPtr(self.sp) - @sizeOf(usize));
self.sp.* = value;
}
};
}
/// Adds a thread to some CPU queue for execution.
pub fn enqueue(t: *Thread) void {
Queue.t_this_cpu.?.enqueue(t);
}
/// Enters thread execution on the current CPU.
pub fn enter() noreturn {
Queue.t_this_cpu.?.enter();
}
/// Yields this CPU's execution to a next thread.
pub fn yield() void {
Queue.t_this_cpu.?.yield();
}
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 {
address_space.clear();
}
// @ 0x200000
const code_page_count = L3.page_count(code.len);
log.info("Code is {} pages", .{code_page_count});
var offset: usize = 0;
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);
const page = mem.phys.alloc_page() orelse return error.out_of_memory;
try address_space.map_single_page(address, page);
const page_data = @as([*]u8, @ptrFromInt(page.virtualize()))[0..amount];
@memcpy(page_data, code[offset .. offset + amount]);
address += amount;
offset += amount;
}
// @ 0x400000
const sp_base = 0x400000;
for (0..8) |i| {
const page = mem.phys.alloc_page() orelse return error.out_of_memory;
try address_space.map_single_page(sp_base + i * L3.SIZE, page);
}
const sp = sp_base + 8 * L3.SIZE;
log.info("Enter with sp = 0x{x}", .{sp});
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;
}