astryon/core/src/thread.zig
Gabriel 50e7b6cca7
core: Check the stack is in user memory before changing it to kernel space
Otherwise, we're just adding the physical base to an address that's already in the higher half.
2025-02-21 18:47:24 +01:00

227 lines
6.8 KiB
Zig

const std = @import("std");
const vmm = @import("arch/vmm.zig").arch;
const interrupts = @import("arch/interrupts.zig").arch;
pub const arch = @import("arch/thread.zig").arch;
const pmm = @import("pmm.zig");
const cpu = @import("arch/cpu.zig");
pub const ThreadState = enum {
Inactive,
Running,
Blocked,
Sleeping,
};
pub const ThreadControlBlock = struct {
id: u64,
address_space: ?vmm.AddressSpace,
regs: interrupts.InterruptStackFrame,
state: ThreadState,
user_priority: u8,
// Managed by scheduleNewTask(), no need to set manually.
ticks: u64,
// Managed by addThreadToPriorityQueue(), no need to set manually.
current_priority: u32,
// Managed by startSleep(), no need to set manually.
sleep_ticks: u64,
};
pub const ThreadList = std.DoublyLinkedList(ThreadControlBlock);
const ALLOCATED_TICKS_PER_TASK = 20;
pub fn enterTask(task: *ThreadControlBlock) noreturn {
cpu.thisCore().current_thread = task;
task.ticks = ALLOCATED_TICKS_PER_TASK;
var table = vmm.readPageTable();
if (task.address_space) |space| {
table = space.phys;
}
task.state = .Running;
// If the stack is in user memory, then we need a pointer to its higher-half version. If it's already in kernel memory, no need to do anything.
var base: usize = 0;
if (arch.readStackPointer() < vmm.PHYSICAL_MAPPING_BASE) {
base += vmm.PHYSICAL_MAPPING_BASE;
}
arch.enterTask(&task.regs, base, table.address);
}
fn switchTask(regs: *interrupts.InterruptStackFrame, new_task: *ThreadControlBlock) void {
const core = cpu.thisCore();
core.current_thread.regs = regs.*;
regs.* = new_task.regs;
if (new_task.address_space) |space| {
if (vmm.readPageTable().address != space.phys.address) vmm.setPageTable(space.phys);
}
new_task.ticks = ALLOCATED_TICKS_PER_TASK;
core.current_thread = new_task;
}
pub fn fetchNewTask(core: *cpu.arch.Core, should_idle_if_not_found: bool) ?*ThreadControlBlock {
const last = core.active_thread_list.last orelse {
if (should_idle_if_not_found) {
return &core.idle_thread.data;
} else return null;
};
const new_task = &last.data;
removeThreadFromPriorityQueue(core, new_task);
return new_task;
}
pub fn scheduleNewTask(core: *cpu.arch.Core, regs: *interrupts.InterruptStackFrame, new_thread: *ThreadControlBlock) *ThreadControlBlock {
if (core.active_thread_list.first) |first| {
first.data.current_priority +|= 4;
}
const current_thread = core.current_thread;
switchTask(regs, new_thread);
return current_thread;
}
pub fn preempt(regs: *interrupts.InterruptStackFrame) void {
const core = cpu.thisCore();
updateSleepQueue(core);
while (popSleepQueue(core)) |thread| {
reviveThread(core, thread);
}
core.current_thread.ticks -|= 1;
if (core.current_thread.ticks == 0) {
const new_thread = fetchNewTask(core, false) orelse return;
const current_thread = scheduleNewTask(core, regs, new_thread);
addThreadToPriorityQueue(core, current_thread);
}
}
pub fn block(regs: *interrupts.InterruptStackFrame) *ThreadControlBlock {
const core = cpu.thisCore();
// fetchNewTask() always returns a thread if should_idle_if_not_found is set to true.
const new_thread = fetchNewTask(core, true) orelse unreachable;
const current_thread = scheduleNewTask(core, regs, new_thread);
current_thread.state = .Blocked;
return current_thread;
}
pub fn startSleep(regs: *interrupts.InterruptStackFrame, ticks: u64) *ThreadControlBlock {
const core = cpu.thisCore();
// fetchNewTask() always returns a thread if should_idle_if_not_found is set to true.
const new_thread = fetchNewTask(core, true) orelse unreachable;
const current_thread = scheduleNewTask(core, regs, new_thread);
current_thread.state = .Sleeping;
addThreadToSleepQueue(core, current_thread, ticks);
return current_thread;
}
fn addThreadToSleepQueue(core: *cpu.arch.Core, thread: *ThreadControlBlock, ticks: u64) void {
thread.sleep_ticks = ticks;
var it: ?*ThreadList.Node = core.sleeping_thread_list.first;
while (it) |n| : (it = n.next) {
if (thread.sleep_ticks <= n.data.sleep_ticks) {
n.data.sleep_ticks -|= thread.sleep_ticks;
core.sleeping_thread_list.insertBefore(n, @fieldParentPtr("data", thread));
return;
}
thread.sleep_ticks -|= n.data.sleep_ticks;
}
core.sleeping_thread_list.append(@fieldParentPtr("data", thread));
}
pub fn removeThreadFromSleepQueue(core: *cpu.arch.Core, thread: *ThreadControlBlock) void {
const node: *ThreadList.Node = @fieldParentPtr("data", thread);
if (node.next) |n| {
n.data.sleep_ticks +|= thread.sleep_ticks;
}
core.sleeping_thread_list.remove(node);
reviveThread(core, thread);
}
fn updateSleepQueue(core: *cpu.arch.Core) void {
const first = core.sleeping_thread_list.first orelse return;
first.data.sleep_ticks -|= 1;
}
fn popSleepQueue(core: *cpu.arch.Core) ?*ThreadControlBlock {
const first = core.sleeping_thread_list.first orelse return null;
if (first.data.sleep_ticks == 0) {
core.sleeping_thread_list.remove(first);
return &first.data;
}
return null;
}
pub fn reviveThread(core: *cpu.arch.Core, thread: *ThreadControlBlock) void {
thread.state = .Running;
addThreadToPriorityQueue(core, thread);
}
var next_id: std.atomic.Value(u64) = std.atomic.Value(u64).init(1);
pub fn createThreadControlBlock(allocator: *pmm.FrameAllocator) !*ThreadControlBlock {
const frame = try pmm.allocFrame(allocator);
const node: *ThreadList.Node = @ptrFromInt(frame.virtualAddress(vmm.PHYSICAL_MAPPING_BASE));
const thread = &node.data;
thread.id = next_id.fetchAdd(1, .seq_cst);
thread.address_space = null;
thread.regs = std.mem.zeroes(@TypeOf(thread.regs));
thread.state = .Inactive;
thread.user_priority = 127;
return thread;
}
pub fn addThreadToPriorityQueue(core: *cpu.arch.Core, thread: *ThreadControlBlock) void {
thread.current_priority = thread.user_priority;
var it: ?*ThreadList.Node = core.active_thread_list.first;
while (it) |n| : (it = n.next) {
if (thread.current_priority <= n.data.current_priority) {
n.data.current_priority -|= thread.current_priority;
core.active_thread_list.insertBefore(n, @fieldParentPtr("data", thread));
return;
}
thread.current_priority -|= n.data.current_priority;
}
core.active_thread_list.append(@fieldParentPtr("data", thread));
}
fn removeThreadFromPriorityQueue(core: *cpu.arch.Core, thread: *ThreadControlBlock) void {
const node: *ThreadList.Node = @fieldParentPtr("data", thread);
if (node.next) |n| {
n.data.current_priority +|= thread.current_priority;
}
core.active_thread_list.remove(node);
}