sched: add base for scheduling and task switching
This commit is contained in:
parent
14e673b8dd
commit
b2fccf1ecd
9 changed files with 341 additions and 0 deletions
arch/x86
include/gay
kernel
56
arch/x86/include/arch/sched.h
Normal file
56
arch/x86/include/arch/sched.h
Normal 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.
|
||||
*/
|
|
@ -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
73
arch/x86/sys/switch.S
Normal 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.
|
||||
*/
|
|
@ -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
94
include/gay/sched.h
Normal 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.
|
||||
*/
|
|
@ -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>.
|
||||
|
|
|
@ -11,6 +11,7 @@ target_sources(gay_kernel PRIVATE
|
|||
irq.c
|
||||
kprintf.c
|
||||
main.c
|
||||
sched.c
|
||||
util.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;
|
||||
}
|
||||
|
||||
|
|
95
kernel/sched.c
Normal file
95
kernel/sched.c
Normal 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.
|
||||
*/
|
Loading…
Reference in a new issue