1

Implement SchedulerService

This commit is contained in:
2022-12-08 17:12:52 +01:00
parent 14766941ca
commit a781e094ec
5 changed files with 84 additions and 203 deletions

View File

@ -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 &copy) = 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<Kernel::SchedulerService>();
while (true) {
// Util::System::out << "Idle!" << endl;
scheduler.yield();
schedulerService.yield();
}
}
};

View File

@ -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 <utility>
namespace Kernel {
constexpr const bool INSANE_TRACE = false;
Scheduler::Scheduler() {
ready_queue.push_back(Memory::make_unique<IdleThread>());
// TODO: Cleanup thread
}
void Scheduler::start(Container::Vector<Memory::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
}
@ -47,31 +23,9 @@ void Scheduler::switch_to(Thread *prev_raw, Container::Vector<Memory::unique_ptr
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(Memory::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 *
*---------------------------------------------------------------------------*
@ -79,7 +33,6 @@ void Scheduler::schedule() {
*****************************************************************************/
void Scheduler::ready(Memory::unique_ptr<Thread> &&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<Thread> *ptr) {
void Scheduler::kill(uint16_t tid, Memory::unique_ptr<Thread> *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<Memory::unique_ptr<Thread>>::iterator it = block_queue.begin();
@ -138,13 +89,12 @@ void Scheduler::kill(uint32_t tid, Memory::unique_ptr<Thread> *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<Thread> *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<Thread> *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<Thread> *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<Thread> *ptr) {
void Scheduler::nice_kill(uint16_t tid, Memory::unique_ptr<Thread> *ptr) {
Device::CPU::disable_int();
for (Memory::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);
Device::CPU::enable_int();
return;
@ -209,14 +153,11 @@ void Scheduler::nice_kill(uint32_t tid, Memory::unique_ptr<Thread> *ptr) {
for (Memory::unique_ptr<Thread> &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();
}

View File

@ -20,20 +20,26 @@
namespace Kernel {
class Scheduler {
friend class SchedulerService;
friend class IdleThread;
public:
/**
* Constructor. Initializes the IdleThread.
*/
Scheduler();
Scheduler(const Scheduler &copy) = delete; // Verhindere Kopieren
// TODO: Rest of constructors
private:
NamedLogger log;
[[nodiscard]] uint16_t get_active() const {
return (*active)->tid;
}
Container::Vector<Memory::unique_ptr<Thread>> ready_queue;
Container::Vector<Memory::unique_ptr<Thread>> 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<Memory::unique_ptr<Thread>>::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> &&thread);
// Roughly the old dispatcher functionality
void start(Container::Vector<Memory::unique_ptr<Thread>>::iterator next); // Start next without prev
@ -41,46 +47,14 @@ private:
// Switch from prev to next
void switch_to(Thread *prev_raw, Container::Vector<Memory::unique_ptr<Thread>>::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> &&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();
}
[[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<typename T, typename... Args>
uint32_t ready(Args... args) {
Memory::unique_ptr<Thread> thread = Memory::make_unique<T>(std::forward<Args>(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<Thread> *ptr);
void kill(uint16_t tid, Memory::unique_ptr<Thread> *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<Thread> *ptr);
void nice_kill(uint16_t tid, Memory::unique_ptr<Thread> *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<Memory::unique_ptr<Thread>> ready_queue;
Container::Vector<Memory::unique_ptr<Thread>> block_queue;
Container::Vector<Memory::unique_ptr<Thread>> 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<Memory::unique_ptr<Thread>>::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
};
}

View File

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

View File

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