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 = @import("panic.zig").panic; const ComptimeBitmap = @import("bitmap.zig").ComptimeBitmap; const vmm = @import("vmm.zig"); 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. pub const EntryPoint = usize; /// 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 default stack size of a task. Currently this is set to a page size. pub const STACK_SIZE: u32 = arch.MEMORY_BLOCK_SIZE / @sizeOf(u32); /// 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 kernel stack for the task. This will be allocated on initialisation. kernel_stack: []usize, /// Pointer to the user stack for the task. This will be allocated on initialisation and will be empty if it's a kernel task user_stack: []usize, /// The current stack pointer into the stack. stack_pointer: usize, /// Whether the process is a kernel process or not kernel: bool, /// The virtual memory manager belonging to the task vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), /// /// 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: EntryPoint - The entry point into the task. This must be a function. /// IN kernel: bool - Whether the task has kernel or user privileges. /// IN task_vmm: *VirtualMemoryManager - The virtual memory manager associated with the task. /// 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: EntryPoint, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), allocator: *Allocator) Allocator.Error!*Task { var task = try allocator.create(Task); errdefer allocator.destroy(task); const pid = allocatePid(); errdefer freePid(pid); var k_stack = try allocator.alloc(usize, STACK_SIZE); errdefer allocator.free(k_stack); var u_stack = if (kernel) &[_]usize{} else try allocator.alloc(usize, STACK_SIZE); errdefer if (!kernel) allocator.free(u_stack); task.* = .{ .pid = pid, .kernel_stack = k_stack, .user_stack = u_stack, .stack_pointer = @ptrToInt(&k_stack[STACK_SIZE - 1]), .kernel = kernel, .vmm = task_vmm, }; try arch.initTask(task, entry_point, allocator); 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.kernel_stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) { allocator.free(self.kernel_stack); } if (!self.kernel) { allocator.free(self.user_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(@ptrToInt(test_fn1), true, undefined, &fa.allocator)); expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, &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, 0); } test "create out of memory for stack" { // Set the global allocator var fa = FailingAllocator.init(testing_allocator, 1); expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, &fa.allocator)); expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, &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, 0); } test "create expected setup" { var task = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); defer task.destroy(std.testing.allocator); // Will allocate the first PID 0 expectEqual(task.pid, 0); expectEqual(task.kernel_stack.len, STACK_SIZE); expectEqual(task.user_stack.len, 0); var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator); defer user_task.destroy(std.testing.allocator); expectEqual(user_task.pid, 1); expectEqual(user_task.user_stack.len, STACK_SIZE); expectEqual(user_task.kernel_stack.len, STACK_SIZE); } 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 allocator = std.testing.allocator; var task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator); var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, allocator); task.destroy(allocator); user_task.destroy(allocator); // All PIDs were freed expectEqual(all_pids.bitmap, 0); } test "Multiple create" { var task1 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); var task2 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); expectEqual(task1.pid, 0); expectEqual(task2.pid, 1); expectEqual(all_pids.bitmap, 3); task1.destroy(std.testing.allocator); expectEqual(all_pids.bitmap, 2); var task3 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); expectEqual(task3.pid, 0); expectEqual(all_pids.bitmap, 3); task2.destroy(std.testing.allocator); task3.destroy(std.testing.allocator); var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator); expectEqual(user_task.pid, 0); expectEqual(all_pids.bitmap, 1); user_task.destroy(std.testing.allocator); expectEqual(all_pids.bitmap, 0); } test "allocatePid and freePid" { expectEqual(all_pids.bitmap, 0); var i: usize = 0; 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); }