WIP: Implement thread exit
This commit is contained in:
@@ -20,6 +20,19 @@ pub const impl = switch (cpu) {
|
||||
|
||||
pub const vmm = impl.vmm;
|
||||
|
||||
pub const IrqGuard = struct {
|
||||
state: bool,
|
||||
|
||||
pub fn acquire() @This() {
|
||||
const state = set_interrupt_mask(true);
|
||||
return .{ .state = state };
|
||||
}
|
||||
|
||||
pub fn release(self: @This()) void {
|
||||
set_interrupt_mask(self.state);
|
||||
}
|
||||
};
|
||||
|
||||
/// Halts the CPU execution indefinitely, without ever returning.
|
||||
pub inline fn halt() noreturn {
|
||||
impl.halt();
|
||||
|
||||
+2
-4
@@ -18,7 +18,6 @@ const std = @import("std");
|
||||
|
||||
fn f0(arg: usize) callconv(.C) void {
|
||||
log.info("Argument is {}", .{arg});
|
||||
thread.yield();
|
||||
}
|
||||
|
||||
noinline fn f1(arg: usize, c: usize) void {
|
||||
@@ -37,8 +36,6 @@ pub export fn kernel_main() callconv(.C) noreturn {
|
||||
var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena");
|
||||
thread.Queue.init_this_cpu(&a);
|
||||
|
||||
const t0 = thread.Thread.create_kernel(&a, &f0, 1234);
|
||||
|
||||
const code = switch (comptime arch.cpu) {
|
||||
.riscv64 => &[_]u8{
|
||||
0x93, 0x02, 0xB0, 0x07, // li t0, 123
|
||||
@@ -55,7 +52,8 @@ pub export fn kernel_main() callconv(.C) noreturn {
|
||||
};
|
||||
const t1 = thread.test_create_user_from_code(&a, code) catch @panic("Could not create test thread");
|
||||
|
||||
thread.enqueue(t0);
|
||||
const t = thread.Thread.create_kernel(&a, &f0, 1234);
|
||||
thread.enqueue(t);
|
||||
thread.enqueue(t1);
|
||||
|
||||
log.info("Test", .{});
|
||||
|
||||
+80
-1
@@ -6,6 +6,7 @@ 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 ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
|
||||
|
||||
@@ -15,7 +16,7 @@ const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
|
||||
pub const KernelThreadFn = fn (usize) callconv(.C) void;
|
||||
|
||||
pub fn kernel_return() callconv(.C) noreturn {
|
||||
@panic("TODO: kernel thread exit");
|
||||
Thread.exit_current();
|
||||
}
|
||||
|
||||
/// Task to run when there are no real threads in the queue
|
||||
@@ -34,6 +35,8 @@ pub const Queue = struct {
|
||||
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;
|
||||
@@ -59,6 +62,7 @@ pub const Queue = struct {
|
||||
|
||||
/// 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| {
|
||||
@@ -67,6 +71,12 @@ pub const Queue = struct {
|
||||
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;
|
||||
@@ -86,6 +96,11 @@ pub const Queue = struct {
|
||||
|
||||
/// 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;
|
||||
@@ -97,6 +112,44 @@ pub const Queue = struct {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents a single execution thread.
|
||||
@@ -106,6 +159,8 @@ pub const Thread = struct {
|
||||
/// 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.
|
||||
@@ -151,6 +206,30 @@ pub const Thread = struct {
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user