From 01d5b76e6d869f1161286c0aef97801043118e53 Mon Sep 17 00:00:00 2001 From: ChUrl Date: Thu, 8 Dec 2022 20:16:08 +0100 Subject: [PATCH] Cleanup Thread --- cmake/kernel/process/CMakeLists.txt | 1 + src/kernel/process/Thread.asm | 62 +++++++------ src/kernel/process/Thread.cc | 130 ++++++++-------------------- src/kernel/process/Thread.h | 73 +++++++--------- src/kernel/process/thread_asm.cpp | 23 +++++ 5 files changed, 118 insertions(+), 171 deletions(-) create mode 100644 src/kernel/process/thread_asm.cpp diff --git a/cmake/kernel/process/CMakeLists.txt b/cmake/kernel/process/CMakeLists.txt index d8b6eec..0eb8de4 100644 --- a/cmake/kernel/process/CMakeLists.txt +++ b/cmake/kernel/process/CMakeLists.txt @@ -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 ) diff --git a/src/kernel/process/Thread.asm b/src/kernel/process/Thread.asm index 36da5b0..1a8c8c4 100755 --- a/src/kernel/process/Thread.asm +++ b/src/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 diff --git a/src/kernel/process/Thread.cc b/src/kernel/process/Thread.cc index f5f2bc5..3a575d0 100755 --- a/src/kernel/process/Thread.cc +++ b/src/kernel/process/Thread.cc @@ -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(object); - stack[-2] = 0x131155U; - stack[-3] = reinterpret_cast(kickoff); - stack[-4] = 0; // EAX - stack[-5] = 0; // ECX - stack[-6] = 0; // EDX - stack[-7] = 0; // EBX - stack[-8] = reinterpret_cast(&stack[-3]); // ESP - stack[-9] = 0; // EBP - stack[-10] = 0; // ESI - stack[-11] = 0; // EDI - stack[-12] = 0x200U; - - *esp = reinterpret_cast(&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(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(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(&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(&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) {} +} + } diff --git a/src/kernel/process/Thread.h b/src/kernel/process/Thread.h index ea710aa..723f62b 100644 --- a/src/kernel/process/Thread.h +++ b/src/kernel/process/Thread.h @@ -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 +#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 }; } diff --git a/src/kernel/process/thread_asm.cpp b/src/kernel/process/thread_asm.cpp new file mode 100644 index 0000000..b8b1268 --- /dev/null +++ b/src/kernel/process/thread_asm.cpp @@ -0,0 +1,23 @@ +#include + +// 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); + +} +