1

Setup cmake project

This commit is contained in:
2022-12-07 16:40:43 +01:00
parent db2816a092
commit f304e7f239
138 changed files with 28939 additions and 0 deletions

View File

@ -0,0 +1,40 @@
#include "ArrayDemo.h"
void ArrayDemo::run() {
bse::array<int, 10> arr1 {};
bse::array<int, 10> arr2 {};
kout.lock();
kout.clear();
kout << "Adding..." << endl;
for (int i = 0; i < 10; ++i) {
arr1[i] = i;
}
kout << "Iterator printing arr1:" << endl;
for (int i : arr1) {
kout << i << " ";
}
kout << endl;
kout << "Swapping arr1 and arr2..." << endl;
arr1.swap(arr2);
kout << "Iterator printing arr1:" << endl;
for (int i : arr1) {
kout << i << " ";
}
kout << endl;
kout << "Iterator printing arr2:" << endl;
for (int i : arr2) {
kout << i << " ";
}
kout << endl;
// arr1.swap(arr3); // Not possible as type/size has to match
kout.unlock();
scheduler.exit();
}

View File

@ -0,0 +1,16 @@
#ifndef ArrayDemo_include__
#define ArrayDemo_include__
#include "kernel/system/Globals.h"
#include "lib/util/Array.h"
class ArrayDemo : public Thread {
public:
ArrayDemo(const ArrayDemo& copy) = delete;
ArrayDemo() : Thread("ArrayDemo") {}
void run() override;
};
#endif

78
src/kernel/demo/HeapDemo.cc Executable file
View File

@ -0,0 +1,78 @@
/*****************************************************************************
* *
* H E A P D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Demonstration der dynamischen Speicherverwaltung. *
* *
* Autor: Michael Schoettner, HHU, 27.12.2016 *
*****************************************************************************/
#include "HeapDemo.h"
void HeapDemo::run() {
kout.lock();
kout.clear();
kout << "HEAP_DEMO ===================================================================" << endl;
/* hier muss Code eingefuegt werden */
allocator.dump_free_memory();
// Rounding to word border
kout << "ROUNDING ====================================================================" << endl;
void* alloc = allocator.alloc(1); // 1 Byte
allocator.dump_free_memory();
allocator.free(alloc);
allocator.dump_free_memory();
// Some objects and forward/backward merging
kout << "SOME OBJECTS ================================================================" << endl;
MyObj* a = new MyObj(5);
allocator.dump_free_memory();
MyObj* b = new MyObj(10);
allocator.dump_free_memory();
MyObj* c = new MyObj(15);
allocator.dump_free_memory();
delete b; // No merge
allocator.dump_free_memory();
delete a; // Merge forward BUG: Bluescreen
allocator.dump_free_memory();
delete c;
allocator.dump_free_memory();
// Allocate too whole heap
// void* ptr = allocator.alloc(1024 * 1024 - 24);
// allocator.dump_free_memory();
// allocator.free(ptr);
// allocator.dump_free_memory();
// Allocate too much
kout << "TOO MUCH ====================================================================" << endl;
allocator.alloc(1024 * 1024); // should fail as only 1024 * 1024 - Headersize bytes are available
allocator.dump_free_memory();
// A lot of allocations
// MyObj* objs[1024];
// for (unsigned int i = 0; i < 1024; ++i) {
// objs[i] = new MyObj(5);
// }
// allocator.dump_free_memory();
// waitForReturn();
// for (unsigned int i = 0; i < 1024; ++i) {
// delete objs[i];
// }
// allocator.dump_free_memory();
// Array allocation
kout << "ARRAY =======================================================================" << endl;
MyObj* objs = new MyObj[1024];
allocator.dump_free_memory();
delete[] objs;
allocator.dump_free_memory();
kout << "HEAP_DEMO END ===============================================================" << endl;
kout.unlock();
scheduler.exit();
}

32
src/kernel/demo/HeapDemo.h Executable file
View File

@ -0,0 +1,32 @@
/*****************************************************************************
* *
* H E A P D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Demonstration der dynamischen Speicherverwaltung. *
* *
* Autor: Michael Schoettner, HHU, 25.9.2016 *
*****************************************************************************/
#ifndef HeapDemo_include__
#define HeapDemo_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
class MyObj {
public:
constexpr MyObj() : value(5) {};
constexpr MyObj(const unsigned int val) : value(val) {};
const unsigned int value;
};
class HeapDemo : public Thread {
public:
HeapDemo(const HeapDemo& copy) = delete;
HeapDemo() : Thread("HeapDemo") {}
void run() override;
};
#endif

34
src/kernel/demo/KeyboardDemo.cc Executable file
View File

@ -0,0 +1,34 @@
/*****************************************************************************
* *
* K E Y B O A R D D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Testausgaben für den CGA-Treiber. *
* *
* Autor: Michael Schoettner, HHU, 26.10.2018 *
*****************************************************************************/
#include "KeyboardDemo.h"
void KeyboardDemo::run() {
/* Hier muess Code eingefuegt werden */
kout << "Keyboard Demo: " << endl;
kout.lock();
kout.clear();
kout << "Info: Die Keyboard Demo sperrt den Output Stream:\n"
<< " Wenn die Preemption Demo laeuft wird diese also erst\n"
<< " fortfahren wenn die Keyboard Demo wieder beendet ist." << endl;
kout << "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nInput: ";
kout.flush();
while (running) {
kout << listener.waitForKeyEvent();
kout.flush();
}
kout.unlock();
scheduler.exit();
}

44
src/kernel/demo/KeyboardDemo.h Executable file
View File

@ -0,0 +1,44 @@
/*****************************************************************************
* *
* K E Y B O A R D D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Testausgaben für den CGA-Treiber. *
* *
* Autor: Michael Schoettner, HHU, 26.10.2018 *
*****************************************************************************/
#ifndef KeyboardDemo_include__
#define KeyboardDemo_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
#include "kernel/event/KeyEventListener.h"
class KeyboardDemo : public Thread {
private:
KeyEventListener listener;
public:
KeyboardDemo(const KeyboardDemo& copy) = delete;
KeyboardDemo() : Thread("KeyboardDemo"), listener(tid) {
kevman.subscribe(listener);
}
// Base class destructor will be called automatically
~KeyboardDemo() override {
if (running) {
// NOTE: If the thread was exited nicely it can unlock before destructor,
// but on forced kill kout has to be unlocked in the destructor.
// This is bad since it could release the lock although some other
// thread set it (so use nice_kill)
kout.unlock();
}
kevman.unsubscribe(listener);
}
void run() override;
};
#endif

View File

@ -0,0 +1,90 @@
#include "MainMenu.h"
#include "ArrayDemo.h"
#include "HeapDemo.h"
#include "KeyboardDemo.h"
#include "PagingDemo.h"
#include "PCSPKdemo.h"
#include "PreemptiveThreadDemo.h"
#include "SmartPointerDemo.h"
#include "StringDemo.h"
#include "TextDemo.h"
#include "VBEdemo.h"
#include "VectorDemo.h"
void print_demo_menu() {
kout.lock();
kout.clear();
kout << "Demo Menu, press number to start, k/K to kill:\n"
<< "1 - Text Demo\n"
<< "2 - PCSPK Demo\n"
<< "3 - Keyboard Demo\n"
<< "4 - Heap Demo\n"
<< "5 - VBE Demo\n"
<< "6 - Paging Demo\n"
<< "7 - Preemption Demo\n"
<< "Extra demos:\n"
<< "8 - bse::vector demo\n"
<< "9 - bse::array demo\n"
<< "0 - bse::unique_ptr demo\n"
<< "! - bse::string demo\n"
<< endl;
kout.unlock();
}
void MainMenu::run() {
print_demo_menu();
char input = '\0';
unsigned int running_demo = 0;
while (running) {
input = listener.waitForKeyEvent();
if ((input >= '0' && input <= '9') || input == '!') {
switch (input) {
case '1':
running_demo = scheduler.ready<TextDemo>();
break;
case '2':
running_demo = scheduler.ready<PCSPKdemo>(&PCSPK::aerodynamic);
break;
case '3':
running_demo = scheduler.ready<KeyboardDemo>();
break;
case '4':
running_demo = scheduler.ready<HeapDemo>();
break;
case '5':
running_demo = scheduler.ready<VBEdemo>();
break;
case '6':
running_demo = scheduler.ready<PagingDemo>();
break;
case '7':
running_demo = scheduler.ready<PreemptiveThreadDemo>(3);
break;
case '8':
running_demo = scheduler.ready<VectorDemo>();
break;
case '9':
running_demo = scheduler.ready<ArrayDemo>();
break;
case '0':
running_demo = scheduler.ready<SmartPointerDemo>();
break;
case '!':
running_demo = scheduler.ready<StringDemo>();
break;
}
} else if (input == 'k') {
scheduler.nice_kill(running_demo); // NOTE: If thread exits itself this will throw error
print_demo_menu();
} else if (input == 'K') {
scheduler.kill(running_demo);
print_demo_menu();
}
}
scheduler.exit();
// This thread won't be deleted...
}

View File

@ -0,0 +1,26 @@
#ifndef MainMenu_Inlucde_H_
#define MainMenu_Inlucde_H_
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
#include "kernel/event/KeyEventListener.h"
class MainMenu : public Thread {
private:
KeyEventListener listener;
public:
MainMenu(const MainMenu& copy) = delete;
MainMenu() : Thread("MainMenu"), listener(tid) {
kevman.subscribe(listener);
}
~MainMenu() override {
kevman.unsubscribe(listener);
}
void run() override;
};
#endif

View File

@ -0,0 +1,16 @@
#include "PCSPKdemo.h"
void PCSPKdemo::run() {
kout.lock();
kout.clear();
kout << "Playing..." << endl;
kout.unlock();
(*melody)(); // This syntax is confusing as hell
kout.lock();
kout << "Finished" << endl;
kout.unlock();
scheduler.exit();
}

View File

@ -0,0 +1,23 @@
#ifndef PCSPKdemo_INCLUDE_H_
#define PCSPKdemo_INCLUDE_H_
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
class PCSPKdemo : public Thread {
private:
void (*melody)(); // Allow to pass a melody to play when initializing the demo
public:
PCSPKdemo(const PCSPKdemo& copy) = delete;
PCSPKdemo(void (*melody)()) : Thread("PCSPKdemo"), melody(melody) {}
~PCSPKdemo() override {
PCSPK::off();
}
void run() override;
};
#endif

View File

@ -0,0 +1,50 @@
#include "PagingDemo.h"
void PagingDemo::writeprotect_page() {
kout << "Accessing a writeprotected page triggers bluescreen,\nif you can read this it didn't work" << endl;
// BlueScreen 1
// asm("int $3");
// BlueScreen 2
log.info() << "Allocating page" << endl;
unsigned int* page = pg_alloc_page();
*page = 42;
log.info() << "Writeprotecting page..." << endl;
pg_write_protect_page(page);
log.info() << "Accessing writeprotected page" << endl;
*page = 42; // We map logical to physical 1:1 so no need to do any lookup
// No free because bluescreen
}
void PagingDemo::notpresent_page() {
kout << "Produces pagefault, if you can read this it didn't work" << endl;
log.info() << "Allocating page" << endl;
unsigned int* page = pg_alloc_page();
*page = 42;
log.info() << "Marking page notpresent..." << endl;
pg_notpresent_page(page);
log.info() << "Accessing page" << endl;
kout << "Page not accessible: " << *page << endl;
// No free because bluescreen
}
void PagingDemo::run() {
kout << "Press w for writeprotect demo, n for notpresent demo" << endl;
switch(listener.waitForKeyEvent()) {
case 'w':
writeprotect_page();
break;
case 'n':
notpresent_page();
break;
}
scheduler.exit();
}

View File

@ -0,0 +1,30 @@
#ifndef BlueScreenDemo_include__
#define BlueScreenDemo_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
#include "kernel/event/KeyEventListener.h"
class PagingDemo: public Thread {
private:
void writeprotect_page();
void free_page();
void notpresent_page();
KeyEventListener listener;
public:
PagingDemo(const PagingDemo& copy) = delete;
PagingDemo(): Thread("PagingDemo"), listener(tid) {
kevman.subscribe(listener);
}
~PagingDemo() override {
kevman.unsubscribe(listener);
}
void run() override;
};
#endif

View File

@ -0,0 +1,34 @@
#include "PreemptiveThreadDemo.h"
void PreemptiveLoopThread::run() {
int cnt = 0;
while (running) {
// Basic synchronization by semaphore
kout.lock();
// Saving + restoring kout position doesn't help much as preemption still occurs
CGA_Stream::setpos(55, id);
kout << fillw(3) << id << fillw(0) << ": " << dec << cnt++ << endl;
kout.unlock();
}
scheduler.exit();
}
void PreemptiveThreadDemo::run() {
kout.lock();
kout.clear();
kout << "Preemptive Thread Demo:" << endl;
kout << "Readying LoopThreads" << endl;
for (unsigned int i = 0; i < number_of_threads; ++i) {
scheduler.ready<PreemptiveLoopThread>(i);
}
kout << "Exiting main thread" << endl;
kout.unlock();
scheduler.exit();
}

View File

@ -0,0 +1,35 @@
#ifndef preemptive_thread_include__
#define preemptive_thread_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
#include "lib/async/Semaphore.h"
class PreemptiveLoopThread : public Thread {
private:
int id;
public:
PreemptiveLoopThread(const PreemptiveLoopThread& copy) = delete; // Verhindere Kopieren
// Gibt der Loop einen Stack und eine Id.
PreemptiveLoopThread(int i) : Thread("LoopThread"), id(i) {}
// Zaehlt einen Zaehler hoch und gibt ihn auf dem Bildschirm aus.
void run() override;
};
class PreemptiveThreadDemo : public Thread {
private:
unsigned int number_of_threads;
public:
PreemptiveThreadDemo(const PreemptiveThreadDemo& copy) = delete; // Verhindere Kopieren
PreemptiveThreadDemo(unsigned int n) : Thread("PreemptiveThreadDemo"), number_of_threads(n) {}
// Thread-Startmethode
void run() override;
};
#endif

View File

@ -0,0 +1,122 @@
#include "SmartPointerDemo.h"
#include "kernel/process/IdleThread.h"
void SmartPointerDemo::run() {
kout.lock();
kout.clear();
kout << "Output is written to log to be able to trace memory allocations/deallocations" << endl;
{
log.info() << "Allocating new unique_ptr<int>..." << endl;
bse::unique_ptr<int> ptr = bse::make_unique<int>(1);
log.info() << "Leaving scope..." << endl;
}
log.info() << "Should be deleted by now..." << endl;
{
log.info() << "Allocating new unique_ptr<int>..." << endl;
bse::unique_ptr<int> ptr1 = bse::make_unique<int>(1);
bse::unique_ptr<int> ptr2;
log.info() << "*ptr1 == " << *ptr1 << ", (bool)ptr2 == " << static_cast<bool>(ptr2) << endl;
log.info() << "Moving ptr1 => ptr2 (no allocations should happen)..." << endl;
ptr2 = std::move(ptr1);
log.info() << "(bool)ptr1 == " << static_cast<bool>(ptr1) << ", *ptr2 == " << *ptr2 << endl;
log.info() << "Leaving scope..." << endl;
}
log.info() << "Should be deleted by now..." << endl;
{
log.info() << "Allocating (2) new unique_ptr<int>..." << endl;
bse::unique_ptr<int> ptr1 = bse::make_unique<int>(1);
bse::unique_ptr<int> ptr2 = bse::make_unique<int>(1);
log.info() << "Moving ptr1 => ptr2 (ptr1 should be freed)..." << endl;
ptr2 = std::move(ptr1);
log.info() << "Leaving scope..." << endl;
}
log.info() << "Should be deleted by now..." << endl;
// =====================================================================
{
log.info() << "Allocating new unique_ptr<int[]>..." << endl;
bse::unique_ptr<int[]> ptr = bse::make_unique<int[]>(10);
ptr[0] = 1;
log.info() << "ptr[0] == " << ptr[0] << endl;
}
log.info() << "Should be deleted by now..." << endl;
{
log.info() << "Allocating new unique_ptr<int[10]>..." << endl;
bse::unique_ptr<int[]> ptr1 = bse::make_unique<int[]>(10);
bse::unique_ptr<int[]> ptr2;
log.info() << "Moving ptr1 => ptr2 (no allocations should happen)..." << endl;
ptr2 = std::move(ptr1);
log.info() << "Leaving scope..." << endl;
}
log.info() << "Should be deleted by now..." << endl;
{
log.info() << "Allocating (2) new unique_ptr<int[10]>..." << endl;
bse::unique_ptr<int> ptr1 = bse::make_unique<int>(10);
bse::unique_ptr<int> ptr2 = bse::make_unique<int>(10);
log.info() << "Moving ptr1 => ptr2 (ptr1 should be freed)..." << endl;
ptr2 = std::move(ptr1);
log.info() << "Leaving scope..." << endl;
}
log.info() << "Should be deleted by now..." << endl;
// NOTE: This wasn't working because of a missing operator[] delete in the allocator
log.info() << "Allocating unique_ptr<int>*..." << endl;
bse::unique_ptr<int>* ptrptr = new bse::unique_ptr<int>[10];
delete[] ptrptr;
log.info() << "Should be deleted by now..." << endl;
// =====================================================================
{
log.info() << "Stackallocating Array<bse::unique_ptr<int>, 10>..." << endl;
bse::array<bse::unique_ptr<int>, 10> arr;
log.info() << "Populating slot 0..." << endl;
arr[0] = bse::make_unique<int>(1);
log.info() << "Moving slot 0 to slot 1..." << endl;
arr[1] = std::move(arr[0]);
log.info() << "Leaving scope" << endl;
}
log.info() << "Should be deleted by now..." << endl;
{
log.info() << "Heapallocating Array<bse::unique_ptr<int>, 10>..." << endl;
bse::array<bse::unique_ptr<int>, 10>* arr = new bse::array<bse::unique_ptr<int>, 10>;
log.info() << "Populating slot 0..." << endl;
(*arr)[0] = bse::make_unique<int>(1);
log.info() << "Moving slot 0 to slot 1..." << endl;
(*arr)[1] = std::move((*arr)[0]);
log.info() << "Deleting" << endl;
delete arr;
log.info() << "Leaving scope" << endl;
}
log.info() << "Should be deleted by now..." << endl;
{
log.info() << "ArrayList<bse::unique_ptr<int>>..." << endl;
bse::vector<bse::unique_ptr<int>> vec;
log.info() << "2x insertion" << endl;
vec.push_back(bse::make_unique<int>(1));
vec.push_back(bse::make_unique<int>(2));
log.info() << "Leaving scope" << endl;
}
log.info() << "Should be deleted by now..." << endl;
kout.unlock();
scheduler.exit();
}

View File

@ -0,0 +1,15 @@
#ifndef SmartPointerDemo_include__
#define SmartPointerDemo_include__
#include "kernel/system/Globals.h"
class SmartPointerDemo : public Thread {
public:
SmartPointerDemo(const SmartPointerDemo& copy) = delete;
SmartPointerDemo() : Thread("SmartPointerDemo") {}
void run() override;
};
#endif

View File

@ -0,0 +1,56 @@
#include "StringDemo.h"
void StringDemo::run() {
kout.lock();
kout.clear();
log.info() << "Allocating new string" << endl;
bse::string str1 = "This is a dynamically allocated string!";
kout << str1 << endl;
log.info() << "Reassign string" << endl;
str1 = "Hello";
kout << str1 << " has length " << dec << str1.size() << endl;
kout << "Again with strlen: Hello has length " << dec << bse::strlen("Hello") << endl;
kout << "Adding strings: " << str1 << " + World" << endl;
log.info() << "Adding strings" << endl;
str1 = str1 + " World";
kout << str1 << endl;
kout << "Hello += World" << endl;
log.info() << "Hello += World" << endl;
bse::string str3 = "Hello";
str3 += " World";
kout << str3 << endl;
kout << "Hello World *= 3" << endl;
str3 *= 3;
kout << str3 << endl;
kout << "String iterator!" << endl;
for (const char c : str1) {
kout << c << " ";
}
kout << endl;
log.info() << "Allocating new string" << endl;
bse::string str2 = "Hello World";
kout << "str1 == str2: " << static_cast<int>(str1 == str2) << endl;
kout << "strcmp(Hello, Hello): " << bse::strcmp("Hello", "Hello") << endl;
log.info() << "Reassign str2" << endl;
str2 = "Hello";
bse::array<char, 5> arr{};
arr[0] = 'H';
arr[1] = 'e';
arr[2] = 'l';
arr[3] = 'l';
arr[4] = 'o';
kout << "bse::array<char, 5> to bse::string: " << static_cast<bse::string>(arr) << ", size: " << (bse::string(arr)).size() << endl;
kout << "(bse::string)arr (" << static_cast<bse::string>(arr) << ") == str2 (" << str2 << "): " << static_cast<int>(bse::string(arr) == str2) << endl;
kout.unlock();
scheduler.exit();
}

View File

@ -0,0 +1,15 @@
#ifndef StringDemo_include__
#define StringDemo_include__
#include "kernel/system/Globals.h"
class StringDemo : public Thread {
public:
StringDemo(const StringDemo& copy) = delete;
StringDemo() : Thread("StringDemo") {}
void run() override;
};
#endif

43
src/kernel/demo/TextDemo.cc Executable file
View File

@ -0,0 +1,43 @@
/*****************************************************************************
* *
* T E X T D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Testausgaben für den CGA-Treiber. *
* *
* Autor: Michael Schoettner, HHU, 26.10.2018 *
*****************************************************************************/
#include "TextDemo.h"
void TextDemo::run() {
/* Hier muess Code eingefuegt werden */
kout.lock();
kout.clear();
kout << "TextDemo\n"
<< endl;
kout << "Attribut (GREEN on WHITE): "
<< bgc(CGA::WHITE) << green << "GREEN on WHITE" << endl
<< "Attribut (WHITE on BLACK): "
<< bgc(CGA::BLACK) << white << "WHITE on BLACK" << endl;
kout << endl;
kout << "Test der Zahlenausgabefunktion:" << endl
<< "| dec | hex | bin |" << endl
<< "+-------+-------+-------+" << endl;
for (unsigned short num = 0; num < 17; ++num) {
kout << fillw(0) << "| " << fillw(6) << dec << num
<< fillw(0) << "| " << fillw(6) << hex << num
<< fillw(0) << "| " << fillw(6) << bin << num
<< fillw(0) << "|" << endl;
}
kout << endl;
kout.unlock();
scheduler.exit();
}

26
src/kernel/demo/TextDemo.h Executable file
View File

@ -0,0 +1,26 @@
/*****************************************************************************
* *
* T E X T D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Testausgaben für den CGA-Treiber. *
* *
* Autor: Michael Schoettner, HHU, 26.10.2018 *
*****************************************************************************/
#ifndef TextDemo_include__
#define TextDemo_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
class TextDemo : public Thread {
public:
TextDemo(const TextDemo& copy) = delete;
TextDemo() : Thread("TextDemo") {}
void run() override;
};
#endif

106
src/kernel/demo/VBEdemo.cc Normal file
View File

@ -0,0 +1,106 @@
/*****************************************************************************
* *
* V B E D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Demo zu VESA. *
* *
* Autor: Michael Schoettner, HHU, 26.12.2016 *
*****************************************************************************/
#include "VBEdemo.h"
#include "bmp_hhu.cc"
/*****************************************************************************
* Methode: VBEdemo::linInterPol1D *
*---------------------------------------------------------------------------*
* Beschreibung: Farbwert in einer Dimension interpoliert berechnen. *
*****************************************************************************/
int VBEdemo::linInterPol1D(int x, int xr, int l, int r) {
return ((((l >> 16) * (xr - x) + (r >> 16) * x) / xr) << 16) | (((((l >> 8) & 0xFF) * (xr - x) + ((r >> 8) & 0xFF) * x) / xr) << 8) | (((l & 0xFF) * (xr - x) + (r & 0xFF) * x) / xr);
}
/*****************************************************************************
* Methode: VBEdemo::linInterPol2D *
*---------------------------------------------------------------------------*
* Beschreibung: Farbwert in zwei Dimensionen interpoliert berechnen. *
*****************************************************************************/
int VBEdemo::linInterPol2D(int x, int y, int lt, int rt, int lb, int rb) {
return linInterPol1D(y, vesa.yres,
linInterPol1D(x, vesa.xres, lt, rt),
linInterPol1D(x, vesa.xres, lb, rb));
}
/*****************************************************************************
* Methode: VBEdemo::drawColors *
*---------------------------------------------------------------------------*
* Beschreibung: Pixel-Demo. *
*****************************************************************************/
void VBEdemo::drawColors() {
int x_res = 640;
int y_res = 480;
for (int y = 0; y < y_res; y++) {
for (int x = 0; x < x_res; x++) {
vesa.drawPixel(x, y, linInterPol2D(x, y, 0x0000FF, 0x00FF00, 0xFF0000, 0xFFFF00));
}
}
}
/*****************************************************************************
* Methode: VBEdemo::drawBitmap *
*---------------------------------------------------------------------------*
* Beschreibung: Bitmap aus GIMP ausgeben. *
*****************************************************************************/
void VBEdemo::drawBitmap() {
unsigned int sprite_width = hhu.width;
unsigned int sprite_height = hhu.height;
unsigned int sprite_bpp = hhu.bytes_per_pixel;
const unsigned char* sprite_pixel = reinterpret_cast<const unsigned char*>(hhu.pixel_data);
/* Hier muss Code eingefuegt werden */
vesa.drawSprite(sprite_width, sprite_height, sprite_bpp, sprite_pixel);
}
/*****************************************************************************
* Methode: VBEdemo::drawFonts *
*---------------------------------------------------------------------------*
* Beschreibung: Fonts ausgeben. *
*****************************************************************************/
void VBEdemo::drawFonts() {
/* Hier muss Code eingefuegt werden */
vesa.drawString(std_font_8x8, 0, 300, 0, "STD FONT 8x8", 12);
vesa.drawString(std_font_8x16, 0, 320, 0, "STD FONT 8x16", 13);
vesa.drawString(acorn_font_8x8, 0, 340, 0, "ACORN FONT 8x8", 14);
vesa.drawString(sun_font_8x16, 0, 360, 0, "SUN FONT 8x16", 13);
vesa.drawString(sun_font_12x22, 0, 380, 0, "SUN FONT 12x22", 14);
vesa.drawString(pearl_font_8x8, 0, 400, 0, "PEARL FONT 8x8", 14);
}
/*****************************************************************************
* Methode: VBEdemo::run *
*---------------------------------------------------------------------------*
* Beschreibung: Der Anwendungsthread erzeugt drei Threads die Zaehler *
* ausgeben und terminiert sich selbst. *
*****************************************************************************/
void VBEdemo::run() {
// In den Grafikmodus schalten (32-Bit Farbtiefe)
vesa.initGraphicMode(MODE_640_480_24BITS);
vesa.setDrawingBuff(BUFFER_VISIBLE);
drawColors();
/* Hier muss Code eingefuegt werden */
vesa.drawRectangle(100, 100, 300, 300, 0);
drawBitmap();
drawFonts();
while (running) {}
// selbst terminieren
scheduler.exit();
}

46
src/kernel/demo/VBEdemo.h Normal file
View File

@ -0,0 +1,46 @@
/*****************************************************************************
* *
* V B E D E M O *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Demo zu VESA. *
* *
* Autor: Michael Schoettner, HHU, 26.12.2016 *
*****************************************************************************/
#ifndef VBEdemo_include__
#define VBEdemo_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
class VBEdemo : public Thread {
private:
// Hilfsfunktionen fuer drawColors()
static int linInterPol1D(int x, int xr, int l, int r);
static int linInterPol2D(int x, int y, int lt, int rt, int lb, int rb);
public:
VBEdemo(const VBEdemo& copy) = delete; // Verhindere Kopieren
// Gib dem Anwendungsthread einen Stack.
VBEdemo() : Thread("VBEdemo") {}
~VBEdemo() override {
allocator.free(reinterpret_cast<void*>(vesa.hfb)); // Memory is allocated after every start and never deleted, so add that
VESA::initTextMode();
}
// Thread-Startmethode
void run() override;
// Farbraum ausgeben
void drawColors();
// Bitmap aus GIMP ausgeben
static void drawBitmap();
// Fonts ausgeben
static void drawFonts();
};
#endif

View File

@ -0,0 +1,110 @@
#include "VectorDemo.h"
void print(OutStream& os, const bse::vector<int>& list) {
os << "Printing List: ";
for (const int i : list) {
os << i << " ";
}
os << endl;
}
void VectorDemo::run() {
bse::vector<int> list;
kout.lock();
kout.clear();
kout << "Logs are written to serial to see the memory interactions" << endl;
log.info() << "Initial list size: " << dec << list.size() << endl;
log.info() << "Adding elements in order" << endl;
for (int i = 0; i < 5; ++i) {
list.push_back(i);
}
print(log.info(), list);
log.info() << "Removing all elements from the front" << endl;
for (unsigned int i = 0; i < 5; ++i) {
list.erase(list.begin());
}
print(log.info(), list);
// ============================================================
log.info() << "Adding elements in order with realloc" << endl;
for (int i = 0; i < 10; ++i) {
log.info() << "Add " << dec << i << endl;
list.push_back(i);
}
print(log.info(), list);
log.info() << "Removing all elements from the back" << endl;
for (unsigned int i = 0; i < 10; ++i) {
list.erase(list.end() - 1);
}
print(log.info(), list);
// ============================================================
for (int i = 0; i < 5; ++i) {
list.push_back(i);
}
print(log.info(), list);
log.info() << "Adding inside the list (at idx 0, 2, 5)" << endl;
list.insert(list.begin() + 0, 10);
list.insert(list.begin() + 2, 10);
list.insert(list.begin() + 5, 10);
print(log.info(), list);
log.info() << "Removing inside the list (at idx 0, 2, 5)" << endl;
list.erase(list.begin() + 0);
list.erase(list.begin() + 2);
list.erase(list.begin() + 5);
print(log.info(), list);
for (unsigned int i = 0; i < 5; ++i) {
list.erase(list.begin());
}
print(log.info(), list);
// ============================================================
log.info() << "Mirror scheduling behavior" << endl;
// These are the threads
int active = 0; // Idle thread
list.push_back(1);
list.push_back(2);
list.push_back(3);
print(log.info(), list);
log.info() << "Starting..." << endl;
for (unsigned int n = 0; n < 10000; ++n) {
list.push_back(active);
active = list[0];
list.erase(list.begin());
if (list.size() != 3 || active == -1) {
log.info() << "ERROR: Thread went missing" << endl;
break;
}
if (n < 5) {
print(log.info(), list);
}
}
log.info() << "Finished." << endl;
print(log.info(), list);
// ============================================================
log.info() << "Range based for support" << endl;
for (int i : list) {
log.info() << "List contains element: " << dec << i << endl;
}
kout.unlock();
scheduler.exit();
}

View File

@ -0,0 +1,17 @@
#ifndef VectorDemo_include__
#define VectorDemo_include__
#include "kernel/system/Globals.h"
#include "kernel/process/Thread.h"
#include "lib/util/Vector.h"
class VectorDemo : public Thread {
public:
VectorDemo(const VectorDemo& copy) = delete;
VectorDemo() : Thread("VectorDemo") {}
void run() override;
};
#endif

1576
src/kernel/demo/bmp_hhu.cc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
#include "kernel/event/KeyEventListener.h"
#include "kernel/system/Globals.h"
void KeyEventListener::trigger(char c) {
lastChar = c;
}
char KeyEventListener::waitForKeyEvent() const {
Logger::instance() << DEBUG << "KEvLis:: Thread with id: " << tid << " waiting for key event" << endl;
scheduler.block();
return lastChar; // This is only executed after thread is woken up by manager
}

View File

@ -0,0 +1,22 @@
#ifndef KeyEventListener_Include_H_
#define KeyEventListener_Include_H_
#include "kernel/process/Thread.h"
class KeyEventListener {
private:
char lastChar = '\0';
friend class KeyEventManager;
unsigned int tid; // Thread which contains this listener, so the listener can block the thread
public:
KeyEventListener(const KeyEventListener& copy) = delete;
KeyEventListener(unsigned int tid) : tid(tid) {}
char waitForKeyEvent() const; // Blocks the thread until woken up by manager
void trigger(char c); // Gets called from KeyEventManager
};
#endif

View File

@ -0,0 +1,26 @@
#include "KeyEventManager.h"
#include "kernel/system/Globals.h"
void KeyEventManager::subscribe(KeyEventListener& sub) {
log.debug() << "Subscribe, Thread ID: " << dec << sub.tid << endl;
listeners.push_back(&sub);
}
void KeyEventManager::unsubscribe(KeyEventListener& unsub) {
log.debug() << "Unsubscribe, Thread ID: " << dec << unsub.tid << endl;
for (bse::vector<KeyEventListener*>::iterator it = listeners.begin(); it != listeners.end(); ++it) {
if ((*it)->tid == unsub.tid) {
listeners.erase(it);
return;
}
}
}
void KeyEventManager::broadcast(char c) {
log.trace() << "Beginning Broadcast" << endl;
for (KeyEventListener* listener : listeners) {
log.trace() << "Broadcasting " << c << " to Thread ID: " << dec << listener->tid << endl;
listener->trigger(c);
scheduler.deblock(listener->tid);
}
}

View File

@ -0,0 +1,31 @@
#ifndef KeyEventManager_Include_H_
#define KeyEventManager_Include_H_
#include "kernel/event/KeyEventListener.h"
#include "kernel/log/Logger.h"
#include "lib/util/Vector.h"
// NOTE: Could do this more generally but we only have key events
// Also pretty limited: One thread can have one listener as identification is done over tid
class KeyEventManager {
private:
NamedLogger log;
bse::vector<KeyEventListener*> listeners;
public:
KeyEventManager(const KeyEventManager& copy) = delete;
KeyEventManager() : log("KEvMan"), listeners(true) {}
void init() {
listeners.reserve();
}
void subscribe(KeyEventListener& sub);
void unsubscribe(KeyEventListener& unsub);
void broadcast(char c); // Unblocks all input waiting threads, I don't have a method to direct input
};
#endif

View File

@ -0,0 +1,305 @@
/*****************************************************************************
* *
* B L U E S C R E E N *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Ein Bluescreen, falls eine x86 Exception auftritt. Evt. *
* ist der Stack und oder Heap kaputt, weswegen hier nicht *
* kout etc. verwendet wird. *
* *
* Autor: Michael Schoettner, 11.12.2018 *
*****************************************************************************/
#include "kernel/system/Globals.h"
// in startup.asm
extern "C" {
// CR2 auslesen
unsigned int get_page_fault_address();
// 1st level interrupt handler in startup.asm sichert Zeiger auf Stackframe
// unmittelbar nach dem Interrupt und nachdem alle Register mit PUSHAD
// gesichert wurden
// |-------------|
// |  EFLAGS |
// |-------------|
// | CS |
// |-------------|
// | EIP |
// |-------------|
// | [ErrorCode] |
// |-------------|
// | EAX |
// |-------------|
// | ECX |
// |-------------|
// | EDX |
// |-------------|
// | EBX |
// |-------------|
// | ESP |
// |-------------|
// | EBP |
// |-------------|
// | ESI |
// |-------------|
// | EDI |
// |-------------| <-- int_esp
void get_int_esp(unsigned int** esp);
}
void break_on_bluescreen() {
/* wenn auf diese Methode ein breakpoint in GDB gesetzt wird
so kann man sich mithilfe des Hex-Dumps umsehen
*/
}
// Cursor-Position
int bs_xpos = 0;
int bs_ypos = 0;
/*****************************************************************************
* Funktion: bs_clear *
*---------------------------------------------------------------------------*
* Beschreibung: Bildschirm loeschen. *
*****************************************************************************/
void bs_clear() {
unsigned int x;
unsigned int y;
unsigned short* ptr = reinterpret_cast<unsigned short*>(0xb8000);
for (x = 0; x < 80; x++) {
for (y = 0; y < 25; y++) {
*(ptr + y * 80 + x) = static_cast<short>(0x1F00);
}
}
bs_xpos = 0;
bs_ypos = 0;
}
/*****************************************************************************
* Funktion: bs_lf *
*---------------------------------------------------------------------------*
* Beschreibung: Zeilenvorschub. *
*****************************************************************************/
void bs_lf() {
bs_ypos++;
bs_xpos = 0;
}
/*****************************************************************************
* Funktion: bs_print_char *
*---------------------------------------------------------------------------*
* Beschreibung: Ein Zeichen ausgeben. *
*****************************************************************************/
void bs_print_char(char c) {
unsigned char* ptr = reinterpret_cast<unsigned char*>(0xb8000);
*(ptr + bs_ypos * 80 * 2 + bs_xpos * 2) = c;
bs_xpos++;
}
/*****************************************************************************
* Funktion: bs_print_string *
*---------------------------------------------------------------------------*
* Beschreibung: Eine Zeichenkette ausgeben. *
*****************************************************************************/
void bs_print_string(char* str) {
while (*str != '\0') {
bs_print_char(*str);
str++;
}
}
/*****************************************************************************
* Funktion: bs_printHexDigit *
*---------------------------------------------------------------------------*
* Beschreibung: Ein Hex-Zeichen ausgeben. *
*****************************************************************************/
void bs_printHexDigit(int c) {
if (c < 10) {
bs_print_char('0' + static_cast<unsigned char>(c));
} else {
bs_print_char('A' + static_cast<unsigned char>(c - 10));
}
}
/*****************************************************************************
* Funktion: bs_print_uintHex *
*---------------------------------------------------------------------------*
* Beschreibung: Integer ausgeben. *
*****************************************************************************/
void bs_print_uintHex(unsigned int c) {
for (int i = 28; i >= 0; i = i - 4) {
bs_printHexDigit((c >> i) & 0xF);
}
}
/*****************************************************************************
* Funktion: bs_printReg *
*---------------------------------------------------------------------------*
* Beschreibung: String mit Integer ausgeben. Wird verwendet um ein *
* Register auszugeben. *
*****************************************************************************/
void bs_printReg(char* str, unsigned int value) {
bs_print_string(str);
bs_print_uintHex(value);
bs_print_string(" \0");
}
/*****************************************************************************
* Funktion: bs_dump *
*---------------------------------------------------------------------------*
* Beschreibung: Hauptroutine des Bluescreens. *
*****************************************************************************/
void bs_dump(unsigned int exceptionNr) {
unsigned int* int_esp;
unsigned int* sptr;
unsigned int faultAdress;
unsigned int has_error_code = 0;
bs_clear();
bs_print_string("HHUos crashed with Exception \0");
// Exception mit Error-Code?
if ((exceptionNr >= 8 && exceptionNr <= 14) || exceptionNr == 17 || exceptionNr == 30) {
has_error_code = 1;
}
// Liegt ein Page-Fault vor?
if (exceptionNr == 14) {
faultAdress = get_page_fault_address();
// Zugriff auf Seite 0 ? -> Null-Ptr. Exception
if ((faultAdress & 0xFFFFF000) == 0) {
exceptionNr = 0x1B;
}
}
bs_print_uintHex(exceptionNr);
bs_print_string(" (\0");
// Spruch ausgeben
switch (exceptionNr) {
case 0x00: bs_print_string("Divide Error\0"); break;
case 0x01: bs_print_string("Debug Exception\0"); break;
case 0x02: bs_print_string("NMI\0"); break;
case 0x03: bs_print_string("Breakpoint Exception\0"); break;
case 0x04: bs_print_string("Into Exception\0"); break;
case 0x05: bs_print_string("Index out of range Exception\0"); break;
case 0x06: bs_print_string("Invalid Opcode\0"); break;
case 0x08: bs_print_string("Double Fault\0"); break;
case 0x0D: bs_print_string("General Protection Error\0"); break;
case 0x0E: bs_print_string("Page Fault\0"); break;
case 0x18: bs_print_string("Stack invalid\0"); break;
case 0x19: bs_print_string("Return missing\0"); break;
case 0x1A: bs_print_string("Type Test Failed\0"); break;
case 0x1B: bs_print_string("Null pointer exception\0"); break;
case 0x1C: bs_print_string("MAGIC.StackTest failed\0"); break;
case 0x1D: bs_print_string("Memory-Panic\0"); break;
case 0x1E: bs_print_string("Pageload failed\0"); break;
case 0x1F: bs_print_string("Stack overflow\0"); break;
default: bs_print_string("unknown\0");
}
bs_print_string(")\0");
bs_lf();
// Zeiger auf int_esp ueber startup.asm beschaffen (Stack-Layout siehe Anfang dieser Datei)
get_int_esp(&int_esp);
// wir müssen den Inhalt auslesen und das als Zeiger verwenden, um den Stack auszulesen
sptr = reinterpret_cast<unsigned int*>(*int_esp);
bs_lf();
// wichtigste Register ausgeben
// Exception mit Error-Code?
bs_printReg("EIP=\0", *(sptr + 8 + has_error_code));
bs_printReg("EBP=\0", *(sptr + 2));
bs_printReg("ESP=\0", *(sptr + 3));
bs_printReg(" CS=\0", *(sptr + 9 + has_error_code));
bs_lf();
// verbleibende nicht-fluechtige Register ausgeben
bs_printReg("EBX=\0", *(sptr + 4));
bs_printReg("ESI=\0", *(sptr + 1));
bs_printReg("EDI=\0", *(sptr));
bs_lf();
// verbleibende fluechtige Register ausgeben
bs_printReg("EDX=\0", *(sptr + 5));
bs_printReg("ECX=\0", *(sptr + 6));
bs_printReg("EAX=\0", *(sptr + 7));
bs_printReg("EFL=\0", *(sptr + 10));
bs_lf();
// Pagefault oder Null-Pointer?
if (exceptionNr == 14 || exceptionNr == 0x1B) {
bs_lf();
bs_print_string("Fault address = \0");
bs_print_uintHex(faultAdress);
bs_lf();
bs_print_string("Last useable address = \0");
bs_print_uintHex(total_mem - 1);
bs_lf();
}
// Exception mit Error-Code?
if (has_error_code == 1) {
unsigned int error_nr = *(sptr + 8);
if (exceptionNr == 14) {
if (error_nr == 3) {
bs_print_string("Error: write access to read-only page.\0");
} else if (error_nr == 2) {
bs_print_string("Error: read access to not-present page.\0");
} else if (error_nr == 0) {
bs_print_string("Error: access to a not-present page.\0");
} else {
bs_print_string("Error code = \0");
bs_print_uintHex(error_nr);
}
bs_lf();
} else {
bs_print_string("Error code = \0");
bs_print_uintHex(error_nr);
bs_lf();
}
}
// Calling stack ...
bs_lf();
bs_print_string("Calling Stack:\0");
bs_lf();
int x = 0;
unsigned int* ebp = reinterpret_cast<unsigned int*>(*(sptr + 2));
unsigned int raddr;
// solange eip > 1 MB && ebp < 128 MB, max. Aufruftiefe 10
while (*ebp > 0x100000 && *ebp < 0x8000000 && x < 10) {
raddr = *(ebp + 1);
bs_printReg(" raddr=\0", raddr);
bs_lf();
// dynamische Kette -> zum Aufrufer
ebp = reinterpret_cast<unsigned int*>(*ebp);
x++;
}
if (x == 0) {
bs_print_string(" empty\0");
bs_lf();
}
bs_lf();
// nur falls gdb benutzt werden soll
break_on_bluescreen();
bs_print_string("System halted\0");
}

View File

@ -0,0 +1,19 @@
/*****************************************************************************
* *
* B L U E S C R E E N *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Ein Bluescreen, falls eine x86 Exception auftritt. Evt. *
* ist der Stack und oder Heap kaputt, weswegen hier nicht *
* kout etc. verwendet wird. *
* *
* Autor: Michael Schoettner, 2.2.2017 *
*****************************************************************************/
#ifndef Bluescreen_include__
#define Bluescreen_include__
// dump blue screen (will not return)
void bs_dump(unsigned int exceptionNr);
#endif

27
src/kernel/interrupt/ISR.h Executable file
View File

@ -0,0 +1,27 @@
/*****************************************************************************
* *
* I S R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Interrupt Service Routine. Jeweils ein Objekt pro ISR. *
* Erlaubt es einen Kontext mit Variablen fuer die Unter- *
* brechungsroutine bereitzustellen. *
* *
* Autor: Michael Schoettner, 06.04.20 *
*****************************************************************************/
#ifndef ISR_include__
#define ISR_include__
class ISR {
public:
ISR(const ISR& copy) = delete; // Verhindere Kopieren
// virtual ~ISR() = default;
ISR() = default;
// Unterbrechungsbehandlungsroutine
virtual void trigger() = 0;
};
#endif

View File

@ -0,0 +1,109 @@
/*****************************************************************************
* *
* I N T D I S P A T C H E R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Zentrale Unterbrechungsbehandlungsroutine des Systems. *
* Der Parameter gibt die Nummer des aufgetretenen *
* Interrupts an. Wenn eine Interrupt Service Routine *
* registriert ist, wird diese aufgerufen. *
* *
* Autor: Michael Schoettner, 31.8.2016 *
*****************************************************************************/
#include "IntDispatcher.h"
#include "device/cpu/CPU.h"
#include "kernel/system/Globals.h"
#include "kernel/interrupt/Bluescreen.h"
extern "C" void int_disp(unsigned int vector);
/*****************************************************************************
* Prozedur: int_disp *
*---------------------------------------------------------------------------*
* Beschreibung: Low-Level Interrupt-Behandlung. *
* Diese Funktion ist in der IDT fuer alle Eintraege einge- *
* tragen. Dies geschieht bereits im Bootloader. *
* Sie wird also fuer alle Interrupts aufgerufen. Von hier *
* aus sollen die passenden ISR-Routinen der Treiber-Objekte*
* mithilfe von 'IntDispatcher::report' aufgerufen werden. *
* Parameter: *
* vector: Vektor-Nummer der Unterbrechung *
*****************************************************************************/
void int_disp(unsigned int vector) {
/* hier muss Code eingefuegt werden */
if (vector < 32) {
bs_dump(vector);
CPU::halt();
}
if (intdis.report(vector) < 0) {
kout << "Panic: unexpected interrupt " << vector;
kout << " - processor halted." << endl;
CPU::halt();
}
}
/*****************************************************************************
* Methode: IntDispatcher::assign *
*---------------------------------------------------------------------------*
* Beschreibung: Registrierung einer ISR. *
* *
* Parameter: *
* vector: Vektor-Nummer der Unterbrechung *
* isr: ISR die registriert werden soll *
* *
* Rueckgabewert: 0 = Erfolg, -1 = Fehler *
*****************************************************************************/
int IntDispatcher::assign(unsigned int vector, ISR& isr) {
/* hier muss Code eingefuegt werden */
if (vector >= size) {
log.error() << "Invalid vector number when assigning" << endl;
return -1;
}
map[vector] = &isr;
log.info() << "Registered ISR for vector " << dec << vector << endl;
return 0;
}
/*****************************************************************************
* Methode: IntDispatcher::report *
*---------------------------------------------------------------------------*
* Beschreibung: Eingetragene ISR ausfuehren. *
* *
* Parameter: *
* vector: Gesuchtes ISR-Objekt fuer diese Vektor-Nummer. *
* *
* Rueckgabewert: 0 = ISR wurde aufgerufen, -1 = unbekannte Vektor-Nummer *
*****************************************************************************/
int IntDispatcher::report(unsigned int vector) {
/* hier muss Code eingefuegt werden */
if (vector >= size) {
return -1;
}
ISR* isr = map[vector];
if (isr == nullptr) {
log.error() << "No ISR registered for vector " << vector << endl;
return -1;
}
// 32 = Timer
// 33 = Keyboard
// log.trace() << "Interrupt: " << dec << vector << endl;
if (vector == 33) {
log.debug() << "Keyboard Interrupt" << endl;
}
isr->trigger();
return 0;
}

View File

@ -0,0 +1,51 @@
/*****************************************************************************
* *
* I N T D I S P A T C H E R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Zentrale Unterbrechungsbehandlungsroutine des Systems. *
* Der Parameter gibt die Nummer des aufgetretenen *
* Interrupts an. Wenn eine Interrupt Service Routine (ISR) *
* in der Map registriert ist, so wird diese aufgerufen. *
* *
* Autor: Michael Schoettner, 30.7.16 *
*****************************************************************************/
#ifndef IntDispatcher_include__
#define IntDispatcher_include__
#include "ISR.h"
#include "lib/util/Array.h"
#include "kernel/log/Logger.h"
class IntDispatcher {
private:
NamedLogger log;
enum { size = 256 };
bse::array<ISR*, size> map;
public:
IntDispatcher(const IntDispatcher& copy) = delete; // Verhindere Kopieren
// Vektor-Nummern
enum {
timer = 32,
keyboard = 33,
com1 = 36
};
// Initialisierung der ISR map mit einer Default-ISR.
IntDispatcher() : log("IntDis") {
for (ISR*& slot : map) {
slot = nullptr;
}
}
// Registrierung einer ISR. (Rueckgabewert: 0 = Erfolg, -1 = Fehler)
int assign(unsigned int vector, ISR& isr);
// ISR fuer 'vector' ausfuehren
int report(unsigned int vector);
};
#endif

117
src/kernel/log/Logger.cc Normal file
View File

@ -0,0 +1,117 @@
#include "Logger.h"
#include "kernel/system/Globals.h"
bool Logger::kout_enabled = true;
bool Logger::serial_enabled = true;
Logger::LogLevel Logger::level = Logger::ERROR;
constexpr const char* ansi_red = "\033[1;31m";
constexpr const char* ansi_green = "\033[1;32m";
constexpr const char* ansi_yellow = "\033[1;33m";
constexpr const char* ansi_blue = "\033[1;34m";
constexpr const char* ansi_magenta = "\033[1;35m";
constexpr const char* ansi_cyan = "\033[1;36m";
constexpr const char* ansi_white = "\033[1;37m";
constexpr const char* ansi_default = "\033[0;39m ";
void Logger::log(const bse::string_view message, CGA::color col) const {
if (Logger::kout_enabled) {
CGA::color old_col = kout.color_fg;
kout << fgc(col)
<< Logger::level_to_string(current_message_level) << "::"
<< message << fgc(old_col);
kout.flush(); // Don't add newline, Logger already does that
}
if (Logger::serial_enabled) {
switch (col) {
case CGA::WHITE:
SerialOut::write(ansi_white);
break;
case CGA::LIGHT_MAGENTA:
SerialOut::write(ansi_magenta);
break;
case CGA::LIGHT_RED:
SerialOut::write(ansi_red);
break;
case CGA::LIGHT_BLUE:
SerialOut::write(ansi_blue);
break;
default:
SerialOut::write(ansi_default);
}
SerialOut::write(Logger::level_to_string(current_message_level));
SerialOut::write(":: ");
SerialOut::write(message);
SerialOut::write('\r');
// serial.write("\r\n");
}
}
void Logger::flush() {
buffer[pos] = '\0';
switch (current_message_level) {
case Logger::TRACE:
trace(buffer.data());
break;
case Logger::DEBUG:
debug(buffer.data());
break;
case Logger::ERROR:
error(buffer.data());
break;
case Logger::INFO:
info(buffer.data());
break;
}
current_message_level = Logger::INFO;
pos = 0;
Logger::unlock();
}
void Logger::trace(const bse::string_view message) const {
if (Logger::level <= Logger::TRACE) {
log(message, CGA::WHITE);
}
}
void Logger::debug(const bse::string_view message) const {
if (Logger::level <= Logger::DEBUG) {
log(message, CGA::LIGHT_MAGENTA);
}
}
void Logger::error(const bse::string_view message) const {
if (Logger::level <= Logger::ERROR) {
log(message, CGA::LIGHT_RED);
}
}
void Logger::info(const bse::string_view message) const {
if (Logger::level <= Logger::INFO) {
log(message, CGA::LIGHT_BLUE);
}
}
// Manipulatoren
Logger& TRACE(Logger& log) {
log.current_message_level = Logger::TRACE;
return log;
}
Logger& DEBUG(Logger& log) {
log.current_message_level = Logger::DEBUG;
return log;
}
Logger& ERROR(Logger& log) {
log.current_message_level = Logger::ERROR;
return log;
}
Logger& INFO(Logger& log) {
log.current_message_level = Logger::INFO;
return log;
}

121
src/kernel/log/Logger.h Normal file
View File

@ -0,0 +1,121 @@
#ifndef Logger_Include_H_
#define Logger_Include_H_
#include "device/graphics/CGA.h"
#include "lib/stream/OutStream.h"
#include "lib/async/SpinLock.h"
#include "lib/util/String.h"
#include "lib/util/StringView.h"
class Logger : public OutStream {
public:
static Logger& instance() {
static Logger log;
return log;
}
private:
Logger() = default;
static bool kout_enabled;
static bool serial_enabled;
void log(const bse::string_view message, CGA::color col) const;
friend class NamedLogger; // Allow NamedLogger to lock/unlock
SpinLock sem; // Semaphore would be a cyclic include
static void lock() { Logger::instance().sem.acquire(); }
static void unlock() { Logger::instance().sem.release(); }
// static void lock() {}
// static void unlock() {}
public:
// ~Logger() override = default;
Logger(const Logger& copy) = delete;
void operator=(const Logger& copy) = delete;
enum LogLevel {
TRACE,
DEBUG,
ERROR,
INFO
};
static LogLevel level;
LogLevel current_message_level = Logger::INFO; // Use this to log with manipulators
void flush() override;
void trace(const bse::string_view message) const;
void debug(const bse::string_view message) const;
void error(const bse::string_view message) const;
void info(const bse::string_view message) const;
// TODO: Make lvl change accessible over menu
static void set_level(LogLevel lvl) {
Logger::level = lvl;
}
static bse::string_view level_to_string(LogLevel lvl) {
switch (lvl) {
case Logger::TRACE:
return "TRACE";
case Logger::DEBUG:
return "DEBUG";
case Logger::ERROR:
return "ERROR";
case Logger::INFO:
return "INFO";
}
}
static void enable_kout() {
Logger::kout_enabled = true;
}
static void disable_kout() {
Logger::kout_enabled = false;
}
static void enable_serial() {
Logger::serial_enabled = true;
}
static void disable_serial() {
Logger::serial_enabled = false;
}
};
// Manipulatoren
Logger& TRACE(Logger& log);
Logger& DEBUG(Logger& log);
Logger& ERROR(Logger& log);
Logger& INFO(Logger& log);
class NamedLogger {
private:
const char* name;
public:
explicit NamedLogger(const char* name) : name(name) {}
Logger& trace() {
Logger::lock();
return Logger::instance() << TRACE << name << "::";
}
Logger& debug() {
Logger::lock();
return Logger::instance() << DEBUG << name << "::";
}
Logger& error() {
Logger::lock();
return Logger::instance() << ERROR << name << "::";
}
Logger& info() {
Logger::lock();
return Logger::instance() << INFO << name << "::";
}
};
#endif

80
src/kernel/memory/Allocator.cc Executable file
View File

@ -0,0 +1,80 @@
/*****************************************************************************
* *
* A L L O C A T O R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Einfache Speicherverwaltung. 'new' und 'delete' werden *
* durch Ueberladen der entsprechenden Operatoren *
* realisiert. *
* *
* Memory-Laylout *
* *
* boot.asm *
* 0x07c0: Bootsector vom BIOS geladen *
* 0x0060: Boot-Code verschiebt sich hier hin *
* 0x9000: Setup-Code (max. 64K inkl. Stack) vom *
* Bootsector-Code geladen *
* setup.asm *
* 0x1000: System-Code (max. 512K) geladen *
* System-Code *
* 0x100000: System-Code, kopiert nach Umschalten in *
* den Protected Mode kopiert (GRUB kann nur *
* an Adressen >1M laden) *
* Globale Variablen: Direkt nach dem Code liegen die globalen *
* Variablen. *
* Heap: *
* 0x300000: Start-Adresse der Heap-Verwaltung *
* 0x400000: Letzte Adresse des Heaps *
* *
* Achtung: Benötigt einen PC mit mindestens 8 MB RAM! *
* *
* Autor: Michael Schoettner, HHU, 1.3.2022 *
*****************************************************************************/
#include "Allocator.h"
#include "kernel/system/Globals.h"
constexpr const unsigned int MEM_SIZE_DEF = 8 * 1024 * 1024; // Groesse des Speichers = 8 MB
constexpr const unsigned int HEAP_START = 0x300000; // Startadresse des Heaps
constexpr const unsigned int HEAP_SIZE = 1024 * 1024; // Default-Groesse des Heaps, falls \
// nicht über das BIOS ermittelbar
/*****************************************************************************
* Konstruktor: Allocator::Allocator *
*****************************************************************************/
Allocator::Allocator() : heap_start(HEAP_START), heap_end(HEAP_START + HEAP_SIZE), heap_size(HEAP_SIZE), initialized(1) {
// Groesse des Hauptspeichers (kann über das BIOS abgefragt werden,
// aber sehr umstaendlich, daher hier fest eingetragen
total_mem = MEM_SIZE_DEF;
}
/*****************************************************************************
* Nachfolgend sind die Operatoren von C++, die wir hier ueberschreiben *
* und entsprechend 'mm_alloc' und 'mm_free' aufrufen. *
*****************************************************************************/
void* operator new(std::size_t size) {
return allocator.alloc(size);
}
void* operator new[](std::size_t count) {
return allocator.alloc(count);
}
void operator delete(void* ptr) {
allocator.free(ptr);
}
void operator delete[](void* ptr) {
allocator.free(ptr);
}
void operator delete(void* ptr, unsigned int sz) {
allocator.free(ptr);
}
// I don't know if accidentally deleted it but one delete was missing
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
void operator delete[](void* ptr, unsigned int sz) {
allocator.free(ptr);
}

58
src/kernel/memory/Allocator.h Executable file
View File

@ -0,0 +1,58 @@
/*****************************************************************************
* *
* A L L O C A T O R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Einfache Speicherverwaltung. 'new' und 'delete' werden *
* durch Ueberladen der entsprechenden Operatoren *
* realisiert. *
* *
* Memory-Laylout *
* *
* boot.asm *
* 0x07c0: Bootsector vom BIOS geladen *
* 0x0060: Boot-Code verschiebt sich hier hin *
* 0x9000: Setup-Code (max. 64K inkl. Stack) vom *
* Bootsector-Code geladen *
* setup.asm *
* 0x1000: System-Code (max. 512K) geladen *
* System-Code *
* 0x100000: System-Code, kopiert nach Umschalten in *
* den Protected Mode kopiert (GRUB kann nur *
* an Adressen >1M laden) *
* Globale Variablen: Direkt nach dem Code liegen die globalen *
* Variablen. *
* Heap: *
* 0x300000: Start-Adresse der Heap-Verwaltung *
* 0x400000: Letzte Adresse des Heaps *
* *
* Achtung: Benötigt einen PC mit mindestens 4 MB RAM! *
* *
* Autor: Michael Schoettner, HHU, 13.6.2020 *
*****************************************************************************/
#ifndef Allocator_include__
#define Allocator_include__
constexpr const unsigned int BASIC_ALIGN = 4; // 32 Bit so 4 Bytes?
constexpr const unsigned int HEAP_MIN_FREE_BLOCK_SIZE = 64; // min. Groesse eines freien Blocks
class Allocator {
public:
Allocator(Allocator& copy) = delete; // Verhindere Kopieren
Allocator();
// virtual ~Allocator() = default;
unsigned int heap_start;
unsigned int heap_end;
unsigned int heap_size;
unsigned int initialized;
virtual void init() = 0;
virtual void dump_free_memory() = 0;
virtual void* alloc(unsigned int req_size) = 0;
virtual void free(void* ptr) = 0;
};
#endif

View File

@ -0,0 +1,76 @@
/*****************************************************************************
* *
* B U M P A L L O C A T O R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Eine sehr einfache Heap-Verwaltung, welche freigegebenen *
* Speicher nicht mehr nutzen kann. *
* *
* Autor: Michael Schoettner, HHU, 3.3.2022 *
*****************************************************************************/
#include "BumpAllocator.h"
#include "kernel/system/Globals.h"
/*****************************************************************************
* Methode: BumpAllocator::init *
*---------------------------------------------------------------------------*
* Beschreibung: BumpAllokartor intitialisieren. *
*****************************************************************************/
void BumpAllocator::init() {
/* Hier muess Code eingefuegt werden */
allocations = 0;
next = reinterpret_cast<unsigned char*>(heap_start);
log.info() << "Initialized Bump Allocator" << endl;
}
/*****************************************************************************
* Methode: BumpAllocator::dump_free_memory *
*---------------------------------------------------------------------------*
* Beschreibung: Ausgabe der Freispeicherinfos. Zu Debuggingzwecken. *
*****************************************************************************/
void BumpAllocator::dump_free_memory() {
/* Hier muess Code eingefuegt werden */
kout << "Freier Speicher:" << endl
<< " - Next: " << hex << reinterpret_cast<unsigned int>(next)
<< ", Allocations: " << dec << allocations << endl;
}
/*****************************************************************************
* Methode: BumpAllocator::alloc *
*---------------------------------------------------------------------------*
* Beschreibung: Einen neuen Speicherblock allozieren. *
*****************************************************************************/
void* BumpAllocator::alloc(unsigned int req_size) {
/* Hier muess Code eingefuegt werden */
log.debug() << "Requested " << hex << req_size << " Bytes" << endl;
if (req_size + reinterpret_cast<unsigned int>(next) > heap_end) {
log.error() << " - More memory requested than available :(" << endl;
return nullptr;
}
void* allocated = next;
next = reinterpret_cast<unsigned char*>(reinterpret_cast<unsigned int>(next) + req_size);
allocations = allocations + 1;
log.trace() << " - Allocated " << hex << req_size << " Bytes." << endl;
return allocated;
}
/*****************************************************************************
* Methode: BumpAllocator::free *
*---------------------------------------------------------------------------*
* Beschreibung: Nicht implementiert. *
*****************************************************************************/
void BumpAllocator::free(void* ptr) {
log.error() << " mm_free: ptr= " << hex << reinterpret_cast<unsigned int>(ptr) << ", not supported" << endl;
}

View File

@ -0,0 +1,38 @@
/*****************************************************************************
* *
* B U M P A L L O C A T O R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Eine sehr einfache Heap-Verwaltung, welche freigegebenen *
* Speicher nicht mehr nutzen kann. *
* *
* Autor: Michael Schoettner, HHU, 3.3.2022 *
*****************************************************************************/
#ifndef BumpAllocator_include__
#define BumpAllocator_include__
#include "Allocator.h"
#include "kernel/log/Logger.h"
class BumpAllocator : Allocator {
private:
unsigned char* next;
unsigned int allocations;
NamedLogger log;
public:
BumpAllocator(Allocator& copy) = delete; // Verhindere Kopieren
BumpAllocator() : log("BMP-Alloc") {}; // Allocator() called implicitely in C++
// ~BumpAllocator() override = default;
void init() override;
void dump_free_memory() override;
void* alloc(unsigned int req_size) override;
void free(void* ptr) override;
};
#endif

View File

@ -0,0 +1,310 @@
/*****************************************************************************
* *
* L I N K E D L I S T A L L O C A T O R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Einfache Speicherverwaltung, welche den freien Speicher *
* mithilfe einer einfach verketteten Liste verwaltet. *
* *
* Autor: Michael Schoettner, HHU, 13.6.2020 *
*****************************************************************************/
#include "LinkedListAllocator.h"
#include "kernel/system/Globals.h"
// I don't order the list by size so that the block order corresponds to the location in memory
// Then I can easily merge adjacent free blocks by finding the previous block without looking at
// memory addresses of each block
// (That was the plan at least)
/*****************************************************************************
* Methode: LinkedListAllocator::init *
*---------------------------------------------------------------------------*
* Beschreibung: Liste der Freispeicherbloecke intitialisieren. *
* Anker zeigt auf ein Dummy-Element. Danach folgt *
* ein Block der den gesamten freien Speicher umfasst. *
* *
* Wird automatisch aufgerufen, sobald eine Funktion der *
* Speicherverwaltung erstmalig gerufen wird. *
*****************************************************************************/
void LinkedListAllocator::init() {
/* Hier muess Code eingefuegt werden */
free_start = reinterpret_cast<free_block_t*>(heap_start);
free_start->allocated = false;
free_start->size = heap_size - sizeof(free_block_t);
free_start->next = free_start; // Only one block, points to itself
log.info() << "Initialized LinkedList Allocator" << endl;
}
/*****************************************************************************
* Methode: LinkedListAllocator::dump_free_memory *
*---------------------------------------------------------------------------*
* Beschreibung: Ausgabe der Freispeicherliste. Zu Debuggingzwecken. *
*****************************************************************************/
void LinkedListAllocator::dump_free_memory() {
/* Hier muess Code eingefuegt werden */
kout << "Freier Speicher:" << endl;
if (free_start == nullptr) {
kout << " - No free Blocks" << endl;
} else {
kout << " - Freelist start: " << hex << reinterpret_cast<unsigned int>(free_start) << endl;
free_block_t* current = free_start;
do {
kout << " - Free Block (Start: " << hex << reinterpret_cast<unsigned int>(current)
<< " Size: " << hex << current->size << ")" << endl;
current = current->next;
} while (current != free_start);
}
}
/*****************************************************************************
* Methode: LinkedListAllocator::alloc *
*---------------------------------------------------------------------------*
* Beschreibung: Einen neuen Speicherblock allozieren. *
*****************************************************************************/
void* LinkedListAllocator::alloc(unsigned int req_size) {
lock.acquire();
/* Hier muess Code eingefuegt werden */
// NOTE: next pointer zeigt auf headeranfang, returned wird zeiger auf anfang des nutzbaren freispeichers
log.debug() << "Requested " << hex << req_size << " Bytes" << endl;
if (free_start == nullptr) {
log.error() << " - No free memory remaining :(" << endl;
lock.release();
return nullptr;
}
// Round to word borders
unsigned int req_size_diff = (BASIC_ALIGN - req_size % BASIC_ALIGN) % BASIC_ALIGN;
unsigned int rreq_size = req_size + req_size_diff;
if (req_size_diff > 0) {
log.trace() << " - Rounded to word border (+" << dec << req_size_diff << " bytes)" << endl;
}
free_block_t* current = free_start;
do {
if (current->size >= rreq_size) { // Size doesn't contain header, only usable
// Current block large enough
// We now have: [<> | current | <>]
// Don't subtract to prevent underflow
if (current->size >= rreq_size + sizeof(free_block_t) + HEAP_MIN_FREE_BLOCK_SIZE) {
// Block so large it can be cut
// Create new header after allocated memory and rearrange pointers
// [<> | current | new_next | <>]
// In case of only one freeblock:
// [current | new_next]
free_block_t* new_next =
reinterpret_cast<free_block_t*>(reinterpret_cast<unsigned int>(current) + sizeof(free_block_t) + rreq_size);
// If only one block exists, current->next is current
// This shouldn't be a problem since the block gets removed from the list later
new_next->next = current->next;
new_next->size = current->size - (rreq_size + sizeof(free_block_t));
new_next->allocated = false;
current->next = new_next; // We want to reach the next free block from the allocated block
current->size = rreq_size;
// Next-fit
free_start = new_next;
log.trace() << " - Allocated " << hex << rreq_size << " Bytes with cutting" << endl;
} else {
// Block too small to be cut, allocate whole block
// Next-fit
free_start = current->next; // Pointer keeps pointing to current if last block
if (free_start == current) {
// No free block remaining
log.trace() << " - Disabled freelist" << endl;
free_start = nullptr;
}
log.trace() << " - Allocated " << hex << current->size << " Bytes without cutting" << endl;
}
// Block aushängen
// If block was cut this is obvious, because current->next is a free block
// If block wasn't cut it also works as current was a free block before, so it's next
// block should also be free
free_block_t* previous = LinkedListAllocator::find_previous_block(current);
previous->next = current->next; // Current block was free so previous block pointed at it
current->allocated = true;
// We leave the current->next pointer intact although the block is allocated
// to allow easier merging of adjacent free blocks
// HACK: Checking list integrity
// free_block_t* c = current;
// log.debug() << "Checking list Integrity" << endl;
// while (c->allocated) {
// log.debug() << hex << (unsigned int)c << endl;
// c = c->next;
// }
// log.debug() << "Finished check" << endl;
log.debug() << "returning memory address " << hex << reinterpret_cast<unsigned int>(current) + sizeof(free_block_t) << endl;
lock.release();
return reinterpret_cast<void*>(reinterpret_cast<unsigned int>(current) + sizeof(free_block_t)); // Speicheranfang, nicht header
}
current = current->next;
} while (current != free_start); // Stop when arriving at the first block again
log.error() << " - More memory requested than available :(" << endl;
lock.release();
return nullptr;
}
/*****************************************************************************
* Methode: LinkedListAllocator::free *
*---------------------------------------------------------------------------*
* Beschreibung: Einen Speicherblock freigeben. *
*****************************************************************************/
void LinkedListAllocator::free(void* ptr) {
lock.acquire();
/* Hier muess Code eingefuegt werden */
// Account for header
free_block_t* block_start = reinterpret_cast<free_block_t*>(reinterpret_cast<unsigned int>(ptr) - sizeof(free_block_t));
log.debug() << "Freeing " << hex << reinterpret_cast<unsigned int>(ptr) << ", Size: " << block_start->size << endl;
if (!block_start->allocated) {
log.error() << "Block already free" << endl;
lock.release();
return;
}
// Reenable the freelist if no block was available
// This also means that no merging can be done
if (free_start == nullptr) {
free_start = block_start;
block_start->allocated = false;
block_start->next = block_start;
log.trace() << " - Enabling freelist with one block" << endl;
lock.release();
return;
}
free_block_t* next_block =
reinterpret_cast<free_block_t*>(reinterpret_cast<unsigned int>(block_start) + sizeof(free_block_t) + block_start->size);
// Find the next free block, multiple next blocks can be allocated so walk through them
free_block_t* next_free = block_start->next;
while (next_free->allocated) {
next_free = next_free->next;
}
free_block_t* previous_free = LinkedListAllocator::find_previous_block(next_free);
free_block_t* previous_free_next =
reinterpret_cast<free_block_t*>(reinterpret_cast<unsigned int>(previous_free) + sizeof(free_block_t) + previous_free->size);
// We have: [previous_free | previous_free_next | <> | block_start | next_block | <> | next_free]
// The <> spaces don't have to exist and next_block could be the same as next_free
// or previous_free_next the same as block_start
// Also next_block and previous_free_next could be allocated blocks
// If previous_free/next_free and block_start are adjacent and free, they can be merged:
// - If next_block and next_free are the same block we can merge forward
// Should result in: [previous_free | previous_free_next | <> | block_start]
// - If previous_free_next and block_start are the same block we can merge backward
// Should result in: [block_start]
// log.trace() << "Before doing any merging:" << endl;
// log.trace() << "previous_free:" << hex << (unsigned int)previous_free << "Size:" << previous_free->size << "Next:" << (unsigned int)previous_free->next << endl;
// log.trace() << "previous_free_next:" << hex << (unsigned int)previous_free_next << "Size:" << previous_free_next->size << "Next:" << (unsigned int)previous_free_next->next << endl;
// log.trace() << "block_start:" << hex << (unsigned int)block_start << "Size:" << block_start->size << "Next:" << (unsigned int)block_start->next << endl;
// log.trace() << "next_block:" << hex << (unsigned int)next_block << "Size:" << next_block->size << "Next:" << (unsigned int)next_block->next << endl;
// log.trace() << "next_free:" << hex << (unsigned int)next_free << "Size:" << next_free->size << "Next:" << (unsigned int)next_free->next << endl;
// Try to merge forward ========================================================================
if (next_block == next_free) {
log.trace() << " - Merging block forward" << endl;
// Current and next adjacent block can be merged
// [previous_free | previous_free_next | <> | block_start | next_free]
// [block_start | next_free | next_free->next] => [block_start | next_free->next]
block_start->next = next_free->next; // We make next_free disappear
if (block_start->next == next_free) {
// If next_free is the only free block it points to itself, so fix that
// [block_start | next_free], but we want to remove next_free
block_start->next = block_start;
// [block_start]
}
block_start->size = block_start->size + sizeof(free_block_t) + next_free->size;
// There shouldn't exist any other allocated blocks pointing to next_free,
// the current one should be the only one (or else I have done something wrong)
// If thats the case I should set the next pointer to the next adjacent block
// when allocating a new block
if (free_start == next_free) {
// next_free is now invalid after merge
log.trace() << " - Moving freelist start to " << hex << reinterpret_cast<unsigned int>(block_start) << endl;
free_start = block_start;
}
} else {
// Can't merge forward so size stays the same
// [previous_free | previous_free_next | <> | block_start | <> | next_free]
block_start->next = next_free;
}
// Attach new free block to freelist
// This could write into a free block, but doesn't matter
previous_free->next = block_start;
// Try to merge backward =====================================================================
if (previous_free_next == block_start) {
log.trace() << " - Merging block backward" << endl;
// Current and previous adjacent block can be merged
// [previous_free | block_start]
previous_free->next = block_start->next;
previous_free->size = previous_free->size + sizeof(free_block_t) + block_start->size;
// For pointers to block_start the same as above applies
// so I don't think I have to manage anything else here
if (free_start == block_start) {
// block_start is now invalid after merge
log.trace() << " - Moving freelist start to " << hex << reinterpret_cast<unsigned int>(previous_free) << endl;
free_start = previous_free;
}
}
// Depending on the merging this might write into the block, but doesn't matter
block_start->allocated = false;
lock.release();
}
free_block_t* LinkedListAllocator::find_previous_block(free_block_t* next_block) {
// Durchlaufe die ganze freispeicherliste bis zum Block der auf next_block zeigt
free_block_t* current = next_block;
while (current->next != next_block) {
// NOTE: This will get stuck if called on the wrong block
current = current->next;
}
// if (current == next_block) {
// kout << "LinkedListAllocator::find_previous_block returned the input block" << endl;
// }
return current;
}

View File

@ -0,0 +1,58 @@
/*****************************************************************************
* *
* L I N K E D L I S T A L L O C A T O R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Einfache Speicherverwaltung, welche den freien Speicher *
* mithilfe einer einfach verketteten Liste verwaltet. *
* *
* Autor: Michael Schoettner, HHU, 13.6.2020 *
*****************************************************************************/
#ifndef LinkedListAllocator_include__
#define LinkedListAllocator_include__
#include "Allocator.h"
#include "lib/async/SpinLock.h"
#include "kernel/log/Logger.h"
// Format eines freien Blocks, 4 + 4 + 4 Byte
typedef struct free_block {
bool allocated; // NOTE: I added this to allow easier merging of free blocks:
// When freeing an allocated block, its next-pointer can
// point to another allocated block, the next free block
// can be found by traversing the leading allocated blocks.
// We only need a way to determine when the free block is reached.
// This also means that the whole list has to be traversed
// to merge blocks. Would be faster with doubly linked list.
unsigned int size;
struct free_block* next;
} free_block_t;
class LinkedListAllocator : Allocator {
private:
// freie Bloecke werden verkettet
struct free_block* free_start = nullptr;
// Traverses the whole list forward till previous block is reached.
// This can only be called on free blocks as allocated blocks
// aren't reachable from the freelist.
static struct free_block* find_previous_block(struct free_block*);
NamedLogger log;
SpinLock lock;
public:
LinkedListAllocator(Allocator& copy) = delete; // Verhindere Kopieren
LinkedListAllocator() : log("LL-Alloc") {}
// ~LinkedListAllocator() override = default;
void init() override;
void dump_free_memory() override;
void* alloc(unsigned int req_size) override;
void free(void* ptr) override;
};
#endif

213
src/kernel/memory/Paging.cc Normal file
View File

@ -0,0 +1,213 @@
/*****************************************************************************
* *
* P A G I N G *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Rudimentaeres Paging: 1:1 Mapping fuer gesamten logischen*
* Adressraum. logische Adresse = physikalische Adresse *
* *
* Page-Directory (alle Eintraege present, read/write *
* 0. Eintrag zeigt auf eine Page-Table (4 KB Eintraege)*
* Alle restl. Eintraege sind 4 MB Seiten und verweisen *
* somit auf keine Page-Tabelle sondern direkt auf die *
* 4 MB Seite. *
* *
* Page-Table (Logische Adressen 0 - 4 MB) *
* 1. Eintrag not present, read-only *
* -> Null-Pointer abfangen *
* 2. restl. Eintraege present & read/write *
* *
* Memory-Laylout *
* *
* boot.asm *
* 0x07c0: Bootsector vom BIOS geladen *
* 0x0060: Boot-Code verschiebt sich hier hin *
* 0x9000: Setup-Code (max. 64K inkl. Stack) vom *
* Bootsector-Code geladen *
* setup.asm *
* 0x1000: System-Code (max. 512K) geladen *
* BIOS-Aufruf *
* 0x24000: Parameter fuer BIOS-Aufurf *
* 0x25000: Altes ESP sichern, vor BIOS-Aufruf *
* 0x26000: 16-Bit Code-Segment fuer BIOS-Aufurf *
* System-Code *
* 0x100000: System-Code, kopiert nach Umschalten in *
* den Protected Mode kopiert (GRUB kann nur *
* an Adressen >1M laden) *
* Globale Variablen: Direkt nach dem Code liegen die globalen *
* Variablen. *
* Paging: *
* 0x200000: Page-Directory *
* 0x201000: Page-Table *
* 0x202000: erste allozierbare Page (via Paging.cc) *
* 0x3FF000: Anfang der letzten allozierbaren Page *
* Heap: *
* 0x400000: Start-Adresse der Heap-Verwaltung *
* Ende: Letzte Adresse des phys. Speichers *
* *
* *
* Autor: Michael Schoettner, 20.12.2018 *
*****************************************************************************/
#include "kernel/memory/Paging.h"
#include "kernel/system/Globals.h"
#include "kernel/log/Logger.h"
// Bits fuer Eintraege in der Page-Table
constexpr const unsigned int PAGE_PRESENT = 0x001;
constexpr const unsigned int PAGE_WRITEABLE = 0x002;
constexpr const unsigned int PAGE_BIGSIZE = 0x080;
constexpr const unsigned int PAGE_RESERVED = 0x800; // Bit 11 ist frei fuer das OS
// Adresse des Page-Directory (benoetigt 4 KB)
constexpr const unsigned int PAGE_DIRECTORY = 0x200000;
// Adresse der Page-Table (benoetigt 4 KB)
constexpr const unsigned int PAGE_TABLE = 0x201000;
// Start- und End-Adresse der 4 KB Seiten die durch die Page-Table adressiert werden
constexpr const unsigned int FST_ALLOCABLE_PAGE = 0x202000;
constexpr const unsigned int LST_ALLOCABLE_PAGE = 0x2FF000;
/*****************************************************************************
* Funktion: pg_alloc_page *
*---------------------------------------------------------------------------*
* Beschreibung: Alloziert eine 4 KB Seite. Allozieren heisst hier *
* lediglich Setzen eines eigenen RESERVED-Bits. *
*****************************************************************************/
unsigned int* pg_alloc_page() {
unsigned int* p_page;
p_page = reinterpret_cast<unsigned int*>(PAGE_TABLE);
// 1. Eintrag ist fuer Null-Pointer-Exception reserviert
// ausserdem liegt an die Page-Table an Adresse PAGE_TABLE
// somit ist est PAGE_TABLE + 4 KB frei (bis max. 3 MB, da beginnt der Heap)
for (int i = 1; i < 1024; i++) {
p_page++;
// pruefe ob Page frei
if (((*p_page) & PAGE_RESERVED) == 0) {
*p_page = (*p_page | PAGE_RESERVED);
return reinterpret_cast<unsigned int*>(i << 12); // Address without flags (Offset 0)
}
}
return nullptr;
}
/*****************************************************************************
* Funktion: pg_write_protect_page *
*---------------------------------------------------------------------------*
* Beschreibung: Schreibschutz fuer die uebergebene Seite aktivieren. *
* Dies fuer das Debugging nuetzlich. *
*****************************************************************************/
void pg_write_protect_page(const unsigned int* p_page) {
/* hier muss Code eingefügt werden */
unsigned int* page = reinterpret_cast<unsigned int*>(PAGE_TABLE) + (reinterpret_cast<unsigned int>(p_page) >> 12); // Pagetable entry
unsigned int mask = PAGE_WRITEABLE; // fill to 32bit
*page = *page & ~mask; // set writable to 0
invalidate_tlb_entry(p_page);
}
/*****************************************************************************
* Funktion: pg_notpresent_page *
*---------------------------------------------------------------------------*
* Beschreibung: Seite als ausgelagert markieren. Nur fuer Testzwecke. *
*****************************************************************************/
void pg_notpresent_page(const unsigned int* p_page) {
/* hier muss Code eingefügt werden */
unsigned int* page = reinterpret_cast<unsigned int*>(PAGE_TABLE) + (reinterpret_cast<unsigned int>(p_page) >> 12); // Pagetable entry
unsigned int mask = PAGE_PRESENT;
*page = *page & ~mask; // set present to 0
invalidate_tlb_entry(p_page);
}
/*****************************************************************************
* Funktion: pg_free_page *
*---------------------------------------------------------------------------*
* Beschreibung: Gibt eine 4 KB Seite frei. Es wird hierbei das RESERVED- *
* Bit geloescht. *
*****************************************************************************/
void pg_free_page(unsigned int* p_page) {
unsigned int idx = reinterpret_cast<unsigned int>(p_page) >> 12;
// ausserhalb Page ?
if (idx < 1 || idx > 1023) {
return;
}
// Eintrag einlesen und aendern (PAGE_WRITEABLE loeschen)
p_page = reinterpret_cast<unsigned int*>(PAGE_TABLE);
p_page += idx;
*p_page = ((idx << 12) | PAGE_WRITEABLE | PAGE_PRESENT);
}
/*****************************************************************************
* Funktion: pg_init *
*---------------------------------------------------------------------------*
* Beschreibung: Page-Tables einrichten und Paging mithilfe von *
* startup.asm aktivieren. *
*****************************************************************************/
void pg_init() {
unsigned int i;
unsigned int* p_pdir; // Zeiger auf Page-Directory
unsigned int* p_page; // Zeiger auf einzige Page-Table fuer 4 KB Pages
unsigned int num_pages; // Anzahl 4 MB Pages die phys. Adressraum umfassen
// wie viele 4 MB Seiten sollen als 'Present' angelegt werden,
// sodass genau der physikalische Adressraum abgedeckt ist?
num_pages = total_mem / (4096 * 1024);
Logger::instance() << INFO << "pg_init: " << total_mem << endl;
Logger::instance() << INFO << " total_mem: " << total_mem << endl;
Logger::instance() << INFO << " #pages: " << total_mem / (4096 * 1024) << endl;
//
// Aufbau des Page-Directory
//
// Eintrag 0: Zeiger auf 4 KB Page-Table
p_pdir = reinterpret_cast<unsigned int*>(PAGE_DIRECTORY);
*p_pdir = PAGE_TABLE | PAGE_WRITEABLE | PAGE_PRESENT;
// Eintraege 1-1023: Direktes Mapping (1:1) auf 4 MB Pages (ohne Page-Table)
for (i = 1; i < 1024; i++) {
p_pdir++;
if (i > num_pages) {
*p_pdir = ((i << 22) | PAGE_BIGSIZE);
} else {
*p_pdir = ((i << 22) | PAGE_BIGSIZE | PAGE_WRITEABLE | PAGE_PRESENT);
}
}
//
// 1. Page-Table
//
p_page = reinterpret_cast<unsigned int*>(PAGE_TABLE);
// ersten Eintrag loeschen -> not present, write protected -> Null-Pointer abfangen
*p_page = 0;
// Eintraege 1-1023: Direktes Mapping (1:1) auf 4 KB page frames
for (i = 1; i < 1024; i++) {
p_page++;
// Seiten unter FST_ALLOCABLE_PAGE reservieren, damit diese nicht
// alloziert werden und das System kaputt geht
if ((i << 12) >= FST_ALLOCABLE_PAGE) {
*p_page = ((i << 12) | PAGE_WRITEABLE | PAGE_PRESENT);
} else {
*p_page = ((i << 12) | PAGE_WRITEABLE | PAGE_PRESENT | PAGE_RESERVED);
}
}
// Paging aktivieren (in startup.asm)
paging_on(reinterpret_cast<unsigned int*>(PAGE_DIRECTORY));
}

View File

@ -0,0 +1,76 @@
/*****************************************************************************
* *
* P A G I N G *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Rudimentaeres Paging: 1:1 Mapping fuer gesamten logischen*
* Adressraum. logische Adresse = physikalische Adresse *
* *
* Page-Directory (alle Eintraege present, read/write *
* 0. Eintrag zeigt auf eine Page-Table (4 KB Eintraege)*
* Alle restl. Eintraege sind 4 MB Seiten und verweisen *
* somit auf keine Page-Tabelle sondern direkt auf die *
* 4 MB Seite. *
* *
* Page-Table (Logische Adressen 0 - 4 MB) *
* 1. Eintrag not present, read-only *
* -> Null-Pointer abfangen *
* 2. restl. Eintraege present & read/write *
* *
* Memory-Laylout *
* *
* boot.asm *
* 0x07c0: Bootsector vom BIOS geladen *
* 0x0060: Boot-Code verschiebt sich hier hin *
* 0x9000: Setup-Code (max. 64K inkl. Stack) vom *
* Bootsector-Code geladen *
* setup.asm *
* 0x1000: System-Code (max. 512K) geladen *
* BIOS-Aufruf *
* 0x24000: Parameter fuer BIOS-Aufurf *
* 0x25000: Altes ESP sichern, vor BIOS-Aufruf *
* 0x26000: 16-Bit Code-Segment fuer BIOS-Aufurf *
* System-Code *
* 0x100000: System-Code, kopiert nach Umschalten in *
* den Protected Mode kopiert (GRUB kann nur *
* an Adressen >1M laden) *
* Globale Variablen: Direkt nach dem Code liegen die globalen *
* Variablen. *
* Paging: *
* 0x200000: Page-Directory *
* 0x201000: Page-Table *
* 0x202000: erste allozierbare Page (via Paging.cc) *
* 0x3FF000: letzte allozierbare Page *
* Heap: *
* 0x400000: Start-Adresse der Heap-Verwaltung *
* Ende: Letzte Adresse des phys. Speichers *
* *
* *
* Autor: Michael Schoettner, 2.2.2017 *
*****************************************************************************/
#ifndef Paging_include__
#define Paging_include__
// Externe Funktionen in startup.asm
extern "C" {
void paging_on(unsigned int* p_pdir); // Paging einschalten
void invalidate_tlb_entry(const unsigned int* ptr); // Page in TLB invalid.
}
// ativiert paging
extern void pg_init();
// alloziert eine 4 KB Page
extern unsigned int* pg_alloc_page();
// Schreibschutz auf Seite setzen -> fuer debugging nuetzlich
extern void pg_write_protect_page(const unsigned int* p_page);
// Present Bit loeschen
extern void pg_notpresent_page(const unsigned int* p_page);
// gibt eine 4 KB Page frei
extern void pg_free_page(unsigned int* p_page);
#endif

View File

@ -0,0 +1,163 @@
#include "TreeAllocator.h"
#include "kernel/system/Globals.h"
void TreeAllocator::init() {
free_start = reinterpret_cast<tree_block_t*>(heap_start);
free_start->allocated = false;
free_start->left = nullptr;
free_start->right = nullptr;
free_start->parent = nullptr;
free_start->red = false; // The root is always black
free_start->next = reinterpret_cast<list_block_t*>(free_start);
free_start->previous = reinterpret_cast<list_block_t*>(free_start);
log.info() << "Initialized Tree Allocator" << endl;
}
void TreeAllocator::dump_free_memory() {
kout << "Free Memory:" << endl;
list_block_t* current = reinterpret_cast<list_block_t*>(heap_start);
do {
if (!current->allocated) {
kout << " - Free Block at " << reinterpret_cast<unsigned int>(current) << ", Size: "
<< reinterpret_cast<unsigned int>(current->next) - reinterpret_cast<unsigned int>(current)
<< endl;
}
current = current->next;
} while (reinterpret_cast<unsigned int>(current) != heap_start);
}
void* TreeAllocator::alloc(unsigned int req_size) {
log.debug() << "Requested " << dec << req_size << " Bytes" << endl;
// Round to word borders + tree_block size
unsigned int rreq_size = req_size;
if (rreq_size < sizeof(tree_block_t) - sizeof(list_block_t)) {
// the list_block_t is part of every block, but when freeing
// memory we need enough space to store the rbt metadata
rreq_size = sizeof(tree_block_t) - sizeof(list_block_t);
log.trace() << " - Increased block size for rbt metadata" << endl;
}
unsigned int req_size_diff = (BASIC_ALIGN - rreq_size % BASIC_ALIGN) % BASIC_ALIGN;
rreq_size = rreq_size + req_size_diff;
if (req_size_diff > 0) {
log.trace() << " - Rounded to word border (+" << dec << req_size_diff << " bytes)" << endl;
}
// Finds smallest block that is large enough
tree_block_t* best_fit = rbt_search_bestfit(rreq_size);
if (best_fit == nullptr) {
log.error() << " - No block found" << endl;
return nullptr;
}
if (best_fit->allocated) {
// Something went really wrong
log.error() << " - Block already allocated :(" << endl;
return nullptr;
}
best_fit->allocated = true;
unsigned int size = get_size(best_fit);
log.trace() << " - Found best-fit: " << hex << reinterpret_cast<unsigned int>(best_fit) << endl;
// HACK: I didn't want to handle situations with only one block (where the tree root would
// get removed), so I make sure there are always at least 2 blocks by inserting a dummy
// block. This is not fast at all but it was fast to code...
// I should change this so it only happens when only one block exists in the freelist
tree_block_t dummy;
dummy.allocated = false;
rbt_insert(&dummy); // I use the address of the stack allocated struct because it is
// removed before exiting the function
rbt_remove(best_fit); // BUG: Can trigger bluescreen
if (size > HEAP_MIN_FREE_BLOCK_SIZE + rreq_size + sizeof(list_block_t)) {
// Block can be cut
log.trace() << " - Allocating " << dec << rreq_size << " Bytes with cutting" << endl;
// [best_fit_start | sizeof(list_block_t) | rreq_size | new_block_start]
tree_block_t* new_block
= reinterpret_cast<tree_block_t*>(reinterpret_cast<char*>(best_fit) + sizeof(list_block_t) + rreq_size);
new_block->allocated = false;
dll_insert(best_fit, new_block);
rbt_insert(new_block);
} else {
// Don't cut block
// The block is already correctly positioned in the linked list so we only
// need to remove it from the freelist, which is done for both cases
log.trace() << " - Allocating " << dec << rreq_size << " Bytes without cutting" << endl;
}
// HACK: Remove the dummy element
rbt_remove(&dummy);
log.trace() << " - Returned address " << hex
<< reinterpret_cast<unsigned int>(reinterpret_cast<char*>(best_fit) + sizeof(list_block_t))
<< endl;
return reinterpret_cast<void*>(reinterpret_cast<char*>(best_fit) + sizeof(list_block_t));
}
void TreeAllocator::free(void* ptr) {
log.info() << "Freeing " << hex << reinterpret_cast<unsigned int>(ptr) << endl;
list_block_t* block = reinterpret_cast<list_block_t*>(reinterpret_cast<char*>(ptr) - sizeof(list_block_t));
if (!block->allocated) {
// Block already free
return;
}
block->allocated = false; // If the block is merged backwards afterwards this is unnecessary
list_block_t* previous = block->previous;
list_block_t* next = block->next;
if (next->allocated && previous->allocated) {
// No merge
rbt_insert(reinterpret_cast<tree_block_t*>(block));
return;
}
// HACK: Same as when allocating
tree_block_t dummy;
dummy.allocated = false;
rbt_insert(&dummy); // I use the address of the stack allocated struct because it is
if (!next->allocated) {
// Merge forward
log.trace() << " - Merging forward" << endl;
// Remove the next block from all lists as it is now part of our freed block
dll_remove(next);
rbt_remove(reinterpret_cast<tree_block_t*>(next)); // BUG: Bluescreen if next is the only block in the freelist
if (previous->allocated) {
// Don't insert if removed later because of backward merge
rbt_insert(reinterpret_cast<tree_block_t*>(block));
}
}
if (!previous->allocated) {
// Merge backward
log.trace() << " - Merging backward" << endl;
// Remove the current block from all lists as it is now part of the previous block
// It doesn't have to be removed from rbt as it wasn't in there as it was allocated before
dll_remove(block);
rbt_remove(reinterpret_cast<tree_block_t*>(previous));
rbt_insert(reinterpret_cast<tree_block_t*>(previous)); // Reinsert with new size
}
// HACK: Same as when allocating
rbt_remove(&dummy);
}
unsigned int TreeAllocator::get_size(list_block_t* block) const {
if (block->next == block) {
// Only one block exists
return heap_end - (reinterpret_cast<unsigned int>(block) + sizeof(list_block_t));
}
if (reinterpret_cast<unsigned int>(block->next) > reinterpret_cast<unsigned int>(block)) {
// Next block is placed later in memory
return reinterpret_cast<unsigned int>(block->next) - (reinterpret_cast<unsigned int>(block) + sizeof(list_block_t));
}
// Next block is placed earlier in memory which means block is at memory end
return reinterpret_cast<unsigned int>(heap_end) - (reinterpret_cast<unsigned int>(block) + sizeof(list_block_t));
}

View File

@ -0,0 +1,81 @@
#ifndef TreeAllocator_include__
#define TreeAllocator_include__
#include "Allocator.h"
#include "kernel/log/Logger.h"
// I can't imagine that this is fast with all the tree logic?
typedef struct list_block {
// Doubly linked list for every block
bool allocated;
struct list_block* next;
struct list_block* previous;
} list_block_t;
// The free blocks are organized in a red-black tree to enable fast insertion with best-fit strategy.
// To allow fast merging of freed blocks every block is part of a doubly linked list.
// Because the red-black tree only contains the free blocks, the memory overhead comes
// down to 4 + 4 + 4 Bytes for the allocated flag, next and previous pointers.
// The size can be calculated by using the next pointer so it doesn't have to be stored.
typedef struct tree_block {
// Doubly linked list for every block
// Locate this at the beginning so we can just cast to allocated_block_t and overwrite the rbt data
bool allocated;
struct list_block* next;
struct list_block* previous;
// RB tree for free blocks
struct tree_block* left;
struct tree_block* right;
struct tree_block* parent;
bool red; // RB tree node color
} tree_block_t;
class TreeAllocator : Allocator {
private:
// Root of the rbt
tree_block_t* free_start;
NamedLogger log;
// Returns the size of the usable memory of a block
unsigned int get_size(list_block_t* block) const;
unsigned int get_size(tree_block_t* block) const { return get_size(reinterpret_cast<list_block_t*>(block)); }
void dump_free_memory(tree_block_t* node);
// NOTE: Would be nice to have this stuff somewhere else for general use (scheduling?),
// makes no sense to have this as members. I'll move it later
void rbt_rot_l(tree_block_t* x);
void rbt_rot_r(tree_block_t* x);
void rbt_transplant(tree_block_t* a, tree_block_t* b);
tree_block_t* rbt_minimum(tree_block_t* node);
void rbt_insert(tree_block_t* node);
void rbt_fix_insert(tree_block_t* k);
void rbt_remove(tree_block_t* z);
void rbt_fix_remove(tree_block_t* x);
tree_block_t* rbt_search_bestfit(tree_block_t* node, unsigned int req_size);
tree_block_t* rbt_search_bestfit(unsigned int req_size) { return rbt_search_bestfit(free_start, req_size); }
void dll_insert(list_block_t* previous, list_block_t* node);
void dll_insert(tree_block_t* previous, tree_block_t* node) {
dll_insert(reinterpret_cast<list_block_t*>(previous), reinterpret_cast<list_block_t*>(node));
}
void dll_remove(list_block_t* node);
public:
TreeAllocator(Allocator& copy) = delete; // Verhindere Kopieren
TreeAllocator() : log("RBT-Alloc") {};
// ~TreeAllocator() override = default;
void init() override;
void dump_free_memory() override;
void* alloc(unsigned int req_size) override;
void free(void* ptr) override;
};
#endif

View File

@ -0,0 +1,302 @@
#include "TreeAllocator.h"
// RBT code taken from https://github.com/Bibeknam/algorithmtutorprograms
// START copy from algorithmtutorprograms
void TreeAllocator::rbt_transplant(tree_block_t* a, tree_block_t* b) {
if (a->parent == nullptr) {
free_start = b;
} else if (a == a->parent->left) {
a->parent->left = b;
} else {
a->parent->right = b;
}
b->parent = a->parent;
}
// insert the key to the tree in its appropriate position
// and fix the tree
void TreeAllocator::rbt_insert(tree_block_t* node) {
// Ordinary Binary Search Insertion
node->parent = nullptr;
node->left = nullptr;
node->right = nullptr;
node->red = true; // new node must be red
tree_block_t* y = nullptr;
tree_block_t* x = free_start;
while (x != nullptr) {
y = x;
if (get_size(node) < get_size(x)) {
x = x->left;
} else {
x = x->right;
}
}
// y is parent of x
node->parent = y;
if (y == nullptr) {
free_start = node;
} else if (get_size(node) < get_size(y)) {
y->left = node;
} else {
y->right = node;
}
// if new node is a root node, simply return
if (node->parent == nullptr) {
node->red = false;
return;
}
// if the grandparent is null, simply return
if (node->parent->parent == nullptr) {
return;
}
// Fix the tree
rbt_fix_insert(node);
}
// fix the red-black tree
void TreeAllocator::rbt_fix_insert(tree_block_t* k) {
tree_block_t* u;
while (k->parent->red) {
if (k->parent == k->parent->parent->right) {
u = k->parent->parent->left; // uncle
if (u->red) {
// case 3.1
u->red = false;
k->parent->red = false;
k->parent->parent->red = true;
k = k->parent->parent;
} else {
if (k == k->parent->left) {
// case 3.2.2
k = k->parent;
rbt_rot_r(k);
}
// case 3.2.1
k->parent->red = false;
k->parent->parent->red = true;
rbt_rot_l(k->parent->parent);
}
} else {
u = k->parent->parent->right; // uncle
if (u->red) {
// mirror case 3.1
u->red = false;
k->parent->red = false;
k->parent->parent->red = true;
k = k->parent->parent;
} else {
if (k == k->parent->right) {
// mirror case 3.2.2
k = k->parent;
rbt_rot_l(k);
}
// mirror case 3.2.1
k->parent->red = false;
k->parent->parent->red = true;
rbt_rot_r(k->parent->parent);
}
}
if (k == free_start) {
break;
}
}
free_start->red = false;
}
// rotate left at node x
void TreeAllocator::rbt_rot_l(tree_block_t* x) {
tree_block_t* y = x->right;
x->right = y->left;
if (y->left != nullptr) {
y->left->parent = x;
}
y->parent = x->parent;
if (x->parent == nullptr) {
free_start = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
y->left = x;
x->parent = y;
}
// rotate right at node x
void TreeAllocator::rbt_rot_r(tree_block_t* x) {
tree_block_t* y = x->left;
x->left = y->right;
if (y->right != nullptr) {
y->right->parent = x;
}
y->parent = x->parent;
if (x->parent == nullptr) {
free_start = y;
} else if (x == x->parent->right) {
x->parent->right = y;
} else {
x->parent->left = y;
}
y->right = x;
x->parent = y;
}
// find the node with the minimum key
tree_block_t* TreeAllocator::rbt_minimum(tree_block_t* node) {
while (node->left != nullptr) {
node = node->left;
}
return node;
}
void TreeAllocator::rbt_remove(tree_block_t* z) {
tree_block_t* x;
tree_block_t* y;
y = z;
bool y_original_red = y->red;
if (z->left == nullptr) {
x = z->right;
rbt_transplant(z, z->right);
} else if (z->right == nullptr) {
x = z->left;
rbt_transplant(z, z->left);
} else {
y = rbt_minimum(z->right);
y_original_red = y->red;
x = y->right;
if (y->parent == z) {
x->parent = y;
} else {
rbt_transplant(y, y->right);
y->right = z->right;
y->right->parent = y;
}
rbt_transplant(z, y);
y->left = z->left;
y->left->parent = y;
y->red = z->red;
}
if (!y_original_red) {
rbt_fix_remove(x);
}
}
// fix the rb tree modified by the delete operation
void TreeAllocator::rbt_fix_remove(tree_block_t* x) {
tree_block_t* s;
while (x != free_start && !x->red) {
if (x == x->parent->left) {
s = x->parent->right;
if (s->red) {
// case 3.1
s->red = false;
x->parent->red = true;
rbt_rot_l(x->parent);
s = x->parent->right;
}
if (!s->left->red && !s->right->red) {
// case 3.2
s->red = true;
x = x->parent;
} else {
if (!s->right->red) {
// case 3.3
s->left->red = false;
s->red = true;
rbt_rot_r(s);
s = x->parent->right;
}
// case 3.4
s->red = x->parent->red;
x->parent->red = false;
s->right->red = false;
rbt_rot_l(x->parent);
x = free_start;
}
} else {
s = x->parent->left;
if (s->red) {
// case 3.1
s->red = false;
x->parent->red = true;
rbt_rot_r(x->parent);
s = x->parent->left;
}
if (!s->right->red) {
// case 3.2
s->red = true;
x = x->parent;
} else {
if (!s->left->red) {
// case 3.3
s->right->red = false;
s->red = true;
rbt_rot_l(s);
s = x->parent->left;
}
// case 3.4
s->red = x->parent->red;
x->parent->red = false;
s->left->red = false;
rbt_rot_r(x->parent);
x = free_start;
}
}
}
x->red = false;
}
// END copy from algorithmtutorprograms
// This is recursive and depends on luck
tree_block_t* TreeAllocator::rbt_search_bestfit(tree_block_t* node, unsigned int req_size) {
if (node == nullptr) {
return nullptr;
}
if (req_size < get_size(node)) {
if (node->left != nullptr && get_size(node->left) >= req_size) {
return rbt_search_bestfit(node->left, req_size);
}
return node;
}
if (req_size > get_size(node)) {
if (node->right != nullptr && get_size(node->right) >= req_size) {
return rbt_search_bestfit(node->right, req_size);
}
// Block doesn't fit
return nullptr;
}
// Perfect fit
return node;
}
// DLL code
void TreeAllocator::dll_insert(list_block_t* previous, list_block_t* node) {
previous->next->previous = node;
node->next = previous->next;
node->previous = previous;
previous->next = node;
}
void TreeAllocator::dll_remove(list_block_t* node) {
node->previous->next = node->next;
node->next->previous = node->previous;
}

View File

@ -0,0 +1,38 @@
/*****************************************************************************
* *
* I D L E T H R E A D *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Wird nur aktiviert, wenn kein Thread arbeiten moechte. *
* *
* Autor: Michael, Schoettner, HHU, 13.8.2016 *
*****************************************************************************/
#ifndef IdleThread_include__
#define IdleThread_include__
#include "kernel/system/Globals.h"
#include "Thread.h"
class IdleThread : public Thread {
public:
IdleThread(const Thread& copy) = delete; // Verhindere Kopieren
IdleThread() : Thread("IdleThread") {}
[[noreturn]] void run() override {
// Idle-Thread läuft, ab jetzt ist der Scheduler fertig initialisiert
log.info() << "IdleThread enabled preemption" << endl;
scheduler.enable_preemption(tid);
if (!scheduler.preemption_enabled()) {
log.error() << "Preemption disabled" << endl;
}
while (true) {
// kout << "Idle!" << endl;
scheduler.yield();
}
}
};
#endif

View File

@ -0,0 +1,341 @@
/*****************************************************************************
* *
* S C H E D U L E R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Implementierung eines einfachen Zeitscheiben-Schedulers. *
* Rechenbereite Threads werden in 'readQueue' verwaltet. *
* *
* Der Scheduler wird mit 'schedule' gestartet. Neue Threads*
* können mit 'ready' hinzugefügt werden. Ein Thread muss *
* die CPU::freiwillig mit 'yield' abgeben, damit andere auch*
* rechnen koennen. Ein Thread kann sich selbst mit 'exit' *
* terminieren. Ein Thread kann einen anderen Thread mit *
* 'kill' beenden. Ein erzwungener Threadwechsel erfolgt *
* mit der Funktion 'preempt', welche von der Timer-ISR *
* aufgerufen wird. *
* *
* Zusaetzlich gibt es nun fuer die Semaphore zwei neue *
* Funktionen 'block' und 'deblock'. *
* *
* Autor: Michael, Schoettner, HHU, 23.11.2018 *
*****************************************************************************/
#include "Scheduler.h"
#include "IdleThread.h"
#include <utility>
constexpr const bool INSANE_TRACE = false;
/*****************************************************************************
* Methode: Dispatcher::dispatch *
*---------------------------------------------------------------------------*
* Beschreibung: Auf den active thread wechseln. *
* *
* Parameter: *
* next Thread der die CPU::erhalten soll. *
*****************************************************************************/
void Scheduler::start(bse::vector<bse::unique_ptr<Thread>>::iterator next) {
active = next;
if (active >= ready_queue.end()) {
active = ready_queue.begin();
log.debug() << "Scheduler::start started different thread than passed" << endl;
}
if constexpr (INSANE_TRACE) {
log.trace() << "Starting Thread with id: " << dec << (*active)->tid << endl;
}
(*active)->start(); // First dereference the Iterator, then the unique_ptr to get Thread
}
void Scheduler::switch_to(Thread* prev_raw, bse::vector<bse::unique_ptr<Thread>>::iterator next) {
active = next;
if (active >= ready_queue.end()) {
active = ready_queue.begin();
// log.debug() << "Scheduler::switch_to started different thread than passed" << endl;
}
if constexpr (INSANE_TRACE) {
log.trace() << "Switching to Thread with id: " << dec << (*active)->tid << endl;
}
prev_raw->switchTo(**active);
}
/*****************************************************************************
* Methode: Scheduler::schedule *
*---------------------------------------------------------------------------*
* Beschreibung: Scheduler starten. Wird nur einmalig aus main.cc gerufen.*
*****************************************************************************/
void Scheduler::schedule() {
/* hier muss Code eingefuegt werden */
// We need to start the idle thread first as this one sets the scheduler to initialized
// and enables preemption.
// Otherwise preemption will be blocked and nothing will happen if the first threads
// run() function is blocking
ready_queue.push_back(bse::make_unique<IdleThread>());
log.info() << "Starting scheduling: starting thread with id: " << dec << (*(ready_queue.end() - 1))->tid << endl;
start(ready_queue.end() - 1);
}
/*****************************************************************************
* Methode: Scheduler::ready *
*---------------------------------------------------------------------------*
* Beschreibung: Thread in readyQueue eintragen. *
*****************************************************************************/
void Scheduler::ready(bse::unique_ptr<Thread>&& thread) {
CPU::disable_int();
log.debug() << "Adding to ready_queue, ID: " << dec << thread->tid << endl;
ready_queue.push_back(std::move(thread));
CPU::enable_int();
}
/*****************************************************************************
* Methode: Scheduler::exit *
*---------------------------------------------------------------------------*
* Beschreibung: Thread ist fertig und terminiert sich selbst. Hier muss *
* nur auf den naechsten Thread mithilfe des Dispatchers *
* umgeschaltet werden. Der aktuell laufende Thread ist *
* nicht in der readyQueue. *
*****************************************************************************/
void Scheduler::exit() {
/* hier muss Code eingefuegt werden */
// Thread-Wechsel durch PIT verhindern
CPU::disable_int();
if (ready_queue.size() == 1) {
log.error() << "Can't exit last thread, active ID: " << dec << (*active)->tid << endl;
CPU::enable_int();
return;
}
log.debug() << "Exiting thread, ID: " << dec << (*active)->tid << endl;
start(ready_queue.erase(active)); // erase returns the next iterator after the erased element
// cannot use switch_to here as the previous thread no longer
// exists (was deleted by erase)
// Interrupts werden in Thread_switch in Thread.asm wieder zugelassen
// dispatch kehr nicht zurueck
}
/*****************************************************************************
* Methode: Scheduler::kill *
*---------------------------------------------------------------------------*
* Beschreibung: Thread mit 'Gewalt' terminieren. Er wird aus der *
* readyQueue ausgetragen und wird dann nicht mehr aufge- *
* rufen. Der Aufrufer dieser Methode muss ein anderer *
* Thread sein. *
* *
* Parameter: *
* that Zu terminierender Thread *
*****************************************************************************/
void Scheduler::kill(unsigned int tid, bse::unique_ptr<Thread>* ptr) {
CPU::disable_int();
unsigned int prev_tid = (*active)->tid;
// Block queue, can always kill
for (bse::vector<bse::unique_ptr<Thread>>::iterator it = block_queue.begin(); it != block_queue.end(); ++it) {
if ((*it)->tid == tid) {
// Found thread to kill
if (ptr != nullptr) {
// Move old thread out of queue to return it
unsigned int pos = bse::distance(block_queue.begin(), it);
*ptr = std::move(block_queue[pos]); // Return the killed thread
}
// Just erase from queue, do not need to switch
block_queue.erase(it);
log.info() << "Killed thread from block_queue with id: " << tid << endl;
CPU::enable_int();
return;
}
}
// Ready queue, can't kill last one
if (ready_queue.size() == 1) {
log.error() << "Kill: Can't kill last thread in ready_queue with id: " << tid << endl;
CPU::enable_int();
return;
}
for (bse::vector<bse::unique_ptr<Thread>>::iterator it = ready_queue.begin(); it != ready_queue.end(); ++it) {
if ((*it)->tid == tid) {
// Found thread to kill
if (ptr != nullptr) {
// Move old thread out of queue to return it
unsigned int pos = bse::distance(ready_queue.begin(), it);
*ptr = std::move(ready_queue[pos]); // Return the killed thread
}
if (tid == prev_tid) {
// If we killed the active thread we need to switch to another one
log.info() << "Killed active thread from ready_queue with id: " << tid << endl;
// Switch to current active after old active was removed
start(ready_queue.erase(it));
}
// Just erase from queue, do not need to switch
ready_queue.erase(it);
log.info() << "Killed thread from ready_queue with id: " << tid << endl;
CPU::enable_int();
return;
}
}
log.error() << "Kill: Couldn't find thread with id: " << tid << " in ready- or block-queue" << endl;
log.error() << "Mabe it already exited itself?" << endl;
CPU::enable_int();
}
// TODO: Can't retrive the thread right now because it's not clear when it's finished,
// maybe introduce a exited_queue and get it from there
void Scheduler::nice_kill(unsigned int tid, bse::unique_ptr<Thread>* ptr) {
CPU::disable_int();
for (bse::unique_ptr<Thread>& thread : block_queue) {
if (thread->tid == tid) {
thread->suicide();
log.info() << "Nice killed thread in block_queue with id: " << tid << endl;
deblock(tid);
CPU::enable_int();
return;
}
}
for (bse::unique_ptr<Thread>& thread : ready_queue) {
if (thread->tid == tid) {
thread->suicide();
log.info() << "Nice killed thread in ready_queue with id: " << tid << endl;
CPU::enable_int();
return;
}
}
log.error() << "Can't nice kill thread (not found) with id: " << tid << endl;
log.error() << "Mabe it already exited itself?" << endl;
CPU::enable_int();
}
/*****************************************************************************
* Methode: Scheduler::yield *
*---------------------------------------------------------------------------*
* Beschreibung: CPU::freiwillig abgeben und Auswahl des naechsten Threads.*
* Naechsten Thread aus der readyQueue holen, den aktuellen *
* in die readyQueue wieder eintragen. Das Umschalten soll *
* mithilfe des Dispatchers erfolgen. *
* *
* Achtung: Falls nur der Idle-Thread läuft, so ist die *
* readyQueue leer. *
*****************************************************************************/
void Scheduler::yield() {
/* hier muss Code eingefuegt werden */
// Thread-Wechsel durch PIT verhindern
CPU::disable_int();
if (ready_queue.size() == 1) {
if constexpr (INSANE_TRACE) {
log.trace() << "Skipping yield as no thread is waiting, active ID: " << dec << (*active)->tid << endl;
}
CPU::enable_int();
return;
}
if constexpr (INSANE_TRACE) {
log.trace() << "Yielding, ID: " << dec << (*active)->tid << endl;
}
switch_to((*active).get(), active + 1); // prev_raw is valid since no thread was killed/deleted
}
/*****************************************************************************
* Methode: Scheduler::preempt *
*---------------------------------------------------------------------------*
* Beschreibung: Diese Funktion wird aus der ISR des PITs aufgerufen und *
* schaltet auf den naechsten Thread um, sofern einer vor- *
* handen ist. *
*****************************************************************************/
void Scheduler::preempt() {
/* Hier muss Code eingefuegt werden */
CPU::disable_int();
yield();
}
/*****************************************************************************
* Methode: Scheduler::block *
*---------------------------------------------------------------------------*
* Beschreibung: Aufrufer ist blockiert. Es soll auf den naechsten Thread *
* umgeschaltet werden. Der Aufrufer soll nicht in die *
* readyQueue eingefuegt werden und wird extern verwaltet. *
* Wird bei uns nur fuer Semaphore verwendet. Jede Semaphore*
* hat eine Warteschlange wo der Thread dann verwaltet wird.*
* Die Methode kehrt nicht zurueck, sondern schaltet um. *
*****************************************************************************/
void Scheduler::block() {
/* hier muss Code eingefuegt werden */
CPU::disable_int();
if (ready_queue.size() == 1) {
log.error() << "Can't block last thread, active ID: " << dec << (*active)->tid << endl;
CPU::enable_int();
return;
}
Thread* prev_raw = (*active).get();
std::size_t pos = bse::distance(ready_queue.begin(), active);
block_queue.push_back(std::move(ready_queue[pos]));
if constexpr (INSANE_TRACE) {
log.trace() << "Blocked thread with id: " << prev_raw->tid << endl;
}
switch_to(prev_raw, ready_queue.erase(active)); // prev_raw is valid as thread was moved before vector erase
}
/*****************************************************************************
* Methode: Scheduler::deblock *
*---------------------------------------------------------------------------*
* Beschreibung: Thread 'that' deblockieren. 'that' wird nur in die *
* readyQueue eingefuegt und dann zurueckgekehrt. In der *
* einfachsten Form entspricht diese Funktion exakt 'ready' *
* Man koennte alternativ aber den deblockierten Thread auch*
* am Anfang der readyQueue einfuegen, um ihn zu beorzugen. *
* *
* Parameter: that: Thread der deblockiert werden soll. *
*****************************************************************************/
void Scheduler::deblock(unsigned int tid) {
/* hier muss Code eingefuegt werden */
CPU::disable_int();
for (bse::vector<bse::unique_ptr<Thread>>::iterator it = block_queue.begin(); it != block_queue.end(); ++it) {
if ((*it)->tid == tid) {
// Found thread with correct tid
std::size_t pos = bse::distance(block_queue.begin(), it);
ready_queue.insert(active + 1, std::move(block_queue[pos])); // We insert the thread after the active
// thread to prefer deblocked threads
block_queue.erase(it);
if constexpr (INSANE_TRACE) {
log.trace() << "Deblocked thread with id: " << tid << endl;
}
CPU::enable_int();
return;
}
}
log.error() << "Couldn't deblock thread with id: " << tid << endl;
CPU::enable_int();
}

View File

@ -0,0 +1,109 @@
/*****************************************************************************
* *
* S C H E D U L E R *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Implementierung eines einfachen Zeitscheiben-Schedulers. *
* Rechenbereite Threads werden in 'readQueue' verwaltet. *
* *
* Autor: Michael, Schoettner, HHU, 22.8.2016 *
*****************************************************************************/
#ifndef Scheduler_include__
#define Scheduler_include__
#include "Thread.h"
#include "lib/mem/UniquePointer.h"
#include "kernel/log/Logger.h"
#include "lib/util/Vector.h"
class Scheduler {
private:
NamedLogger log;
bse::vector<bse::unique_ptr<Thread>> ready_queue;
bse::vector<bse::unique_ptr<Thread>> block_queue;
// NOTE: It makes sense to keep track of the active thread through this as it makes handling the
// unique_ptr easier and reduces the copying in the vector when cycling through the threads
bse::vector<bse::unique_ptr<Thread>>::iterator active = nullptr;
// Scheduler wird evt. von einer Unterbrechung vom Zeitgeber gerufen,
// bevor er initialisiert wurde
unsigned int idle_tid = 0U;
// Roughly the old dispatcher functionality
void start(bse::vector<bse::unique_ptr<Thread>>::iterator next); // Start next without prev
void switch_to(Thread* prev_raw, bse::vector<bse::unique_ptr<Thread>>::iterator next); // Switch from prev to next
// Kann nur vom Idle-Thread aufgerufen werden (erster Thread der vom Scheduler gestartet wird)
void enable_preemption(unsigned int tid) { idle_tid = tid; }
friend class IdleThread;
void ready(bse::unique_ptr<Thread>&& thread);
public:
Scheduler(const Scheduler& copy) = delete; // Verhindere Kopieren
Scheduler() : log("SCHED"), ready_queue(true), block_queue(true) {} // lazy queues, wait for allocator
// The scheduler has to init the queues explicitly after the allocator is available
void init() {
ready_queue.reserve();
block_queue.reserve();
}
unsigned int get_active() const {
return (*active)->tid;
}
// Scheduler initialisiert?
// Zeitgeber-Unterbrechung kommt evt. bevor der Scheduler fertig
// intiialisiert wurde!
bool preemption_enabled() const { return idle_tid != 0U; }
// Scheduler starten
void schedule();
// Helper that directly constructs the thread, then readys it
template<typename T, typename... Args>
unsigned int ready(Args... args) {
bse::unique_ptr<Thread> thread = bse::make_unique<T>(std::forward<Args>(args)...);
unsigned int tid = thread->tid;
ready(std::move(thread));
return tid;
}
// Thread terminiert sich selbst
// NOTE: When a thread exits itself it will disappear...
// Maybe put exited threads in an exited queue?
// Then they would have to be acquired from there to exit...
void exit(); // Returns on error because we don't have exceptions
// Thread mit 'Gewalt' terminieren
void kill(unsigned int tid, bse::unique_ptr<Thread>* ptr);
void kill(unsigned int tid) { kill(tid, nullptr); }
// Asks thread to exit
// NOTE: I had many problems with killing threads that were stuck in some semaphore
// or were involved in any locking mechanisms, so with this a thread can make sure
// to "set things right" before exiting itself (but could also be ignored)
void nice_kill(unsigned int tid, bse::unique_ptr<Thread>* ptr);
void nice_kill(unsigned int tid) { nice_kill(tid, nullptr); }
// CPU freiwillig abgeben und Auswahl des naechsten Threads
void yield(); // Returns when only the idle thread runs
// Thread umschalten; wird aus der ISR des PITs gerufen
void preempt(); // Returns when only the idle thread runs
// Blocks current thread (move to block_queue)
void block(); // Returns on error because we don't have exceptions
// Deblock by tid (move to ready_queue)
void deblock(unsigned int tid);
};
#endif

133
src/kernel/process/Thread.asm Executable file
View File

@ -0,0 +1,133 @@
;*****************************************************************************
;* *
;* 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]
Thread_start:
; *
; * Hier muss Code eingefuegt werden
; *
;; NOTE: New code with pusha/popa, restores all registers as I use this not only for first start
;; == High address ==
;; ESP
;; SP --> RET ADDR
;; == Low address ==
mov esp, [esp + 0x4]
;; == High address ==
;; *OBJECT
;; 0x13115
;; *KICKOFF
;; EAX
;; ECX
;; EDX
;; EBX
;; ESP
;; EBP
;; ESI
;; EDI
;; SP --> EFLAGS
;; == Low address ==
popf
popa
;; == High address ==
;; *OBJECT
;; 0x13115
;; SP --> *KICKOFF
;; == Low address ==
sti
ret
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
;; 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
;; == High address ==
;; ESP_NEXT
;; *ESP_PREV
;; SP --> RET ADDR
;; == Low address ==
pusha
pushf
;; == High address ==
;; + 0x2c ESP_NEXT
;; + 0x28 *ESP_PREV
;; + 0x24 RET ADDR
;; EAX
;; ECX
;; EDX
;; EBX
;; ESP
;; EBP
;; ESI
;; EDI
;; SP --> EFLAGS
;; == Low address ==
mov eax, [esp + 0x28] ; Point to *ESP_PREV (Address)
mov [eax], esp ; Update thread esp variable
;; ============================================================
mov esp, [esp + 0x2c] ; Move to next coroutines stack
;; == High address ==
;; NEW
;; THREAD
;; STACK
;; RET ADDR
;; EAX
;; ECX
;; EDX
;; EBX
;; ESP
;; EBP
;; ESI
;; EDI
;; SP --> EFLAGS
;; == Low address ==
popf ; Load new registers from stack
popa
;; == High address ==
;; NEW
;; THREAD
;; STACK
;; SP --> RET ADDR
;; == Low address ==
;; Enable interrupts again
sti
ret

133
src/kernel/process/Thread.cc Executable file
View File

@ -0,0 +1,133 @@
/*****************************************************************************
* *
* 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 "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(unsigned int esp);
// NOTE: Only when backing up the previous thread the esp gets updated,
// so only esp_pre is a pointer
void Thread_switch(unsigned int* esp_prev, unsigned int esp_next);
}
unsigned int ThreadCnt = 1; // Skip tid 0 as the scheduler indicates no preemption with 0
/*****************************************************************************
* Prozedur: Coroutine_init *
*---------------------------------------------------------------------------*
* Beschreibung: Bereitet den Kontext der Koroutine fuer den ersten *
* Aufruf vor. *
*****************************************************************************/
void Thread_init(unsigned int* esp, unsigned int* stack, void (*kickoff)(Thread*), void* object) {
// NOTE: c++17 doesn't allow register
// register unsigned int** sp = (unsigned int**)stack;
// 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.
// I thought this syntax was a bit clearer than decrementing a pointer
stack[-1] = reinterpret_cast<unsigned int>(object);
stack[-2] = 0x131155U;
stack[-3] = reinterpret_cast<unsigned int>(kickoff);
stack[-4] = 0; // EAX
stack[-5] = 0; // ECX
stack[-6] = 0; // EDX
stack[-7] = 0; // EBX
stack[-8] = reinterpret_cast<unsigned int>(&stack[-3]); // ESP
stack[-9] = 0; // EBP
stack[-10] = 0; // ESI
stack[-11] = 0; // EDI
stack[-12] = 0x200U;
*esp = reinterpret_cast<unsigned int>(&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(char* name) : stack(new unsigned int[1024]), esp(0), log(name), name(name), tid(ThreadCnt++) {
if (stack == nullptr) {
log.error() << "Couldn't initialize Thread (couldn't alloc stack)" << endl;
return;
}
log.info() << "Initialized thread with ID: " << tid << " (" << name << ")" << endl;
Thread_init(&esp, &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 */
// log.trace() << name << ":: Has esp " << hex << esp << endl;
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);
}

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* *
* 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 "kernel/log/Logger.h"
class Thread {
private:
unsigned int* stack;
unsigned int esp;
protected:
Thread(char* name);
NamedLogger log;
bool running = true; // For soft exit, if thread uses infinite loop inside run(), use this as condition
char* name; // For logging
unsigned int tid; // Thread-ID (wird im Konstruktor vergeben)
friend class Scheduler; // Scheduler can access tid
public:
Thread(const Thread& copy) = delete; // Verhindere Kopieren
virtual ~Thread() {
log.info() << "Uninitialized thread, ID: " << dec << tid << " (" << name << ")" << endl;
delete[] stack;
}
// Thread aktivieren
void start() const;
// Umschalten auf Thread 'next'
void switchTo(Thread& next);
// Ask thread to terminate itself
void suicide() { running = false; }
// Methode des Threads, muss in Sub-Klasse implementiert werden
virtual void run() = 0;
};
#endif

33
src/kernel/system/Globals.cc Executable file
View File

@ -0,0 +1,33 @@
/*****************************************************************************
* *
* G L O B A L S *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Globale Variablen des Systems. *
* *
* Autor: Michael Schoettner, 30.7.16 *
*****************************************************************************/
#include "Globals.h"
CGA_Stream kout; // Ausgabe-Strom fuer Kernel
const BIOS& bios = BIOS::instance(); // Schnittstelle zum 16-Bit BIOS
VESA vesa; // VESA-Treiber
PIC pic; // Interrupt-Controller
IntDispatcher intdis; // Unterbrechungsverteilung
PIT pit(10000); // 10000
PCSPK pcspk; // PC-Lautsprecher
Keyboard kb; // Tastatur
// BumpAllocator allocator;
LinkedListAllocator allocator;
// TreeAllocator allocator;
Scheduler scheduler;
KeyEventManager kevman;
SerialOut serial;
unsigned int total_mem; // RAM total
unsigned long systime = 0;

54
src/kernel/system/Globals.h Executable file
View File

@ -0,0 +1,54 @@
/*****************************************************************************
* *
* G L O B A L S *
* *
*---------------------------------------------------------------------------*
* Beschreibung: Globale Variablen des Systems. *
* *
* Autor: Michael Schoettner, 30.7.16 *
*****************************************************************************/
#ifndef Globals_include__
#define Globals_include__
#include "device/graphics/CGA_Stream.h"
#include "device/hid/Keyboard.h"
#include "device/sound/PCSPK.h"
#include "device/time/PIT.h"
#include "device/graphics/VESA.h"
#include "kernel/memory/BumpAllocator.h"
#include "kernel/memory/LinkedListAllocator.h"
#include "kernel/memory/TreeAllocator.h"
#include "device/bios/BIOS.h"
#include "device/cpu/CPU.h"
#include "kernel/interrupt/IntDispatcher.h"
#include "device/interrupt/PIC.h"
#include "kernel/memory/Paging.h"
#include "kernel/process/Scheduler.h"
#include "device/port/SerialOut.h"
#include "kernel/event/KeyEventManager.h"
// I wanted to make more of these singletons but there were problems with atexit missing because of nostdlib I guess
extern CGA_Stream kout; // Ausgabe-Strom fuer Kernel
extern const BIOS& bios; // Schnittstelle zum 16-Bit BIOS
extern VESA vesa; // VESA-Treiber
extern PIC pic; // Interrupt-Controller
extern IntDispatcher intdis; // Unterbrechungsverteilung
extern PIT pit; // Zeitgeber
extern PCSPK pcspk; // PC-Lautsprecher
extern Keyboard kb; // Tastatur
// extern BumpAllocator allocator;
extern LinkedListAllocator allocator;
// extern TreeAllocator allocator;
extern Scheduler scheduler;
extern KeyEventManager kevman;
extern SerialOut serial;
extern unsigned int total_mem; // RAM total
extern unsigned long systime; // wird all 10ms hochgezaehlt
#endif