sched: add base for scheduling and task switching

This commit is contained in:
anna 2021-10-15 00:01:47 +02:00
parent 14e673b8dd
commit b2fccf1ecd
Signed by: fef
GPG key ID: EC22E476DC2D3D84
9 changed files with 341 additions and 0 deletions

View file

@ -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.
*/

View file

@ -6,6 +6,7 @@ target_sources(gay_arch PRIVATE
irq.c
irq.S
port.S
switch.S
trap.c
trap.S
)

73
arch/x86/sys/switch.S Normal file
View file

@ -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.
*/

View file

@ -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.
*

94
include/gay/sched.h Normal file
View file

@ -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.
*/

View file

@ -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>.

View file

@ -11,6 +11,7 @@ target_sources(gay_kernel PRIVATE
irq.c
kprintf.c
main.c
sched.c
util.c
)

View file

@ -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;
}

95
kernel/sched.c Normal file
View file

@ -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.
*/