Commit e7e0f348 authored by Christian Dietrich's avatar Christian Dietrich
Browse files

Handout for Assignment 4: Context switch

parent 9c81a4b6
[SECTION .text]
[GLOBAL context_switch]
[GLOBAL context_launch]
; The current contexts register are saved in the context storage
; and the next contexts register are read into the processor.
align 8
context_switch:
align 8
context_launch:
#include "machine/context.h"
void prepareContext(void * tos, Context& context,
void (*kickoff)(void *),
void * param1) {
(void) tos;
(void) context;
(void) kickoff;
(void) param1;
}
/*! \file
* \brief Functionality required for \ref context_switch "context switching"
*/
/*! \defgroup context Context Switch
* \brief Low-Level functionality required for context switching
*/
#pragma once
#include "types.h"
/*! \brief Structure for saving the CPU context when switching coroutines.
* \ingroup context
*/
struct Context {
intptr_t ebx; ///< EBX of the thread
intptr_t ebp; ///< EBP of the thread
intptr_t edi; ///< EDI of the thread
intptr_t esi; ///< ESI of the thread
void *esp; ///< Current stack pointer of the thread
} __attribute__((packed));
/*! \brief Prepares a context for its first activation.
*
* \ingroup context
*
* To allow the execution to start in \ref Thread::kickoff during the first activation,
* the stack must be initialized such that it contains all the information required.
* This is, most importantly, the function to be called (typically the respective
* implementation of \ref Thread::kickoff) and any parameters required by this function.
*
* Additionally to the stack, the initial context is setup in a way that \ref context_switch
* and \ref context_launch can work with it.
*
* `prepareContext()` can be implemented in the high-level programming language C++
* (in file `context.cc`).
*
* \param tos Pointer to the top of stack (= address of first byte beyond the memory reserved for the stack)
* \param kickoff Pointer to the \ref Thread::kickoff function
* \param param1 first parameter for \ref Thread::kickoff function
*
* \todo Implement Function (and helper functions, if required)
*/
void prepareContext(void * tos, Context& context,
void (*kickoff)(void *),
void * param1 = nullptr);
/*! \brief Executes the context switch.
*
* \ingroup context
*
* For a clean context switch, the current register values must be stored in the given context struct.
* Subsequently, these values must be restored accordingly from the `next` context struct.
*
* This function must be implemented in assembler in the file `context.asm` (why?).
* It must be declared as `extern "C"`, as assembler functions are not C++ name mangled.
*
* \param current Pointer to the structure that the current context will be stored in
* \param next Pointer to the structure that the next context will be read from
*
* \todo Implement Method
*/
extern "C" void context_switch(Context* current, Context* next);
/*! \brief Launch context switching.
*
* To start context switching, the current context is thrown away and the stored registers
* within the given `next` context struct replace it.
*
* This function must be implemented in assembler in the file `context.asm` (why?).
* It must be declared as `extern "C"`, as assembler functions are not C++ name mangled.
*
* \ingroup context
*
* \param next Pointer to the structure that the next context will be read from
*
* \todo Implement Method
*/
extern "C" void context_launch(Context* next);
// vim: set noet ts=4 sw=4:
#include "thread/dispatcher.h"
// vim: set noet ts=4 sw=4:
/*! \file
* \brief \ref Dispatcher for \ref Thread threads
*/
#pragma once
#include "thread/thread.h"
#include "machine/core.h"
/*! \brief The dispatcher dispatches threads and, by that, puts the scheduler's
* decisions into action.
* \ingroup thread
*
* The dispatcher manages the life pointer that refers to the currently
* active coroutine and performs the actual switching of processes.
* For single-core systems, a single life pointer is sufficient, as only a
* single process can be active at one point in 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 Implement Method
*/
Dispatcher();
/*! \brief Returns the co-routing currently running on the CPU core calling
* this method
*
*
* \todo Implement Method
*/
Thread* active() {
return nullptr;
}
/*! \brief This method stores first as life pointer for this CPU core and
* triggers the execution of first.
* \param first First co-routine to be executed on this CPU core.
*
* \todo Implement Method
*/
void go(Thread * first);
/*! \brief Updates the life pointer to next and issues a co-routine
* change from the old to the new life pointer.
* \param next Next co-routine to be executed.
*
* \todo Implement Method
*/
void dispatch(Thread *next);
};
// vim: set noet ts=4 sw=4:
// vim: set noet ts=4 sw=4:
/*! \file
*
* \brief \ref Scheduler to manage the \ref Thread "threads"
*/
#pragma once
#include "thread/dispatcher.h"
#include "thread/thread.h"
#include "object/queue.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 (idle?) 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 Implement Method
*
*/
void schedule();
/*! \brief Include a thread in scheduling decisions.
*
* This method will register a thread for scheduling, that is, it will
* be appended to the ready queue and dispatched once its time has come.
* \param that \ref Thread to be scheduled
*
* \todo 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 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)
*
* \todo Implement Method
*/
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 are made by the scheduler himself on basis of
* threads currently in the ready queue. There is no need for the
* calling thread to make this decision.
* The currently active thread is appended to the end of the queue; the
* first thread in the queue will be activated.
*
* \todo Implement Method
*
*/
void resume();
};
extern Scheduler scheduler;
// vim: set noet ts=4 sw=4:
#include "thread/thread.h"
#include "thread/dispatcher.h"
#include "debug/output.h"
// 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 "machine/context.h"
#include "object/queue.h"
/*! \brief The is an object used by the scheduler.
* \ingroup thread
*/
class Thread {
// pointer to the next element of the readylist
Thread* queue_link;
friend class Queue<Thread>;
protected:
/*! \brief Current thread context for context switch
*/
Context context;
/*! \brief Function to start a thread.
*
* 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. For this purpose, we use this `kickoff()` function.
*
* <b>Activating kickoff</b>
*
* The thread initialization via \ref prepareContext() not only initializes
* the Stack for the first thread change, but also pushes the address of
* `kickoff()` as return address to the stack.
* Consequently, the first execution of \ref context_switch() will start
* execution by returning to the beginning of `kickoff()` .
*
* This `kickoff()` function simply calls the \ref action() method on the
* thread passed as parameter and, thus, resolves the virtual C++ method.
*
* \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 Implement Method
*/
static void kickoff(Thread* object);
public:
// /*! \brief Unique ID of thread
// */
// const size_t id;
/*! \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.
*
* \todo 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 all subsequent context
* switches.
*
* \todo Implement Method
*
*/
void go();
/*! \brief Switches from the currently running thread to the `next` one.
*
* The values currently stored in the non-scratch registers will be
* stored on this thread's stack; the corresponding values belonging to
* `next` thread will be loaded (from `next`'s stack).
* \param next Pointer to the next thread.
*
* \todo Implement Method
* \opt To detect stack overflows you can check if the bottom of the stack
* still contains a predefined value (which was set in constructor).
*
*/
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() = 0;
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment