diff --git a/arch/x86/include/arch/sched.h b/arch/x86/include/arch/sched.h new file mode 100644 index 0000000..c62d204 --- /dev/null +++ b/arch/x86/include/arch/sched.h @@ -0,0 +1,56 @@ +/* See the end of this file for copyright and license terms. */ + +#pragma once + +#include <gay/cdefs.h> +#include <gay/types.h> + +/** + * @brief In-kernel context save for the x86. + * This precise structure layout is hardcoded in assembly, so don't forget to + * update `arch/x86/sys/switch.S` if you need to change it for whatever reason. + */ +struct x86_context { + /** + * The register itself is %esp, which points to the %eip that was pushed + * by the function calling `arch_switch_to()` as per the x86 SysV ABI + */ + union { + register_t esp; + register_t *eip; + }; + register_t esi; + register_t edi; + register_t ebx; + register_t ebp; +} __packed; + +/** + * @brief Arch dependent Task Control Block (x86 version). + * This is what's required for in-kernel task switching. + * Treat as a completely opaque type outside of the `arch/x86` directory. + */ +typedef struct x86_context tcb_t; + +/** + * @brief Arch dependent low level task switch routine (x86 version). + * `new` must not be equal to `old` or the whole thing probably blows up. + * + * @param new TCB of the new task we are switching to + * @param old TCB of the old task we are switching from + */ +extern void arch_switch_to(tcb_t *new, tcb_t *old); + +/* + * This file is part of GayBSD. + * Copyright (c) 2021 fef <owo@fef.moe>. + * + * GayBSD is nonviolent software: you may only use, redistribute, and/or + * modify it under the terms of the Cooperative Nonviolent Public License + * (CNPL) as found in the LICENSE file in the source code root directory + * or at <https://git.pixie.town/thufie/npl-builder>; either version 7 + * of the license, or (at your option) any later version. + * + * GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent + * permitted by applicable law. See the CNPL for details. + */ diff --git a/arch/x86/sys/CMakeLists.txt b/arch/x86/sys/CMakeLists.txt index c979f27..f033599 100644 --- a/arch/x86/sys/CMakeLists.txt +++ b/arch/x86/sys/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(gay_arch PRIVATE irq.c irq.S port.S + switch.S trap.c trap.S ) diff --git a/arch/x86/sys/switch.S b/arch/x86/sys/switch.S new file mode 100644 index 0000000..dc3ec2d --- /dev/null +++ b/arch/x86/sys/switch.S @@ -0,0 +1,73 @@ +/* See the end of this file for copyright and license terms. */ + +#include <asm/common.h> + +/* + * Alright, a lot of stuff that is not immediately obvious to someone who hasn't + * done this sort of thing before is going on here, and since i'm totally new to + * x86 myself, here is some excessive documentation of the entire process (which + * will hopefully also help other newcomers in understanding the actual switching + * mechanism). I think the main reason this particular function might seem a + * little confusing is that it returns to a different place than where it came + * from, which is kind of the whole point if you think about it. + * + * This routine is called from within kernel space, and will perform a switch to + * another task that also runs in kernel space. So, this has nothing to do with + * changing ring levels. When another task switches back to the original task, + * that original task just continues execution as if it had never called this + * function. + * As per the x86 SysV ABI, procedure calling is done by pushing all arguments + * to the stack in reverse order, and then use the `call` instruction which + * additionally pushes %eip to the stack and jumps to the subroutine's address. + * So, when we enter this method, the stack looks like this (remember, this is a + * full descending stack): + * + * | address | value | + * +---------+------------------+ + * | 8(%esp) | old (argument 2) | + * | 4(%esp) | new (argument 1) | + * | (%esp) | caller's %eip | + * + * What we need to do now is store all caller saved registers (which critically + * include the stack pointer) into `old`, set their values to the ones from + * `new` (again, including the stack pointer) and then just return. + * The new stack pointer will point to the same stack layout shown above, but + * this time that of the new task we are going to switch to. Since the stack + * also includes the %eip from when the new task called arch_switch_to(), we + * automatically switch to that task when returning. + */ + + .text + +/* void arch_switch_to(tcb_t *new, tcb_t *old); */ +ASM_ENTRY(arch_switch_to) + mov 8(%esp), %eax /* %eax = old */ + mov %esp, (%eax) + mov %esi, 4(%eax) + mov %edi, 8(%eax) + mov %ebx, 12(%eax) + mov %ebp, 16(%eax) + + mov 4(%esp), %eax /* %eax = new */ + mov (%eax), %esp + mov 4(%eax), %esi + mov 8(%eax), %edi + mov 12(%eax), %ebx + mov 16(%eax), %ebp + + ret +ASM_END(arch_switch_to) + +/* + * This file is part of GayBSD. + * Copyright (c) 2021 fef <owo@fef.moe>. + * + * GayBSD is nonviolent software: you may only use, redistribute, and/or + * modify it under the terms of the Cooperative Nonviolent Public License + * (CNPL) as found in the LICENSE file in the source code root directory + * or at <https://git.pixie.town/thufie/npl-builder>; either version 7 + * of the license, or (at your option) any later version. + * + * GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent + * permitted by applicable law. See the CNPL for details. + */ diff --git a/include/gay/clist.h b/include/gay/clist.h index ffc7d4b..34e39e1 100644 --- a/include/gay/clist.h +++ b/include/gay/clist.h @@ -61,6 +61,14 @@ void clist_add_first(struct clist *head, struct clist *new); */ void clist_del(struct clist *node); +/** + * @brief Return whether a clist is empty. + * + * @param head The `struct clist *` acting as the head of the clist + * @returns An expression that evaluates true if the list is empty + */ +#define clist_is_empty(head) ((head)->next == (head)) + /** * @brief Iterate over every node in a clist. * diff --git a/include/gay/sched.h b/include/gay/sched.h new file mode 100644 index 0000000..aa37122 --- /dev/null +++ b/include/gay/sched.h @@ -0,0 +1,94 @@ +/* See the end of this file for copyright and license terms. */ + +#pragma once + +#include <arch/sched.h> + +#include <gay/cdefs.h> +#include <gay/clist.h> +#include <gay/types.h> + +enum task_state { + TASK_READY, + TASK_BLOCKED, + TASK_DEAD, +}; + +struct task { + /** + * @brief List node for enqueuing this task in the scheduler's pipeline. + * When the task is currently running, this is invalid because the task + * is not in any queue. + */ + struct clist run_queue; + tcb_t tcb; + struct task *parent; + pid_t pid; + enum task_state state; +}; + +/** + * @brief The task that is currently running on this particular cpu core. + * May not be accessed from irq context. + * + * This will become a macro which calls some function once we have SMP support, + * so if you need to access it multiple times from the same place you should + * copy it into a local variable first to avoid extra overhead. + * + * For the same reason, don't assign values to this directly, and use + * `set_current()` instead. + */ +extern struct task *current; + +/** + * @brief Update the `current` task. + * You will almost never need this because `switch_to()` does it automatically. + */ +#define set_current(task) (current = (task)) + +extern struct task kernel_task; + +/** + * @brief Initialize the scheduler. + * + * @return 0 on success, or a negative error number on failure + */ +int sched_init(void); + +/** + * @brief Main scheduler routine. + * Calling this function will result in the current task being suspended and put + * back to the queue, and a new one is selected to be run next. The scheduler + * is free to choose the current task again, in which case this call does not + * sleep. The function returns when the scheduler has been invoked again and + * decided to give this task its next turn. + */ +void schedule(void); + +/** + * @brief Perform an in-kernel task switch. + * `new` must not be equal to `old` or the whole thing probably blows up. + * + * The Linux kernel has a similar function with the same name, except that the + * arguments are in reverse order because i find it funny to cause unnecessary + * confusion which will undoubtedly result in a lot of bugs further down the + * road that are going to take weeks to find. + * + * @param new New task to switch to + * @param old Current task + */ +void switch_to(struct task *new, struct task *old); + +/* + * This file is part of GayBSD. + * Copyright (c) 2021 fef <owo@fef.moe>. + * + * GayBSD is nonviolent software: you may only use, redistribute, and/or + * modify it under the terms of the Cooperative Nonviolent Public License + * (CNPL) as found in the LICENSE file in the source code root directory + * or at <https://git.pixie.town/thufie/npl-builder>; either version 7 + * of the license, or (at your option) any later version. + * + * GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent + * permitted by applicable law. See the CNPL for details. + */ diff --git a/include/gay/types.h b/include/gay/types.h index 54b776b..f97e813 100644 --- a/include/gay/types.h +++ b/include/gay/types.h @@ -91,6 +91,11 @@ typedef __register_t register_t; typedef __u_register_t u_register_t; #endif /* not _U_REGISTER_T_DECLARED */ +#ifndef _PID_T_DECLARED +#define _PID_T_DECLARED 1 +typedef int pid_t; +#endif /* not _PID_T_DECLARED */ + /* * This file is part of GayBSD. * Copyright (c) 2021 fef <owo@fef.moe>. diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 64735e7..6bc3ee1 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -11,6 +11,7 @@ target_sources(gay_kernel PRIVATE irq.c kprintf.c main.c + sched.c util.c ) diff --git a/kernel/main.c b/kernel/main.c index e5c790a..6e710a2 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -1,6 +1,7 @@ /* See the end of this file for copyright and license terms. */ #include <gay/irq.h> +#include <gay/sched.h> /** * @brief Main kernel entry point. @@ -15,7 +16,14 @@ */ int main(int argc, char *argv[]) { + int err; + irq_init(); + + err = sched_init(); + if (err) + return err; + return 0; } diff --git a/kernel/sched.c b/kernel/sched.c new file mode 100644 index 0000000..c5270f1 --- /dev/null +++ b/kernel/sched.c @@ -0,0 +1,95 @@ +/* See the end of this file for copyright and license terms. */ + +#include <arch/interrupt.h> +#include <arch/sched.h> + +#include <gay/clist.h> +#include <gay/sched.h> +#include <gay/types.h> + +struct task kernel_task = { + .parent = nil, + .pid = 0, + .state = TASK_READY, +}; + +struct task *current = &kernel_task; + +/* + * Two run queues, one active and one inactive. The scheduler takes tasks out + * of the active queue, runs them if possible, and then puts them into the + * inactive queue if they aren't dead. When the active queue gets empty, the + * queues are switched and everything starts over again. + * The `current` task is always in neither queue. + */ +static struct clist _run_queues[2]; +static struct clist *active_queue = &_run_queues[0]; +static struct clist *inactive_queue = &_run_queues[1]; + +int sched_init(void) +{ + clist_init(active_queue); + clist_init(inactive_queue); + return 0; +} + +void schedule(void) +{ + struct task *old = current; + struct task *new = nil; + + clist_add(inactive_queue, &old->run_queue); + +/* Dijkstra would be so mad if he saw this */ +try_again: + if (clist_is_empty(active_queue)) { + struct clist *tmp = active_queue; + active_queue = inactive_queue; + inactive_queue = tmp; + } + + struct task *cursor, *tmp; + clist_foreach_entry_safe(active_queue, cursor, tmp, run_queue) { + clist_del(&cursor->run_queue); + if (cursor->state == TASK_READY) { + new = cursor; + break; + } else if (cursor->state == TASK_BLOCKED) { + clist_add(inactive_queue, &cursor->run_queue); + } + } + + /* + * This should really make the CPU go idle rather than constantly + * looping around until some event occurs, but i have no idea how to do + * that (yet) + */ + if (new == nil) + goto try_again; + + if (new != old) + switch_to(new, old); +} + +void switch_to(struct task *new, struct task *old) +{ + disable_intr(); + current = new; + arch_switch_to(&new->tcb, &old->tcb); + current = old; + enable_intr(); +} + +/* + * This file is part of GayBSD. + * Copyright (c) 2021 fef <owo@fef.moe>. + * + * GayBSD is nonviolent software: you may only use, redistribute, and/or + * modify it under the terms of the Cooperative Nonviolent Public License + * (CNPL) as found in the LICENSE file in the source code root directory + * or at <https://git.pixie.town/thufie/npl-builder>; either version 7 + * of the license, or (at your option) any later version. + * + * GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent + * permitted by applicable law. See the CNPL for details. + */