1

Cleanup Thread

This commit is contained in:
2022-12-08 20:16:08 +01:00
parent 3f5b0c3f17
commit 01d5b76e6d
5 changed files with 118 additions and 171 deletions

View File

@ -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
)

View File

@ -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

View File

@ -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) {}
}
}

View File

@ -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 &copy) = 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
};
}

View 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);
}