Cleanup Thread
This commit is contained in:
@ -3,5 +3,6 @@ cmake_minimum_required(VERSION 3.14)
|
||||
target_sources(kernel PUBLIC
|
||||
${CHURLOS_SRC_DIR}/kernel/process/Scheduler.cc
|
||||
${CHURLOS_SRC_DIR}/kernel/process/Thread.cc
|
||||
${CHURLOS_SRC_DIR}/kernel/process/thread_asm.cpp
|
||||
${CHURLOS_SRC_DIR}/kernel/process/Thread.asm
|
||||
)
|
||||
|
@ -1,32 +1,24 @@
|
||||
;*****************************************************************************
|
||||
;* *
|
||||
;* C O R O U T I N E *
|
||||
;* *
|
||||
;*---------------------------------------------------------------------------*
|
||||
;* Beschreibung: Assemblerdarstellung der 'struct CoroutineState' aus *
|
||||
;* CoroutineState.h *
|
||||
;* *
|
||||
;* Die Reihenfolge der Registerbezeichnungen muss unbedingt *
|
||||
;* mit der von 'struct CoroutineState' uebereinstimmen. *
|
||||
;* *
|
||||
;* Autor: Olaf Spinczyk, TU Dortmund *
|
||||
;*****************************************************************************
|
||||
|
||||
; EXPORTIERTE FUNKTIONEN
|
||||
|
||||
[GLOBAL Thread_switch]
|
||||
[GLOBAL Thread_start]
|
||||
|
||||
; IMPLEMENTIERUNG DER FUNKTIONEN
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
;; Starts or continues a thread
|
||||
Thread_start:
|
||||
; *
|
||||
; * Hier muss Code eingefuegt werden
|
||||
; *
|
||||
;; NOTE: Starting/Continuing a thread works like this:
|
||||
;; 1. When preparing a new thread the stack is prepared manually to contain the
|
||||
;; address of the "kickoff" function in place of the return address and the
|
||||
;; thread's stack registers. The "esp" member variable points to this stack
|
||||
;; 2. When a running thread is paused (because of preemption), the registers are pushed
|
||||
;; and the stack looks the same way
|
||||
;; 3. When a thread should be started or continued this function is called, during the call
|
||||
;; the argument (the stack pointer) and return address will be pushed to the stack
|
||||
;; 4. Load the pushed stack pointer to switch the stack (leave the current stack and use
|
||||
;; the stack of the thread that should be started/continued
|
||||
;; 5. Load all the registers that were prepared/saved beforehand
|
||||
;; 6. Call return, this jumps either to kickoff (if the thread is started for the first time)
|
||||
;; or to the last execution position before the thread was paused
|
||||
|
||||
;; NOTE: New code with pusha/popa, restores all registers as I use this not only for first start
|
||||
;; == High address ==
|
||||
;; ESP
|
||||
;; SP --> RET ADDR
|
||||
@ -59,20 +51,21 @@ Thread_start:
|
||||
sti
|
||||
ret
|
||||
|
||||
|
||||
;; Store the current thread's state and continue with another thread
|
||||
Thread_switch:
|
||||
; *
|
||||
; * Hier muss Code eingefuegt werden
|
||||
; *
|
||||
|
||||
;; NOTE: The thread switching works like this:
|
||||
;; 1. Prev thread is running, pit interrupt triggers preemption, interrupt handler called
|
||||
;; 2. Prev registers are pushed to prev stack after the return address
|
||||
;; 3. Switch to next stack
|
||||
;; 3. Registers are popped from stack, the esp now points
|
||||
;; 1. Prev thread is running, timer interrupt triggers preemption, interrupt handler called
|
||||
;; 2. This function is called (with the call the return address where the execution should resume
|
||||
;; when switching back to the prev thread is pushed to the stack)
|
||||
;; 3. Prev registers are pushed to the stack after the return address
|
||||
;; The prev thread's stack pointer is also saved (to the thread's esp member variable)
|
||||
;; 4. Switch to next stack by loading the esp of the next thread (leave current stack, we will switch
|
||||
;; back to it when resuming the prev thread, this is possible because we saved the stack pointer
|
||||
;; to the thread instance)
|
||||
;; 5. Registers are popped from stack, the esp now points
|
||||
;; to the return address (that was written to the stack when it
|
||||
;; was switched from)
|
||||
;; 4. Return follows the return address to resume normal stack execution
|
||||
;; 6. Return follows the return address to resume normal stack execution
|
||||
|
||||
;; == High address ==
|
||||
;; ESP_NEXT
|
||||
@ -83,6 +76,9 @@ Thread_switch:
|
||||
pusha
|
||||
pushf
|
||||
;; == High address ==
|
||||
;; PREV
|
||||
;; THREAD
|
||||
;; STACK
|
||||
;; + 0x2c ESP_NEXT
|
||||
;; + 0x28 *ESP_PREV
|
||||
;; + 0x24 RET ADDR
|
||||
@ -102,7 +98,7 @@ Thread_switch:
|
||||
|
||||
;; ============================================================
|
||||
|
||||
mov esp, [esp + 0x2c] ; Move to next coroutines stack
|
||||
mov esp, [esp + 0x2c] ; Move to next thread's stack
|
||||
;; == High address ==
|
||||
;; NEW
|
||||
;; THREAD
|
||||
|
@ -21,114 +21,52 @@
|
||||
*****************************************************************************/
|
||||
|
||||
#include "Thread.h"
|
||||
|
||||
// TODO: Move to thread_interface or sth.
|
||||
// Funktionen, die auf der Assembler-Ebene implementiert werden, muessen als
|
||||
// extern "C" deklariert werden, da sie nicht dem Name-Mangeling von C++
|
||||
// entsprechen.
|
||||
extern "C" {
|
||||
void Thread_start(uint32_t esp);
|
||||
|
||||
// NOTE: Only when backing up the previous thread the esp gets updated, so only esp_pre is a pointer
|
||||
void Thread_switch(uint32_t *esp_prev, uint32_t esp_next);
|
||||
}
|
||||
#include "thread_asm.cpp"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
uint16_t ThreadCnt = 2; // Skip IDs that are fixed to specific unique threads
|
||||
uint16_t ThreadCnt = 8; // Reserve some IDs that are fixed to specific unique threads
|
||||
|
||||
/*****************************************************************************
|
||||
* Prozedur: Coroutine_init *
|
||||
*---------------------------------------------------------------------------*
|
||||
* Beschreibung: Bereitet den Kontext der Koroutine fuer den ersten *
|
||||
* Aufruf vor. *
|
||||
*****************************************************************************/
|
||||
void Thread_init(uint32_t *esp, uint32_t *stack, void (*kickoff)(Thread *), void *object) {
|
||||
|
||||
// NOTE: c++17 doesn't allow register
|
||||
// register uint32_t** sp = (uint32_t**)stack;
|
||||
// uint32_t** sp = (uint32_t**)stack;
|
||||
|
||||
// Stack initialisieren. Es soll so aussehen, als waere soeben die
|
||||
// eine Funktion aufgerufen worden, die als Parameter den Zeiger
|
||||
// "object" erhalten hat.
|
||||
// Da der Funktionsaufruf simuliert wird, kann fuer die Ruecksprung-
|
||||
// adresse nur ein unsinniger Wert eingetragen werden. Die aufgerufene
|
||||
// Funktion muss daher dafuer sorgen, dass diese Adresse nie benoetigt
|
||||
// wird, sie darf also nicht terminieren, sonst kracht's.
|
||||
|
||||
// I thought this syntax was a bit clearer than decrementing a pointer
|
||||
stack[-1] = reinterpret_cast<uint32_t>(object);
|
||||
stack[-2] = 0x131155U;
|
||||
stack[-3] = reinterpret_cast<uint32_t>(kickoff);
|
||||
stack[-4] = 0; // EAX
|
||||
stack[-5] = 0; // ECX
|
||||
stack[-6] = 0; // EDX
|
||||
stack[-7] = 0; // EBX
|
||||
stack[-8] = reinterpret_cast<uint32_t>(&stack[-3]); // ESP
|
||||
stack[-9] = 0; // EBP
|
||||
stack[-10] = 0; // ESI
|
||||
stack[-11] = 0; // EDI
|
||||
stack[-12] = 0x200U;
|
||||
|
||||
*esp = reinterpret_cast<uint32_t>(&stack[-12]);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Funktion: kickoff *
|
||||
*---------------------------------------------------------------------------*
|
||||
* Beschreibung: Funktion zum Starten einer Korutine. Da diese Funktion *
|
||||
* nicht wirklich aufgerufen, sondern nur durch eine *
|
||||
* geschickte Initialisierung des Stacks der Koroutine *
|
||||
* angesprungen wird, darf er nie terminieren. Anderenfalls *
|
||||
* wuerde ein sinnloser Wert als Ruecksprungadresse *
|
||||
* interpretiert werden und der Rechner abstuerzen. *
|
||||
*****************************************************************************/
|
||||
[[noreturn]] void kickoff(Thread *object) {
|
||||
object->run();
|
||||
|
||||
// object->run() kehrt (hoffentlich) nie hierher zurueck
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Methode: Coroutine::Coroutine *
|
||||
*---------------------------------------------------------------------------*
|
||||
* Beschreibung: Initialer Kontext einer Koroutine einrichten. *
|
||||
* *
|
||||
* Parameter: *
|
||||
* stack Stack für die neue Koroutine *
|
||||
*****************************************************************************/
|
||||
Thread::Thread() : tid(ThreadCnt++), stack(new uint32_t[1024]), esp(0) {
|
||||
Thread::Thread() : tid(ThreadCnt++), stack(new uint32_t[STACK_SIZE]) {
|
||||
if (stack == nullptr) {
|
||||
// TODO: Exception
|
||||
return;
|
||||
}
|
||||
Thread_init(&esp, &stack[1024], kickoff, this); // Stack grows from top to bottom
|
||||
|
||||
// Manually prepare the stack. Because the stack "grows" from top to bottom
|
||||
// we need to write at the last addresses
|
||||
auto *manual_stack = &stack[STACK_SIZE];
|
||||
|
||||
manual_stack[-1] = reinterpret_cast<uint32_t>(this); // The argument for kickoff is the thread itself
|
||||
manual_stack[-2] = 0x131155; // Dummy return address that will never be called ("run" doesn't return)
|
||||
manual_stack[-3] = reinterpret_cast<uint32_t>(kickoff); // Return will jump to kickoff
|
||||
manual_stack[-4] = 0; // EAX
|
||||
manual_stack[-5] = 0; // ECX
|
||||
manual_stack[-6] = 0; // EDX
|
||||
manual_stack[-7] = 0; // EBX
|
||||
manual_stack[-8] = reinterpret_cast<uint32_t>(&manual_stack[-3]); // ESP
|
||||
manual_stack[-9] = 0; // EBP
|
||||
manual_stack[-10] = 0; // ESI
|
||||
manual_stack[-11] = 0; // EDI
|
||||
manual_stack[-12] = 0x200; // EFLAGS
|
||||
|
||||
esp = reinterpret_cast<uint32_t>(&manual_stack[-12]);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Methode: Coroutine::switchToNext *
|
||||
*---------------------------------------------------------------------------*
|
||||
* Beschreibung: Auf die nächste Koroutine umschalten. *
|
||||
*****************************************************************************/
|
||||
void Thread::switchTo(Thread &next) {
|
||||
|
||||
/* hier muss Code eingefügt werden */
|
||||
|
||||
Thread_switch(&esp, next.esp);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Methode: Coroutine::start *
|
||||
*---------------------------------------------------------------------------*
|
||||
* Beschreibung: Aktivierung der Koroutine. *
|
||||
*****************************************************************************/
|
||||
void Thread::start() const {
|
||||
|
||||
/* hier muss Code eingefügt werden */
|
||||
|
||||
Thread_start(esp);
|
||||
}
|
||||
|
||||
void Thread::switchTo(Thread &next) {
|
||||
Thread_switch(&esp, next.esp);
|
||||
}
|
||||
|
||||
[[noreturn]] void Thread::kickoff(Thread *thread) {
|
||||
thread->run();
|
||||
|
||||
// TODO: Replace with exception
|
||||
// object->run() kehrt (hoffentlich) nie hierher zurueck
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,34 +1,8 @@
|
||||
/*****************************************************************************
|
||||
* *
|
||||
* T H R E A D *
|
||||
* *
|
||||
*---------------------------------------------------------------------------*
|
||||
* Beschreibung: Implementierung eines kooperativen Thread-Konzepts. *
|
||||
* Thread-Objekte werden vom Scheduler in einer verketteten *
|
||||
* Liste 'readylist' verwaltet. *
|
||||
* *
|
||||
* Im Konstruktor wird der initialie Kontext des Threads *
|
||||
* eingerichtet. Mit 'start' wird ein Thread aktiviert. *
|
||||
* Die CPU sollte mit 'yield' freiwillig abgegeben werden. *
|
||||
* Um bei einem Threadwechsel den Kontext sichern zu *
|
||||
* koennen, enthaelt jedes Threadobjekt eine Struktur *
|
||||
* ThreadState, in dem die Werte der nicht-fluechtigen *
|
||||
* Register gesichert werden koennen. *
|
||||
* *
|
||||
* Zusaetzlich zum vorhandenen freiwilligen Umschalten der *
|
||||
* CPU mit 'Thread_switch' gibt es nun ein forciertes Um- *
|
||||
* durch den Zeitgeber-Interrupt ausgeloest wird und in *
|
||||
* Assembler in startup.asm implementiert ist. Fuer das *
|
||||
* Zusammenspiel mit dem Scheduler ist die Methode *
|
||||
* 'prepare_preemption' in Scheduler.cc wichtig. *
|
||||
* *
|
||||
* Autor: Michael, Schoettner, HHU, 16.12.2016 *
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef Thread_include__
|
||||
#define Thread_include__
|
||||
|
||||
#include "lib/stream/Logger.h"
|
||||
#include <cstdint>
|
||||
#include "lib/util/RestrictedConstructors.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@ -36,7 +10,7 @@ class Thread {
|
||||
friend class Scheduler;
|
||||
|
||||
public:
|
||||
uint16_t tid; // Thread-ID (wird im Konstruktor vergeben)
|
||||
uint16_t tid;
|
||||
|
||||
/**
|
||||
* Reserved thread ids.
|
||||
@ -46,38 +20,53 @@ public:
|
||||
CLEANUP = 1 // TODO: Can cleanup be done in a thread?
|
||||
};
|
||||
|
||||
protected:
|
||||
// TODO: Remove this
|
||||
bool running = true; // For soft exit, if thread uses infinite loop inside run(), use this as condition
|
||||
|
||||
public:
|
||||
Thread();
|
||||
|
||||
Thread(const Thread ©) = delete; // Verhindere Kopieren
|
||||
MakeUncopyable(Thread)
|
||||
|
||||
// TODO: Rest of constructors
|
||||
MakeUnmovable(Thread)
|
||||
|
||||
// TODO: Investigate this
|
||||
virtual ~Thread() {
|
||||
delete[] stack;
|
||||
}
|
||||
|
||||
// Methode des Threads, muss in Sub-Klasse implementiert werden
|
||||
virtual void run() = 0;
|
||||
|
||||
private:
|
||||
// Thread aktivieren
|
||||
/**
|
||||
* Start or continue this thread.
|
||||
* The thread's state will be restored by loading the stack pointer saved
|
||||
* in the "esp" member variable.
|
||||
*/
|
||||
void start() const;
|
||||
|
||||
// Umschalten auf Thread 'next'
|
||||
/**
|
||||
* Switch from this thread to another thread.
|
||||
*
|
||||
* @param next The thread to switch to
|
||||
*/
|
||||
void switchTo(Thread &next);
|
||||
|
||||
// Ask thread to terminate itself
|
||||
void suicide() { running = false; }
|
||||
|
||||
protected:
|
||||
bool running = true; // TODO: Remove
|
||||
|
||||
private:
|
||||
uint32_t *stack;
|
||||
uint32_t esp;
|
||||
/**
|
||||
* This function will be called when the thread is executed for the first time.
|
||||
* It's address is manually placed as return address on the thread's stack.
|
||||
*
|
||||
* @param thread The thread containing the "run" member function that will be called
|
||||
*/
|
||||
[[noreturn]] static void kickoff(Thread *thread);
|
||||
|
||||
private:
|
||||
static const constexpr uint16_t STACK_SIZE = 1024;
|
||||
|
||||
uint32_t *stack; // Pointer to the thread's stack
|
||||
uint32_t esp; // Store the stack pointer to allow interrupting/resuming thread execution
|
||||
};
|
||||
|
||||
}
|
||||
|
23
src/kernel/process/thread_asm.cpp
Normal file
23
src/kernel/process/thread_asm.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include <cstdint>
|
||||
|
||||
// Implemented in Thread.asm
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
* Start or continue a thread.
|
||||
*
|
||||
* @param esp The stack pointer of the thread to start/continue
|
||||
*/
|
||||
void Thread_start(uint32_t esp);
|
||||
|
||||
/**
|
||||
* Switch from the current thread to another thread.
|
||||
* The stack pointer member variable of the current stack will be updated.
|
||||
*
|
||||
* @param esp_prev The address where the current thread's stack pointer will be saved
|
||||
* @param esp_next The stack pointer of the thread to switch to
|
||||
*/
|
||||
void Thread_switch(uint32_t *esp_prev, uint32_t esp_next);
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user