1

migrate coroutines to threads

This commit is contained in:
churl
2022-06-05 15:52:23 +02:00
parent 4840c91707
commit 717872c2d9
6 changed files with 36 additions and 102 deletions

113
c_os/kernel/threads/Thread.asm Executable file
View File

@ -0,0 +1,113 @@
;*****************************************************************************
;* *
;* 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 *
;*****************************************************************************
%include "kernel/threads/Thread.inc"
; EXPORTIERTE FUNKTIONEN
[GLOBAL Thread_switch]
[GLOBAL Thread_start]
; IMPLEMENTIERUNG DER FUNKTIONEN
[SECTION .text]
; COROUTINE_START : Startet die erste Coroutine ueberhaupt.
;
; C Prototyp: void Coroutine_start (struct CoroutineState* regs);
;; Coroutine_start is called with one arg: CoroutineState* regs, so the main stack looks like this:
;; NOTE: Since this assembly is not generated by the compiler, there is no ebp prelude.
;; To address parameters we use esp.
;; == High address ==
;; *REGS
;; ESP: RET ADDR
;; == Low address ==
Thread_start:
; *
; * Hier muss Code eingefuegt werden
; *
;; Set eax to the address where the CoroutineState struct is in memory:
mov eax, [esp + 0x4]
;; Now eax points to the beginning of CoroutineState:
;; struct CoroutineState {
;; == Low address ==
;; EAX: void *ebx;
;; void *esi;
;; void *edi;
;; void *ebp;
;; void *esp;
;; == High address ==
;; };
;; Load the contents of CoroutineState to the registers
mov esp, [eax + esp_offset]
;; The stackpointer now points to the coroutine stack
;; struct CoroutineState {
;; == High address ==
;; *OBJECT
;; 0x13115
;; SP: *KICKOFF
;; == Low address ==
;; };
;; Return to kickoff
ret
; COROUTINE_SWITCH : Coroutinenumschaltung. Der aktuelle Registersatz wird
; gesichert und der Registersatz der neuen Coroutine
; wird in den Prozessor eingelesen.
;
; C Prototyp: void Coroutine_switch (struct CoroutineState* regs_now, struct CoroutineState* reg_then);
;
; Achtung: Die Parameter werden von rechts nach links uebergeben.
;
;; == High address ==
;; *REGS_THEN
;; *REGS_NOW
;; SP --> RET ADDR
;; == Low address ==
Thread_switch:
; *
; * Hier muss Code eingefuegt werden
; *
;; Make eax point to beginning of regs_now in memory
mov eax, [esp + 0x4]
;; Save current coroutine registers
mov [eax + ebx_offset], ebx
mov [eax + esi_offset], esi
mov [eax + edi_offset], edi
mov [eax + ebp_offset], ebp
mov [eax + esp_offset], esp
;; Make eax point to beginning of regs_then in memory
mov eax, [esp + 0x8]
;; Load next coroutine registers
mov ebx, [eax + ebx_offset]
mov esi, [eax + esi_offset]
mov edi, [eax + edi_offset]
mov ebp, [eax + ebp_offset]
mov esp, [eax + esp_offset]
;; Stackpointer now points to kickoff address, ret jumps there
;; NOTE: The stackpointer only points to kickoff if the next coroutine was just initialized.
;; Otherwise it just points somewhere in the next coroutines stack
ret

126
c_os/kernel/threads/Thread.cc Executable file
View File

@ -0,0 +1,126 @@
/*****************************************************************************
* *
* C O R O U T I N E *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Implementierung eines Koroutinen-Konzepts. *
* Die Koroutinen sind miteinander verkettet, weswegen die *
* Klasse Coroutine ein Subtyp von 'Chain' ist. *
* *
* Im Konstruktor wird der initialie Kontext der Koroutine *
* eingerichtet. Mit 'start' wird ein Koroutine aktiviert. *
* Das Umschalten auf die naechste Koroutine erfolgt durch *
* Aufruf von 'switchToNext'. *
* *
* Um bei einem Koroutinenwechsel den Kontext sichern zu *
* koennen, enthaelt jedes Koroutinenobjekt eine Struktur *
* CoroutineState, in dem die Werte der nicht-fluechtigen *
* Register gesichert werden koennen. *
* *
* Autor: Michael, Schoettner, HHU, 13.08.2020 *
*****************************************************************************/
#include "kernel/threads/Thread.h"
// 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(struct ThreadState* regs);
void Thread_switch(struct ThreadState* regs_now, struct ThreadState* reg_then);
}
unsigned int ThreadCnt = 0;
/*****************************************************************************
* Prozedur: Coroutine_init *
*---------------------------------------------------------------------------*
* Beschreibung: Bereitet den Kontext der Koroutine fuer den ersten *
* Aufruf vor. *
*****************************************************************************/
void Thread_init(struct ThreadState* regs, unsigned int* stack, void (*kickoff)(Thread*), void* object) {
register unsigned int** sp = (unsigned int**)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.
*(--sp) = (unsigned int*)object; // Parameter
*(--sp) = (unsigned int*)0x131155; // Ruecksprungadresse (Dummy)
// Nun legen wir noch die Adresse der Funktion "kickoff" ganz oben auf
// den Stack. Wenn dann bei der ersten Aktivierung dieser Koroutine der
// Stackpointer so initialisiert wird, dass er auf diesen Eintrag
// verweist, genuegt ein ret, um die Funktion kickoff zu starten.
// Genauso sollen auch alle spaeteren Threadwechsel ablaufen.
*(--sp) = (unsigned int*)kickoff; // Adresse
// Initialisierung der Struktur ThreadState mit den Werten, die die
// nicht-fluechtigen Register beim ersten Starten haben sollen.
// Wichtig ist dabei nur der Stackpointer.
regs->ebx = 0;
regs->esi = 0;
regs->edi = 0;
regs->ebp = 0;
regs->esp = sp; // esp now points to the location of the address of kickoff
}
/*****************************************************************************
* 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. *
*****************************************************************************/
void kickoff(Thread* object) {
object->run();
// object->run() kehrt hoffentlich nie hierher zurueck
for (;;) {}
}
/*****************************************************************************
* Methode: Coroutine::Coroutine *
*---------------------------------------------------------------------------*
* Beschreibung: Initialer Kontext einer Koroutine einrichten. *
* *
* Parameter: *
* stack Stack für die neue Koroutine *
*****************************************************************************/
Thread::Thread() : stack(new unsigned int[1024]), tid(ThreadCnt++) {
Thread_init(&regs, stack + 1024, kickoff, this); // Stack grows from top to bottom
}
/*****************************************************************************
* Methode: Coroutine::switchToNext *
*---------------------------------------------------------------------------*
* Beschreibung: Auf die nächste Koroutine umschalten. *
*****************************************************************************/
void Thread::switchTo(Thread& next) {
/* hier muss Code eingefügt werden */
Thread_switch(&this->regs, &next.regs);
}
/*****************************************************************************
* Methode: Coroutine::start *
*---------------------------------------------------------------------------*
* Beschreibung: Aktivierung der Koroutine. *
*****************************************************************************/
void Thread::start() {
/* hier muss Code eingefügt werden */
Thread_start(&this->regs);
}

View File

@ -25,30 +25,27 @@
#include "lib/Chain.h"
class Thread : public Chain {
private:
Thread(const Thread &copy); // Verhindere Kopieren
Thread(const Thread& copy) = delete; // Verhindere Kopieren
private:
struct ThreadState regs;
unsigned int *stack;
public:
unsigned int tid; // Thread-ID (wird im Konstruktor vergeben)
unsigned int* stack;
public:
unsigned int tid; // Thread-ID (wird im Konstruktor vergeben)
Thread();
Thread () { }
// Thread aktivieren
void start ();
void start();
// Umschalten auf Thread 'next'
void switchTo (Thread& next);
void switchTo(Thread& next);
// Methode des Threads, muss in Sub-Klasse implementiert werden
virtual void run () = 0;
virtual void run() = 0;
~Thread ();
};
~Thread();
};
#endif

26
c_os/kernel/threads/Thread.inc Executable file
View File

@ -0,0 +1,26 @@
;*****************************************************************************
;* *
;* 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 *
;*****************************************************************************
; Nicht-fluechtige Register des Intel 80386 Prozessors
; Die folgenden Angaben belegen ebx_offset mit dem Wert 0, esi_offset mit 4
; edi_offset mit 8 usw. Dies entspricht den Abstaenden der Elemente ebx, esi,
; edi usw. der Struktur CoroutineState zum Anfang der Struktur (in Bytes).
[ABSOLUTE 0]
ebx_offset: resd 1
esi_offset: resd 1
edi_offset: resd 1
ebp_offset: resd 1
esp_offset: resd 1

View File

@ -0,0 +1,39 @@
/*****************************************************************************
* *
* C O R O U T I N E S T A T E *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Die Struktur CoroutineState dient dazu, bei einem *
* Koroutinenwechsel die Werte der nicht-fluechtigen *
* Register zu sichern. Beim GNU C Compiler sind eax, ecx *
* und edx fluechtige Register, die bei Funktionsaufrufen *
* und somit auch bei einem Koroutinenwechsel keine spaeter *
* noch benoetigten Werte haben duerfen. Daher muss in der *
* Struktur CoroutineState auch kein Platz fuer sie bereit- *
* gestellt werden. *
* *
* Achtung: Fuer den Zugriff auf die Elemente von *
* struct CoroutineState aus einer Assemblerfunktion *
* heraus werden in der Datei Coroutine.inc Namen fuer die *
* benoetigten Abstaende der einzelnen Elemente zum Anfang *
* der Struktur definiert. Damit dann auch auf die richtigen*
* Elemente zugegriffen wird, sollten sich die Angaben von *
* CoroutineState.h und Coroutine.inc exakt entsprechen. *
* Wer also CoroutineState.h aendert, muss auch *
* Coroutine.inc anpassen (und umgekehrt.) *
* *
* Autor: Olaf Spinczyk, TU Dortmund *
*****************************************************************************/
#ifndef __CoroutineState_include__
#define __CoroutineState_include__
struct ThreadState {
void* ebx;
void* esi;
void* edi;
void* ebp;
void* esp;
};
#endif