Files
zing/src/thread.zig
T

173 lines
5.0 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");
/// 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,
/// 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 {
if (self.current) |curr| {
// Switching from thread
if (curr.next) |next| {
// ... to thread
if (next != curr) {
self.current = next;
next.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 {
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;
}
}
};
/// Represents a single execution thread.
pub const Thread = struct {
/// Arena.
allocator: *arena.Arena,
/// Architecture-specific task context.
arch_context: arch.Context,
/// Next thread in the queue.
next: ?*Thread = null,
/// Previous thread in the queue.
prev: ?*Thread = null,
/// Creates a new (kernel) thread with given `pc` (entry point) and `arg`ument.
pub fn create(a: *arena.Arena, pc: usize, arg: usize) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.arch_context = arch.Context.kernel(pc, 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);
}
};
/// 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();
}