173 lines
5.0 KiB
Zig
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();
|
|
}
|