metal-cos/sys/kern/sched.c
2023-09-10 16:12:25 -04:00

228 lines
5.2 KiB
C

/*
* Copyright (c) 2013-2023 Ali Mashtizadeh
* All rights reserved.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sys/kassert.h>
#include <sys/kconfig.h>
#include <sys/kdebug.h>
#include <sys/kmem.h>
#include <sys/ktime.h>
#include <sys/mp.h>
#include <sys/spinlock.h>
#include <sys/thread.h>
#include <machine/trap.h>
#include <machine/pmap.h>
// Scheduler Queues
/**
* Scheduler lock that protects all queues.
*/
Spinlock schedLock;
/**
* All threads currently waiting.
*/
ThreadQueue waitQueue;
/**
* All runnable threads.
*/
ThreadQueue runnableQueue;
/**
* Current thread executing on a given CPU.
*/
Thread *curProc[MAX_CPUS];
/*
* Scheduler Functions
*/
/**
* Sched_Current() --
*
* Get the currently executing thread. This function retains a reference count
* and you must release the reference by calling Thread_Release.
*
* @return Returns the current thread.
*/
Thread *
Sched_Current()
{
Spinlock_Lock(&schedLock);
Thread *thr = curProc[CPU()];
Thread_Retain(thr);
Spinlock_Unlock(&schedLock);
return thr;
}
/**
* Sched_SetRunnable --
*
* Set the thread to the runnable state and move it from the wait queue if
* necessary to the runnable queue.
*
* @param [in] thr Thread to be set as runnable.
*/
void
Sched_SetRunnable(Thread *thr)
{
Spinlock_Lock(&schedLock);
if (thr->proc->procState == PROC_STATE_NULL)
thr->proc->procState = PROC_STATE_READY;
if (thr->schedState == SCHED_STATE_WAITING) {
thr->waitTime += KTime_GetEpochNS() - thr->waitStart;
thr->waitStart = 0;
TAILQ_REMOVE(&waitQueue, thr, schedQueue);
}
thr->schedState = SCHED_STATE_RUNNABLE;
TAILQ_INSERT_TAIL(&runnableQueue, thr, schedQueue);
Spinlock_Unlock(&schedLock);
}
/**
* Sched_SetWaiting --
*
* Set the thread to the waiting state and move it to the wait queue. The
* thread should be currently running.
*
* @param [in] thr Thread to be set as waiting.
*/
void
Sched_SetWaiting(Thread *thr)
{
Spinlock_Lock(&schedLock);
ASSERT(thr->schedState == SCHED_STATE_RUNNING);
thr->schedState = SCHED_STATE_WAITING;
TAILQ_INSERT_TAIL(&waitQueue, thr, schedQueue);
thr->waitStart = KTime_GetEpochNS();
Spinlock_Unlock(&schedLock);
}
/**
* Sched_SetZombie --
*
* Set the thread to the zombie state and move it to the parent processes's
* zombie process queue. The thread should be currently running.
*
* @param [in] thr Thread to be set as zombie.
*/
void
Sched_SetZombie(Thread *thr)
{
Process *proc = thr->proc;
/*
* Do not go to sleep holding spinlocks.
*/
ASSERT(Critical_Level() == 0);
if (proc->threads == 1) {
// All processes have parents except 'init' and 'kernel'
ASSERT(proc->parent != NULL);
Mutex_Lock(&proc->parent->zombieProcLock);
Spinlock_Lock(&proc->parent->lock); // Guards child list
proc->procState = PROC_STATE_ZOMBIE;
TAILQ_REMOVE(&proc->parent->childrenList, proc, siblingList);
TAILQ_INSERT_TAIL(&proc->parent->zombieProc, proc, siblingList);
Spinlock_Unlock(&proc->parent->lock);
CV_Signal(&proc->zombieProcPCV);
CV_Signal(&proc->parent->zombieProcCV);
}
/*
* Set as zombie just before releasing the zombieProcLock in case we had to
* sleep to acquire the zombieProcLock.
*/
Spinlock_Lock(&schedLock);
thr->schedState = SCHED_STATE_ZOMBIE;
Spinlock_Unlock(&schedLock);
Spinlock_Lock(&proc->lock);
TAILQ_INSERT_TAIL(&proc->zombieQueue, thr, schedQueue);
Spinlock_Unlock(&proc->lock);
if (proc->threads == 1)
Mutex_Unlock(&proc->parent->zombieProcLock);
}
/**
* Sched_Switch --
*
* Switch between threads. During the creation of kernel threads (and by proxy
* user threads) we may not return through this code path and thus the kernel
* thread initialization function must release the scheduler lock.
*
* @param [in] oldthr Current thread we are switching from.
* @param [in] newthr Thread to switch to.
*/
static void
Sched_Switch(Thread *oldthr, Thread *newthr)
{
// Load AS
PMap_LoadAS(newthr->space);
Thread_SwitchArch(oldthr, newthr);
}
/**
* Sched_Scheduler --
*
* Run our round robin scheduler to find the process and switch to it.
*/
void
Sched_Scheduler()
{
Thread *prev;
Thread *next;
Spinlock_Lock(&schedLock);
// Select next thread
next = TAILQ_FIRST(&runnableQueue);
if (!next) {
/*
* There may be no other runnable processes on this core. This is a
* good opportunity to migrate threads. We should never hit this case
* once the OS is up and running because of the idle threads, but just
* in case we should assert that we never return to a zombie or waiting
* thread.
*/
ASSERT(curProc[CPU()]->schedState == SCHED_STATE_RUNNING);
Spinlock_Unlock(&schedLock);
return;
}
ASSERT(next->schedState == SCHED_STATE_RUNNABLE);
TAILQ_REMOVE(&runnableQueue, next, schedQueue);
prev = curProc[CPU()];
curProc[CPU()] = next;
next->schedState = SCHED_STATE_RUNNING;
next->ctxSwitches++;
if (prev->schedState == SCHED_STATE_RUNNING) {
prev->schedState = SCHED_STATE_RUNNABLE;
TAILQ_INSERT_TAIL(&runnableQueue, prev, schedQueue);
}
Sched_Switch(prev, next);
Spinlock_Unlock(&schedLock);
}