This commit is contained in:
Niklas Gollenstede
2025-04-14 11:20:52 +02:00
commit 5a2e32aaeb
126 changed files with 16742 additions and 0 deletions

17
thread/dispatcher.cc Normal file
View File

@@ -0,0 +1,17 @@
// vim: set noet ts=4 sw=4:
#include "dispatcher.h"
Dispatcher::Dispatcher() {}
Thread *Dispatcher::active() { return nullptr; }
bool Dispatcher::isActive(const Thread *thread, unsigned *cpu) {
(void)thread;
(void)cpu;
return false;
}
void Dispatcher::go(Thread *first) { (void)first; }
void Dispatcher::dispatch(Thread *next) { (void)next; }

66
thread/dispatcher.h Normal file
View File

@@ -0,0 +1,66 @@
// vim: set noet ts=4 sw=4:
/*! \file
* \brief \ref Dispatcher for \ref Thread threads
*/
#pragma once
#include "../thread/thread.h"
#include "../types.h"
/*! \brief The dispatcher dispatches threads and puts the scheduler's
* decisions into action.
* \ingroup thread
*
* The dispatcher manages the life pointer that refers to the currently
* active thread and performs the actual switching of processes.
* For single-core systems, a single life pointer is sufficient, as only a
* single thread can be active at any one time. On multi-core systems,
* every CPU core needs its own life pointer.
*/
class Dispatcher {
/*! \brief set the currently active thread
* \param thread active Thread
*/
void setActive(Thread* thread) { (void)thread; }
public:
/*! \brief constructor
*
* \todo(14) Implement Method
*/
Dispatcher();
/*! \brief Returns the thread currently running on the CPU core calling
* this method
*
* \todo(14) Implement Method
*/
Thread* active();
/*! \brief Check if thread is currently active
* \param thread Pointer to the thread in question
* \param cpu will receive the core number if `cpu` pointer is not `nullptr`
* and the thread is currently active
* \return `true` if currently running, false otherwise
*
* \todo(15) Implement method for kill IPI (in \MPStuBS only)
*/
bool isActive(const Thread* thread, unsigned* cpu = nullptr);
/*! \brief This method stores first as life pointer for this CPU core and
* triggers the execution of first. Only to be used for the first thread
* running on a CPU.
* \param first First thread to be executed on this CPU core.
*
* \todo(14) Implement Method
*/
void go(Thread* first);
/*! \brief Updates the life pointer to next and issues a thread change from
* the old to the new life pointer.
* \param next Next thread to be executed.
*
* \todo(14) Implement Method
*/
void dispatch(Thread* next);
};

3
thread/idlethread.cc Normal file
View File

@@ -0,0 +1,3 @@
#include "idlethread.h"
void IdleThread::action() {}

27
thread/idlethread.h Normal file
View File

@@ -0,0 +1,27 @@
/*! \file
* \brief \ref IdleThread executed by the \ref Scheduler if no other \ref
* Thread is ready
*/
#pragma once
#include "../types.h"
#include "thread.h"
/*! \brief Thread that is executed when there is nothing to do for this core.
* \ingroup thread
*
* Using the IdleThread simplifies the idea of waiting and is an answer to the
* questions that arise once the ready queue is empty.
*
* \note Instance of this class should *never* be inserted into the scheduler's
* ready queue, as the IdleThread should only be executed if there is no
* proper work to do.
*/
class IdleThread : public Thread {
public:
/*! \brief Wait for a thread to become ready and sleep in the meantime.
*
* \todo(16) Implement Method
*/
void action() override;
};

27
thread/scheduler.cc Normal file
View File

@@ -0,0 +1,27 @@
// vim: set noet ts=4 sw=4:
#include "scheduler.h"
Scheduler::Scheduler() {}
Thread* Scheduler::getNext() { return nullptr; }
void Scheduler::schedule() {}
void Scheduler::ready(Thread* that) { (void)that; }
void Scheduler::resume(bool ready) { (void)ready; }
void Scheduler::exit() {}
void Scheduler::kill(Thread* that) { (void)that; }
bool Scheduler::isActive(const Thread* that, unsigned int* cpu) {
(void)that;
(void)cpu;
return false;
}
bool Scheduler::isEmpty() const { return false; }
void Scheduler::setIdle(IdleThread* that) { (void)that; }

120
thread/scheduler.h Normal file
View File

@@ -0,0 +1,120 @@
// vim: set noet ts=4 sw=4:
/*! \file
*
* \brief \ref Scheduler to manage the \ref Thread "threads"
*/
#pragma once
#include "../types.h"
#include "dispatcher.h"
#include "idlethread.h"
#include "thread.h"
/*! \brief The scheduler plans the threads' execution order and, from this,
* selects the next thread to be running.
* \ingroup thread
*
* The scheduler manages the ready queue (a private \ref Queue object),
* that is the list of threads that are ready to execute. The scheduler
* arranges threads in a FIFO order, that is, when a thread is set ready, it
* will be appended to the end of the queue, while threads to be executed are
* taken from the front of the queue.
*/
class Scheduler {
/*! \brief a Dispatcher object, providing the low level context switching
* routines.
*/
Dispatcher dispatcher;
/*! \brief Helper to retrieve next Thread
* \return pointer of next thread
*/
Thread* getNext();
public:
Scheduler();
/*! \brief Start scheduling
*
* This method starts the scheduling by removing the first thread from
* the ready queue and activating it. \MPStuBS needs to call this method
* once for every CPU core to dispatch the first thread.
*
* \todo(14) Implement Method
*/
void schedule();
/*! \brief Include a thread in scheduling decisions.
*
* This method will register a thread for scheduling. It will be appended
* to the ready queue and dispatched once its time has come.
* \param that \ref Thread to be scheduled
*
* \todo(14) Implement Method
*/
void ready(Thread* that);
/*! \brief (Self-)termination of the calling thread.
*
* This method can be used by a thread to exit itself. The calling
* thread will not be appended to the ready queue; a reschedule will be
* issued.
*
* \todo(14) Implement Method
*/
void exit();
/*! \brief Kills the passed thread
*
* This method is used to kill the \ref Thread `that`.
* For \OOStuBS, it is sufficient to remove `that` from the ready queue
* and, thereby, exclude the thread from scheduling.
* For \MPStuBS, a simple removal is not sufficient, as the thread might
* currently be running on another CPU core. In this case, the thread needs
* to be marked as *dying* (a flag checked by resume prior to enqueuing
* into the ready queue)
*/
/*!
* Note: The thread should be able to kill itself.
*
* \todo(14) Implement Method
*
* \todo(15) Adapt method (for MPStuBS)
*/
void kill(Thread* that);
/*! \brief Issue a thread change
*
* This method issues the change of the currently active thread without
* requiring the calling thread to be aware of the other threads.
* Scheduling decisions, i.e. which thread will be run next, are made by
* the scheduler itself with the knowledge of the currently ready threads.
* The currently active thread is appended to the end of the queue; the
* first thread in the queue will be activated (to implement the FIFO
* policy).
*
* \todo(14) Implement Method
*/
void resume(bool ready = true);
/*! \brief return the active thread from the dispatcher
*/
Thread* active() { return dispatcher.active(); }
/// \copydoc Dispatcher::isActive
bool isActive(const Thread* that, unsigned int* cpu = nullptr);
/*! \brief Checks whether the ready queue is empty.
*
* \todo(16) Implement Method
*/
bool isEmpty() const;
/*! \brief Set the idle thread for the executing CPU
* \param[in] that the idle thread to use for the executing CPU
*
* \todo(16) Implement Method
*/
void setIdle(IdleThread* that);
};

16
thread/thread.cc Normal file
View File

@@ -0,0 +1,16 @@
// vim: set noet ts=4 sw=4:
#include "thread.h"
// Alias to simplify stuff
typedef void (*kickoff_t)(void*);
void Thread::kickoff(Thread* object) { (void)object; }
Thread::Thread(void* tos) { (void)tos; }
void Thread::resume(Thread* next) { (void)next; }
void Thread::go() {}
void Thread::action() {}

86
thread/thread.h Normal file
View File

@@ -0,0 +1,86 @@
// vim: set noet ts=4 sw=4:
/*! \file
* \brief \ref Thread abstraction required for multithreading
*/
/*! \defgroup thread Multithreading
* \brief The Multithreading Subsystem
*
* The group Multithreading contains all elements that form the foundation
* of CPU multiplexing. This module's objective is to provide the abstraction
* thread that provides a virtualised CPU for the user's applications.
*/
#pragma once
#include "../arch/context.h"
#include "../object/queue.h"
#include "../types.h"
/*! \brief The Thread is an object used by the scheduler.
* \ingroup thread
*/
class Thread {
protected:
/*! \brief The thread's entry point.
*
* For the first activation of a thread, we need a "return address"
* pointing to a function that will take care of calling C++ virtual
* methods (e.g. \ref action()), based on the thread object pointer.
* For this purpose, we use this `kickoff()` function.
*
* \note As this function is never actually called, but only executed by
* returning from the co-routine's initial stack, it may never return.
* Otherwise garbage values from the stack will be interpreted as
* return address and the system might crash.
*
* \param object Thread to be started
*/
/// \todo(14) Implement Method
static void kickoff(Thread* object);
public:
/*! \brief Marker for a dying thread
*/
volatile bool kill_flag;
/*! \brief Constructor
* Initializes the context using \ref prepareContext with the given stack
* space.
*
* \param tos the top of stack, highest address of some memory block that
* should be used as stack (remember stacks grow to the lower addresses on
* x86).
*
* \todo(14) Implement constructor
*/
explicit Thread(void* tos);
/*! \brief Activates the first thread on this CPU.
*
* Calling the method starts the first thread on the calling CPU.
* From then on, \ref Thread::resume() must be used for all subsequent
* context switches.
*
* \todo(14) Implement Method
*/
void go();
/*! \brief Switches from the currently running thread to the `next` one.
*
* The values currently present in the callee-saved registers will be
* stored in this threads context-structure, the corresponding values
* belonging to `next` thread will be loaded.
* \param next Pointer to the next thread.
*
* \todo(14) Implement Method
*/
void resume(Thread* next);
/*! \brief Method that contains the thread's program code.
*
* Derived classes are meant to override this method to provide
* meaningful code to be run in this thread.
*/
virtual void action();
};