diff --git a/kernel-src/arch/x86-64/main.c b/kernel-src/arch/x86-64/main.c index c6491f37..aa40a797 100644 --- a/kernel-src/arch/x86-64/main.c +++ b/kernel-src/arch/x86-64/main.c @@ -13,6 +13,7 @@ #include #include #include +#include static cpu_t bsp_cpu; @@ -35,5 +36,6 @@ void kernel_entry() { time_t u = arch_hpet_init(); timekeeper_init(arch_hpet_ticks, u); arch_apic_timerinit(); + sched_init(); __assert(!"kernel entry end"); } diff --git a/kernel-src/include/kernel/scheduler.h b/kernel-src/include/kernel/scheduler.h new file mode 100644 index 00000000..12c8a3a2 --- /dev/null +++ b/kernel-src/include/kernel/scheduler.h @@ -0,0 +1,60 @@ +#ifndef _SCHEDULER_H +#define _SCHEDULER_H + +#include +#include + +#define SCHED_THREAD_FLAGS_QUEUED 1 +#define SCHED_THREAD_FLAGS_RUNNING 2 + +typedef int pid_t; +typedef int tid_t; +typedef int gid_t; +typedef int uid_t; +typedef unsigned int mode_t; + +struct proc_t; + +typedef struct thread_t { + struct thread_t *next; + struct thread_t *prev; + struct proc_t *proc; + struct cpu_t *cpu; + context_t context; + extracontext_t extracontext; + void *kernelstack; + void *kernelstacktop; + size_t kernelstacksize; + vmmcontext_t *vmmctx; + tid_t tid; + int flags; + long priority; +} thread_t; + +typedef struct proc_t { + spinlock_t lock; + struct proc_t *sibling; + struct proc_t *parent; + struct proc_t *child; + pid_t pid; + gid_t gid; + uid_t uid; + thread_t **threads; + size_t threadtablesize; + size_t runningthreadcount; + mode_t umask; + int flags; +} proc_t; + +#include + +void sched_init(); +void sched_runinit(); +void sched_threadexit(); +void sched_queue(thread_t *thread); +void sched_stopcurrentthread(); +void sched_yield(); +proc_t *sched_newproc(); +thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, proc_t *proc, void *ustack); + +#endif diff --git a/kernel-src/include/x86-64/arch/cpu.h b/kernel-src/include/x86-64/arch/cpu.h index 3890a86b..76c0c870 100644 --- a/kernel-src/include/x86-64/arch/cpu.h +++ b/kernel-src/include/x86-64/arch/cpu.h @@ -10,8 +10,9 @@ #include #include #include +#include -typedef struct { +typedef struct cpu_t { uint64_t gdt[7]; ist_t ist; long id; @@ -21,8 +22,13 @@ typedef struct { timer_t *timer; bool intstatus; long ipl; + thread_t *thread; + thread_t *idlethread; + timerentry_t schedtimerentry; } cpu_t; +#define CPU_HALT() asm volatile("hlt") + static inline cpu_t *_cpu() { return (cpu_t *)rdmsr(MSR_GSBASE); } diff --git a/kernel-src/sys/scheduler.c b/kernel-src/sys/scheduler.c new file mode 100644 index 00000000..7a7c0360 --- /dev/null +++ b/kernel-src/sys/scheduler.c @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include +#include +#include + +#define QUANTUM_US 100000 + +static scache_t *threadcache; +static scache_t *processcache; + +#define RUNQUEUE_COUNT 64 + +typedef struct { + thread_t *list; + thread_t *last; +} rqueue_t; + +static rqueue_t runqueue[RUNQUEUE_COUNT]; +static uint64_t runqueuebitmap; +static spinlock_t runqueuelock; + +proc_t *sched_newproc() { + proc_t *proc = slab_allocate(processcache); + if (proc == NULL) + return NULL; + + proc->threads = alloc(sizeof(thread_t *)); + if (proc->threads == NULL) { + slab_free(processcache, proc); + return NULL; + } + // TODO move this to slab + proc->threadtablesize = 1; + proc->runningthreadcount = 1; + SPINLOCK_INIT(proc->lock); + + return proc; +} + +thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, proc_t *proc, void *ustack) { + thread_t *thread = slab_allocate(threadcache); + if (thread == NULL) + return NULL; + + thread->kernelstack = vmm_map(NULL, kstacksize, VMM_FLAGS_ALLOCATE, ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_NOEXEC, NULL); + if (thread->kernelstack == NULL) { + slab_free(threadcache, thread); + return NULL; + } + + thread->kernelstacktop = (void *)((uintptr_t)thread->kernelstack + kstacksize); + + // non kernel thread vmm contexts are handled by the caller + thread->vmmctx = proc ? NULL : &vmm_kernelctx; + thread->proc = proc; + thread->priority = priority; + + CTX_INIT(&thread->context, proc != NULL); + CTX_SP(&thread->context) = proc ? (ctxreg_t)ustack : (ctxreg_t)thread->kernelstacktop; + CTX_IP(&thread->context) = (ctxreg_t)ip; + + return thread; +} + +void sched_destroythread(thread_t *thread) { + vmm_unmap(thread->kernelstack, thread->kernelstacksize, 0); + slab_free(threadcache, thread); +} + +void sched_destroyproc(proc_t *proc) { + free(proc->threads); + slab_free(processcache, proc); +} + +static thread_t *runqueuenext() { + bool intstate = interrupt_set(false); + + thread_t *thread = NULL; + + if (runqueuebitmap == 0) + goto leave; + + // TODO use the bitmap for this + for (int i = 0; i < RUNQUEUE_COUNT; ++i) { + if (runqueue[i].list) { + thread = runqueue[i].list; + runqueue[i].list = thread->next; + if (runqueue[i].list == NULL) { + runqueue[i].last = NULL; + runqueuebitmap &= ~((uint64_t)1 << i); + } + break; + } + } + + thread->flags &= ~SCHED_THREAD_FLAGS_QUEUED; + + leave: + interrupt_set(intstate); + return thread; +} + +static __attribute__((noreturn)) void switchthread(thread_t *thread) { + interrupt_set(false); + thread_t* current = _cpu()->thread; + + _cpu()->thread = thread; + + if(current == NULL || thread->vmmctx != current->vmmctx) + vmm_switchcontext(thread->vmmctx); + + ARCH_CONTEXT_SWITCHTHREAD(thread); + + __builtin_unreachable(); +} + +static void runqueueinsert(thread_t *thread) { + thread->flags |= SCHED_THREAD_FLAGS_QUEUED; + + runqueuebitmap |= ((uint64_t)1 << thread->priority); + + thread->prev = runqueue[thread->priority].last; + if (thread->prev) + thread->prev->next = thread; + else + runqueue[thread->priority].list = thread; + + thread->next = NULL; + runqueue[thread->priority].last = thread; +} + +void sched_queue(thread_t *thread) { + bool intstate = interrupt_set(false); + spinlock_acquire(&runqueuelock); + + // maybe instead of an assert, a simple return would suffice as the thread would already be queued anyways + __assert((thread->flags & SCHED_THREAD_FLAGS_QUEUED) == 0 && (thread->flags & SCHED_THREAD_FLAGS_RUNNING) == 0); + + runqueueinsert(thread); + + spinlock_release(&runqueuelock); + interrupt_set(intstate); + // TODO yield if higher priority than current thread (or send another CPU an IPI) +} + +__attribute__((noreturn)) void sched_stopcurrentthread() { + interrupt_set(false); + if (_cpu()->thread) + _cpu()->thread->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + + spinlock_acquire(&runqueuelock); + + thread_t *next = runqueuenext(); + if (next == NULL) + next = _cpu()->idlethread; + + spinlock_release(&runqueuelock); + + switchthread(next); +} + +void sched_threadexit() { + interrupt_set(false); + thread_t *thread = _cpu()->thread; + proc_t *proc = thread->proc; + + if (proc) { + spinlock_acquire(&proc->lock); + + int toffset = -1; + + for (int i = 0; i < proc->threadtablesize; ++i) { + if (proc->threads[i] == thread) { + toffset = i; + break; + } + } + + __assert(toffset != -1); + --proc->runningthreadcount; + + if (proc->runningthreadcount == 0) { + // TODO prepare proc state to be left as a zombie + } + spinlock_release(&proc->lock); + } + + _cpu()->thread = NULL; + + // because a thread deallocating its own data is a nightmare, thread deallocation and such will be left to whoever frees the proc it's tied to + // (likely an exit(2) call) + // FIXME this doesn't apply to kernel threads and something should be figured out for them + + sched_stopcurrentthread(); +} + +static void yield(context_t *context) { + thread_t *thread = _cpu()->thread; + + spinlock_acquire(&runqueuelock); + + thread_t *next = runqueuenext(); + + // TODO priority checking and stuff + + if (next) { + ARCH_CONTEXT_THREADSAVE(thread, context); + runqueueinsert(thread); + + spinlock_release(&runqueuelock); + switchthread(next); + } + + spinlock_release(&runqueuelock); +} + +void sched_yield() { + bool old = interrupt_set(false); + arch_context_saveandcall(yield); + interrupt_set(old); +} + +static void timerhook(void *private, context_t *context) { + thread_t* current = _cpu()->thread; + + spinlock_acquire(&runqueuelock); + + thread_t *next = runqueuenext(); + + // TODO priority handling here + + if (next) { + current->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + ARCH_CONTEXT_THREADSAVE(current, context); + + runqueueinsert(current); + + ARCH_CONTEXT_THREADLOAD(next, context); + if (next->vmmctx != current->vmmctx) + vmm_switchcontext(next->vmmctx); + next->flags |= SCHED_THREAD_FLAGS_RUNNING; + _cpu()->thread = next; + } + + spinlock_release(&runqueuelock); +} + +static void cpuidlethread() { + // TODO check every interrupt rather than waiting for a scheduler timer interrupt + interrupt_set(true); + while (1) + CPU_HALT(); +} + +void sched_init() { + threadcache = slab_newcache(sizeof(thread_t), 0, NULL, NULL); + __assert(threadcache); + processcache = slab_newcache(sizeof(proc_t), 0, NULL, NULL); + __assert(processcache); + + _cpu()->schedtimerentry.func = timerhook; + _cpu()->schedtimerentry.repeatus = QUANTUM_US; + SPINLOCK_INIT(runqueuelock); + + _cpu()->idlethread = sched_newthread(cpuidlethread, PAGE_SIZE * 4, 0, NULL, NULL); + __assert(_cpu()->idlethread); + _cpu()->thread = sched_newthread(NULL, PAGE_SIZE * 32, 0, NULL, NULL); + __assert(_cpu()->thread); + + timer_insert(_cpu()->timer, &_cpu()->schedtimerentry, QUANTUM_US); + // XXX move this resume to a more appropriate place + timer_resume(_cpu()->timer); +}