diff --git a/src/kernel/process/IdleThread.h b/src/kernel/process/IdleThread.h index bd62184..d7386d1 100644 --- a/src/kernel/process/IdleThread.h +++ b/src/kernel/process/IdleThread.h @@ -1,18 +1,10 @@ -/***************************************************************************** - * * - * 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" +#include "kernel/service/SchedulerService.h" +#include "kernel/system/System.h" namespace Kernel { @@ -20,19 +12,14 @@ class IdleThread : public Thread { public: IdleThread(const Thread ©) = delete; // Verhindere Kopieren - IdleThread() : Thread("IdleThread") {} + IdleThread() { + tid = Thread::IDLE; // The IdleThread gets a fixed id for convenience + } [[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; - } - + auto &schedulerService = Kernel::System::getService(); while (true) { - // Util::System::out << "Idle!" << endl; - scheduler.yield(); + schedulerService.yield(); } } }; diff --git a/src/kernel/process/Scheduler.cc b/src/kernel/process/Scheduler.cc index bc22485..b3bd3d0 100644 --- a/src/kernel/process/Scheduler.cc +++ b/src/kernel/process/Scheduler.cc @@ -1,42 +1,18 @@ -/***************************************************************************** - * * - * 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 namespace Kernel { -constexpr const bool INSANE_TRACE = false; +Scheduler::Scheduler() { + ready_queue.push_back(Memory::make_unique()); + // TODO: Cleanup thread +} void Scheduler::start(Container::Vector>::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 } @@ -47,31 +23,9 @@ void Scheduler::switch_to(Thread *prev_raw, Container::Vectortid << 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(Memory::make_unique()); - log.info() << "Starting scheduling: starting thread with id: " << dec << (*(ready_queue.end() - 1))->tid << endl; - start(ready_queue.end() - 1); -} - /***************************************************************************** * Methode: Scheduler::ready * *---------------------------------------------------------------------------* @@ -79,7 +33,6 @@ void Scheduler::schedule() { *****************************************************************************/ void Scheduler::ready(Memory::unique_ptr &&thread) { Device::CPU::disable_int(); - log.debug() << "Adding to ready_queue, ID: " << dec << thread->tid << endl; ready_queue.push_back(std::move(thread)); Device::CPU::enable_int(); } @@ -100,18 +53,16 @@ void Scheduler::exit() { Device::CPU::disable_int(); if (ready_queue.size() == 1) { - log.error() << "Can't exit last thread, active ID: " << dec << (*active)->tid << endl; Device::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 + // dispatch kehrt nicht zurueck } /***************************************************************************** @@ -125,10 +76,10 @@ void Scheduler::exit() { * Parameter: * * that Zu terminierender Thread * *****************************************************************************/ -void Scheduler::kill(uint32_t tid, Memory::unique_ptr *ptr) { +void Scheduler::kill(uint16_t tid, Memory::unique_ptr *ptr) { Device::CPU::disable_int(); - uint32_t prev_tid = (*active)->tid; + uint16_t prev_tid = (*active)->tid; // Block queue, can always kill for (Container::Vector>::iterator it = block_queue.begin(); @@ -138,13 +89,12 @@ void Scheduler::kill(uint32_t tid, Memory::unique_ptr *ptr) { if (ptr != nullptr) { // Move old thread out of queue to return it - uint32_t pos = distance(block_queue.begin(), it); + uint16_t pos = 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; Device::CPU::enable_int(); return; @@ -153,7 +103,6 @@ void Scheduler::kill(uint32_t tid, Memory::unique_ptr *ptr) { // 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; Device::CPU::enable_int(); return; } @@ -165,13 +114,12 @@ void Scheduler::kill(uint32_t tid, Memory::unique_ptr *ptr) { if (ptr != nullptr) { // Move old thread out of queue to return it - uint32_t pos = distance(ready_queue.begin(), it); + uint16_t pos = 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)); @@ -179,27 +127,23 @@ void Scheduler::kill(uint32_t tid, Memory::unique_ptr *ptr) { // Just erase from queue, do not need to switch ready_queue.erase(it); - log.info() << "Killed thread from ready_queue with id: " << tid << endl; Device::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; Device::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(uint32_t tid, Memory::unique_ptr *ptr) { +void Scheduler::nice_kill(uint16_t tid, Memory::unique_ptr *ptr) { Device::CPU::disable_int(); for (Memory::unique_ptr &thread: block_queue) { if (thread->tid == tid) { thread->suicide(); - log.info() << "Nice killed thread in block_queue with id: " << tid << endl; deblock(tid); Device::CPU::enable_int(); return; @@ -209,14 +153,11 @@ void Scheduler::nice_kill(uint32_t tid, Memory::unique_ptr *ptr) { for (Memory::unique_ptr &thread: ready_queue) { if (thread->tid == tid) { thread->suicide(); - log.info() << "Nice killed thread in ready_queue with id: " << tid << endl; Device::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; Device::CPU::enable_int(); } @@ -239,33 +180,12 @@ void Scheduler::yield() { Device::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; - } Device::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 */ - - Device::CPU::disable_int(); - yield(); -} - /***************************************************************************** * Methode: Scheduler::block * *---------------------------------------------------------------------------* @@ -283,7 +203,6 @@ void Scheduler::block() { Device::CPU::disable_int(); if (ready_queue.size() == 1) { - log.error() << "Can't block last thread, active ID: " << dec << (*active)->tid << endl; Device::CPU::enable_int(); return; } @@ -292,10 +211,6 @@ void Scheduler::block() { std::size_t pos = 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 } @@ -310,7 +225,7 @@ void Scheduler::block() { * * * Parameter: that: Thread der deblockiert werden soll. * *****************************************************************************/ -void Scheduler::deblock(uint32_t tid) { +void Scheduler::deblock(uint16_t tid) { /* hier muss Code eingefuegt werden */ @@ -325,15 +240,11 @@ void Scheduler::deblock(uint32_t tid) { 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; - } Device::CPU::enable_int(); return; } } - log.error() << "Couldn't deblock thread with id: " << tid << endl; Device::CPU::enable_int(); } diff --git a/src/kernel/process/Scheduler.h b/src/kernel/process/Scheduler.h index a2f1dab..848aff1 100644 --- a/src/kernel/process/Scheduler.h +++ b/src/kernel/process/Scheduler.h @@ -20,20 +20,26 @@ namespace Kernel { class Scheduler { + friend class SchedulerService; + + friend class IdleThread; + +public: + /** + * Constructor. Initializes the IdleThread. + */ + Scheduler(); + + Scheduler(const Scheduler ©) = delete; // Verhindere Kopieren + + // TODO: Rest of constructors + private: - NamedLogger log; + [[nodiscard]] uint16_t get_active() const { + return (*active)->tid; + } - Container::Vector> ready_queue; - Container::Vector> block_queue; - - // 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 - // as we don't have to keep the active thread at the front (would only make sense with a queue) - Container::Vector>::iterator active = nullptr; - - // Scheduler wird evt. von einer Unterbrechung vom Zeitgeber gerufen, - // bevor er initialisiert wurde - uint32_t idle_tid = 0; + void ready(Memory::unique_ptr &&thread); // Roughly the old dispatcher functionality void start(Container::Vector>::iterator next); // Start next without prev @@ -41,46 +47,14 @@ private: // Switch from prev to next void switch_to(Thread *prev_raw, Container::Vector>::iterator next); - // Kann nur vom Idle-Thread aufgerufen werden (erster Thread der vom Scheduler gestartet wird) - void enable_preemption(uint32_t tid) { idle_tid = tid; } + // CPU freiwillig abgeben und Auswahl des naechsten Threads + void yield(); // Returns when only the idle thread runs - friend class IdleThread; + // Blocks current thread (move to block_queue) + void block(); // Returns on error because we don't have exceptions - void ready(Memory::unique_ptr &&thread); - -public: - Scheduler(const Scheduler ©) = 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(); - } - - [[nodiscard]] uint32_t get_active() const { - return (*active)->tid; - } - - // Scheduler initialisiert? - // Zeitgeber-Unterbrechung kommt evt. bevor der Scheduler fertig - // intiialisiert wurde! - [[nodiscard]] bool preemption_enabled() const { return idle_tid != 0U; } - - // Scheduler starten - void schedule(); - - // Helper that directly constructs the thread, then readys it - template - uint32_t ready(Args... args) { - Memory::unique_ptr thread = Memory::make_unique(std::forward(args)...); - uint32_t tid = thread->tid; - - ready(std::move(thread)); - - return tid; - } + // Deblock by tid (move to ready_queue) + void deblock(uint16_t tid); // Thread terminiert sich selbst // NOTE: When a thread exits itself it will disappear... @@ -89,29 +63,29 @@ public: void exit(); // Returns on error because we don't have exceptions // Thread mit 'Gewalt' terminieren - void kill(uint32_t tid, Memory::unique_ptr *ptr); + void kill(uint16_t tid, Memory::unique_ptr *ptr); - void kill(uint32_t tid) { kill(tid, nullptr); } + void kill(uint16_t 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(uint32_t tid, Memory::unique_ptr *ptr); + void nice_kill(uint16_t tid, Memory::unique_ptr *ptr); - void nice_kill(uint32_t tid) { nice_kill(tid, nullptr); } + void nice_kill(uint16_t tid) { nice_kill(tid, nullptr); } - // CPU freiwillig abgeben und Auswahl des naechsten Threads - void yield(); // Returns when only the idle thread runs +private: + Container::Vector> ready_queue; + Container::Vector> block_queue; + Container::Vector> exited; // TODO: Manage exited threads - // Thread umschalten; wird aus der ISR des PITs gerufen - void preempt(); // Returns when only the idle thread runs + // 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 + // as we don't have to keep the active thread at the front (would only make sense with a queue) + Container::Vector>::iterator active = nullptr; - // 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(uint32_t tid); + // TODO: Synchronization }; } diff --git a/src/kernel/process/Thread.cc b/src/kernel/process/Thread.cc index b669b37..f5f2bc5 100755 --- a/src/kernel/process/Thread.cc +++ b/src/kernel/process/Thread.cc @@ -22,20 +22,20 @@ #include "Thread.h" +// TODO: Move to thread_interface or sth. // Funktionen, die auf der Assembler-Ebene implementiert werden, muessen als // extern "C" deklariert werden, da sie nicht dem Name-Mangeling von C++ // entsprechen. extern "C" { void Thread_start(uint32_t esp); -// NOTE: Only when backing up the previous thread the esp gets updated, -// so only esp_pre is a pointer +// NOTE: Only when backing up the previous thread the esp gets updated, so only esp_pre is a pointer void Thread_switch(uint32_t *esp_prev, uint32_t esp_next); } namespace Kernel { -uint32_t ThreadCnt = 1; // Skip tid 0 as the scheduler indicates no preemption with 0 +uint16_t ThreadCnt = 2; // Skip IDs that are fixed to specific unique threads /***************************************************************************** * Prozedur: Coroutine_init * @@ -99,13 +99,11 @@ void Thread_init(uint32_t *esp, uint32_t *stack, void (*kickoff)(Thread *), void * Parameter: * * stack Stack für die neue Koroutine * *****************************************************************************/ -Thread::Thread(char *name) : stack(new uint32_t[1024]), esp(0), log(name), name(name), tid(ThreadCnt++) { +Thread::Thread() : tid(ThreadCnt++), stack(new uint32_t[1024]), esp(0) { if (stack == nullptr) { - log.error() << "Couldn't initialize Thread (couldn't alloc stack)" << endl; + // TODO: Exception return; } - - log.info() << "Initialized thread with ID: " << tid << " (" << name << ")" << endl; Thread_init(&esp, &stack[1024], kickoff, this); // Stack grows from top to bottom } @@ -118,7 +116,6 @@ void Thread::switchTo(Thread &next) { /* hier muss Code eingefügt werden */ - // log.trace() << name << ":: Has esp " << hex << esp << endl; Thread_switch(&esp, next.esp); } diff --git a/src/kernel/process/Thread.h b/src/kernel/process/Thread.h index ec81b4d..d8f7ada 100644 --- a/src/kernel/process/Thread.h +++ b/src/kernel/process/Thread.h @@ -33,25 +33,33 @@ namespace Kernel { class Thread { -private: - uint32_t *stack; - uint32_t esp; + friend class SchedulerService; + + friend class Scheduler; protected: - Thread(char *name); + // TODO: Remove this + bool running = true; // For soft exit, if thread uses infinite loop inside run(), use this as condition - NamedLogger log; - - bool running = true; // For soft exit, if thread uses infinite loop inside run(), use this as condition - char *name; // For logging - uint32_t tid; // Thread-ID (wird im Konstruktor vergeben) - friend class Scheduler; // Scheduler can access tid + // TODO: Public this + uint16_t tid; // Thread-ID (wird im Konstruktor vergeben) public: - Thread(const Thread ©) = delete; // Verhindere Kopieren + // TODO: Is this a good idea? + enum Threads : uint16_t { + IDLE = 0, + CLEANUP = 1 // TODO: Can cleanup be done in a thread? + }; +public: + Thread(); + + Thread(const Thread ©) = delete; // Verhindere Kopieren + + // TODO: Rest of constructors + + // TODO: Investigate this virtual ~Thread() { - log.info() << "Uninitialized thread, ID: " << dec << tid << " (" << name << ")" << endl; delete[] stack; } @@ -66,6 +74,10 @@ public: // Methode des Threads, muss in Sub-Klasse implementiert werden virtual void run() = 0; + +private: + uint32_t *stack; + uint32_t esp; }; }