Cleanup Thread
This commit is contained in:
@ -3,5 +3,6 @@ cmake_minimum_required(VERSION 3.14)
|
|||||||
target_sources(kernel PUBLIC
|
target_sources(kernel PUBLIC
|
||||||
${CHURLOS_SRC_DIR}/kernel/process/Scheduler.cc
|
${CHURLOS_SRC_DIR}/kernel/process/Scheduler.cc
|
||||||
${CHURLOS_SRC_DIR}/kernel/process/Thread.cc
|
${CHURLOS_SRC_DIR}/kernel/process/Thread.cc
|
||||||
|
${CHURLOS_SRC_DIR}/kernel/process/thread_asm.cpp
|
||||||
${CHURLOS_SRC_DIR}/kernel/process/Thread.asm
|
${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_switch]
|
||||||
[GLOBAL Thread_start]
|
[GLOBAL Thread_start]
|
||||||
|
|
||||||
; IMPLEMENTIERUNG DER FUNKTIONEN
|
|
||||||
|
|
||||||
[SECTION .text]
|
[SECTION .text]
|
||||||
|
|
||||||
|
;; Starts or continues a thread
|
||||||
Thread_start:
|
Thread_start:
|
||||||
; *
|
;; NOTE: Starting/Continuing a thread works like this:
|
||||||
; * Hier muss Code eingefuegt werden
|
;; 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 ==
|
;; == High address ==
|
||||||
;; ESP
|
;; ESP
|
||||||
;; SP --> RET ADDR
|
;; SP --> RET ADDR
|
||||||
@ -59,20 +51,21 @@ Thread_start:
|
|||||||
sti
|
sti
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
;; Store the current thread's state and continue with another thread
|
||||||
Thread_switch:
|
Thread_switch:
|
||||||
; *
|
|
||||||
; * Hier muss Code eingefuegt werden
|
|
||||||
; *
|
|
||||||
|
|
||||||
;; NOTE: The thread switching works like this:
|
;; NOTE: The thread switching works like this:
|
||||||
;; 1. Prev thread is running, pit interrupt triggers preemption, interrupt handler called
|
;; 1. Prev thread is running, timer interrupt triggers preemption, interrupt handler called
|
||||||
;; 2. Prev registers are pushed to prev stack after the return address
|
;; 2. This function is called (with the call the return address where the execution should resume
|
||||||
;; 3. Switch to next stack
|
;; when switching back to the prev thread is pushed to the stack)
|
||||||
;; 3. Registers are popped from stack, the esp now points
|
;; 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
|
;; to the return address (that was written to the stack when it
|
||||||
;; was switched from)
|
;; 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 ==
|
;; == High address ==
|
||||||
;; ESP_NEXT
|
;; ESP_NEXT
|
||||||
@ -83,6 +76,9 @@ Thread_switch:
|
|||||||
pusha
|
pusha
|
||||||
pushf
|
pushf
|
||||||
;; == High address ==
|
;; == High address ==
|
||||||
|
;; PREV
|
||||||
|
;; THREAD
|
||||||
|
;; STACK
|
||||||
;; + 0x2c ESP_NEXT
|
;; + 0x2c ESP_NEXT
|
||||||
;; + 0x28 *ESP_PREV
|
;; + 0x28 *ESP_PREV
|
||||||
;; + 0x24 RET ADDR
|
;; + 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 ==
|
;; == High address ==
|
||||||
;; NEW
|
;; NEW
|
||||||
;; THREAD
|
;; THREAD
|
||||||
|
@ -21,114 +21,52 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
#include "Thread.h"
|
#include "Thread.h"
|
||||||
|
#include "thread_asm.cpp"
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Kernel {
|
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
|
||||||
|
|
||||||
/*****************************************************************************
|
Thread::Thread() : tid(ThreadCnt++), stack(new uint32_t[STACK_SIZE]) {
|
||||||
* 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) {
|
|
||||||
if (stack == nullptr) {
|
if (stack == nullptr) {
|
||||||
// TODO: Exception
|
// TODO: Exception
|
||||||
return;
|
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 {
|
void Thread::start() const {
|
||||||
|
|
||||||
/* hier muss Code eingefügt werden */
|
|
||||||
|
|
||||||
Thread_start(esp);
|
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__
|
#ifndef Thread_include__
|
||||||
#define Thread_include__
|
#define Thread_include__
|
||||||
|
|
||||||
#include "lib/stream/Logger.h"
|
#include <cstdint>
|
||||||
|
#include "lib/util/RestrictedConstructors.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
@ -36,7 +10,7 @@ class Thread {
|
|||||||
friend class Scheduler;
|
friend class Scheduler;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint16_t tid; // Thread-ID (wird im Konstruktor vergeben)
|
uint16_t tid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reserved thread ids.
|
* Reserved thread ids.
|
||||||
@ -46,38 +20,53 @@ public:
|
|||||||
CLEANUP = 1 // TODO: Can cleanup be done in a thread?
|
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:
|
public:
|
||||||
Thread();
|
Thread();
|
||||||
|
|
||||||
Thread(const Thread ©) = delete; // Verhindere Kopieren
|
MakeUncopyable(Thread)
|
||||||
|
|
||||||
// TODO: Rest of constructors
|
MakeUnmovable(Thread)
|
||||||
|
|
||||||
// TODO: Investigate this
|
|
||||||
virtual ~Thread() {
|
virtual ~Thread() {
|
||||||
delete[] stack;
|
delete[] stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methode des Threads, muss in Sub-Klasse implementiert werden
|
|
||||||
virtual void run() = 0;
|
virtual void run() = 0;
|
||||||
|
|
||||||
private:
|
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;
|
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);
|
void switchTo(Thread &next);
|
||||||
|
|
||||||
// Ask thread to terminate itself
|
|
||||||
void suicide() { running = false; }
|
void suicide() { running = false; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool running = true; // TODO: Remove
|
||||||
|
|
||||||
private:
|
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