pluto/src/kernel/task.zig

209 lines
6.5 KiB
Zig
Raw Normal View History

Initial scheduler Fix TSS Also change to .{} syntax where appropriate. Added the SS segment Fixed spelling Refactoring GDT Multitasking working for now WIP scheduler Refactored Bitmap a bit WIP still Task switching working Handlers return the stack pointer that will be used to restore the tasks stack, normal handlers will return the same stack pointer it was called with where task switching will return the stack pointer of the next task and restore its state using the interrupt stub. Initial scheduler done Created a stage 2 init task Change u32 to usize Move Task to arch specific WIP WIP2 Removed esp from task, replaced with stack_pointer Removed the debug logs Fixed init task stack Change pickNextTask to pointer manipulation This allows less allocations so faster switching Temporary enable interrupts for some runtime tests PIT and RTC need interrupts enabled to run their runtime tests Renamed schedule => pickNextTask, comptime bitmap for pids not task init And some other stuff: No pub for the task anymore Use the leak detector allocator Fmt Fix unit tests And some other stuff :P PR review Moved Task out of arch and have the stack init in the arch file Mocking clean up Removed commented code Renamed createTask to scheduleTask where the user will have to provide a task to schedule Removed redundant pub in log runtime test Removed global allocator for scheduler Cleaner assembly in paging Fmt Added new Scheduler test mode Added new test mode to CI Removed one of the prints Added doc comment, task test for i386 Removed test WIP Runtime tests work Have a global set in one task and reacted to in another. Also test that local variables are preserved after a task switch. Removed new lines Increased line length Move the allocation of the bool above the task creation
2020-07-18 22:46:24 +01:00
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const builtin = @import("builtin");
const is_test = builtin.is_test;
const build_options = @import("build_options");
const mock_path = build_options.mock_path;
const arch = @import("arch.zig").internals;
const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic;
const ComptimeBitmap = @import("bitmap.zig").ComptimeBitmap;
const Allocator = std.mem.Allocator;
/// The kernels main stack start as this is used to check for if the task being destroyed is this stack
/// as we cannot deallocate this.
extern var KERNEL_STACK_START: *u32;
/// The function type for the entry point.
const EntryPointFn = fn () void;
/// The bitmap type for the PIDs
const PidBitmap = if (is_test) ComptimeBitmap(u128) else ComptimeBitmap(u1024);
/// The list of PIDs that have been allocated.
var all_pids: PidBitmap = brk: {
var pids = PidBitmap.init();
// Set the first PID as this is for the current task running, init 0
_ = pids.setFirstFree() orelse unreachable;
break :brk pids;
};
/// The task control block for storing all the information needed to save and restore a task.
pub const Task = struct {
const Self = @This();
/// The unique task identifier
pid: PidBitmap.IndexType,
/// Pointer to the stack for the task. This will be allocated on initialisation.
stack: []u32,
/// The current stack pointer into the stack.
stack_pointer: usize,
///
/// Create a task. This will allocate a PID and the stack. The stack will be set up as a
/// kernel task. As this is a new task, the stack will need to be initialised with the CPU
/// state as described in arch.CpuState struct.
///
/// Arguments:
/// IN entry_point: EntryPointFn - The entry point into the task. This must be a function.
/// IN allocator: *Allocator - The allocator for allocating memory for a task.
///
/// Return: *Task
/// Pointer to an allocated task. This will then need to be added to the task queue.
///
/// Error: Allocator.Error
/// OutOfMemory - If there is no more memory to allocate. Any memory or PID allocated will
/// be freed on return.
///
pub fn create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
var task = try allocator.create(Task);
errdefer allocator.destroy(task);
task.pid = allocatePid();
errdefer freePid(task.pid);
const task_stack = try arch.initTaskStack(@ptrToInt(entry_point), allocator);
task.stack = task_stack.stack;
task.stack_pointer = task_stack.pointer;
return task;
}
///
/// Destroy the task. This will release the allocated PID and free the stack and self.
///
/// Arguments:
/// IN/OUT self: *Self - The pointer to self.
///
pub fn destroy(self: *Self, allocator: *Allocator) void {
freePid(self.pid);
// We need to check that the the stack has been allocated as task 0 (init) won't have a
// stack allocated as this in the linker script
if (@ptrToInt(self.stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
allocator.free(self.stack);
}
allocator.destroy(self);
}
};
///
/// Allocate a process identifier. If out of PIDs, then will panic. Is this occurs, will need to
/// increase the bitmap.
///
/// Return: u32
/// A new PID.
///
fn allocatePid() PidBitmap.IndexType {
return all_pids.setFirstFree() orelse panic(@errorReturnTrace(), "Out of PIDs\n", .{});
}
///
/// Free an allocated PID. One must be allocated to be freed. If one wasn't allocated will panic.
///
/// Arguments:
/// IN pid: u32 - The PID to free.
///
fn freePid(pid: PidBitmap.IndexType) void {
if (!all_pids.isSet(pid)) {
panic(@errorReturnTrace(), "PID {} not allocated\n", .{pid});
}
all_pids.clearEntry(pid);
}
// For testing the errdefer
const FailingAllocator = std.testing.FailingAllocator;
const testing_allocator = &std.testing.base_allocator_instance.allocator;
fn test_fn1() void {}
test "create out of memory for task" {
// Set the global allocator
var fa = FailingAllocator.init(testing_allocator, 0);
expectError(error.OutOfMemory, Task.create(test_fn1, &fa.allocator));
// Make sure any memory allocated is freed
expectEqual(fa.allocated_bytes, fa.freed_bytes);
// Make sure no PIDs were allocated
expectEqual(all_pids.bitmap, 1);
}
test "create out of memory for stack" {
// Set the global allocator
var fa = FailingAllocator.init(testing_allocator, 1);
expectError(error.OutOfMemory, Task.create(test_fn1, &fa.allocator));
// Make sure any memory allocated is freed
expectEqual(fa.allocated_bytes, fa.freed_bytes);
// Make sure no PIDs were allocated
expectEqual(all_pids.bitmap, 1);
}
test "create expected setup" {
var task = try Task.create(test_fn1, std.testing.allocator);
defer task.destroy(std.testing.allocator);
// Will allocate the first PID 1, 0 will always be allocated
expectEqual(task.pid, 1);
}
test "destroy cleans up" {
// This used the leak detector allocator in testing
// So if any alloc were not freed, this will fail the test
var fa = FailingAllocator.init(testing_allocator, 2);
var task = try Task.create(test_fn1, &fa.allocator);
task.destroy(&fa.allocator);
// Make sure any memory allocated is freed
expectEqual(fa.allocated_bytes, fa.freed_bytes);
// All PIDs were freed
expectEqual(all_pids.bitmap, 1);
}
test "Multiple create" {
var task1 = try Task.create(test_fn1, std.testing.allocator);
var task2 = try Task.create(test_fn1, std.testing.allocator);
expectEqual(task1.pid, 1);
expectEqual(task2.pid, 2);
expectEqual(all_pids.bitmap, 7);
task1.destroy(std.testing.allocator);
expectEqual(all_pids.bitmap, 5);
var task3 = try Task.create(test_fn1, std.testing.allocator);
expectEqual(task3.pid, 1);
expectEqual(all_pids.bitmap, 7);
task2.destroy(std.testing.allocator);
task3.destroy(std.testing.allocator);
}
test "allocatePid and freePid" {
expectEqual(all_pids.bitmap, 1);
var i: usize = 1;
while (i < PidBitmap.NUM_ENTRIES) : (i += 1) {
expectEqual(i, allocatePid());
}
expectEqual(all_pids.bitmap, PidBitmap.BITMAP_FULL);
i = 0;
while (i < PidBitmap.NUM_ENTRIES) : (i += 1) {
freePid(@truncate(PidBitmap.IndexType, i));
}
expectEqual(all_pids.bitmap, 0);
}