This commit is contained in:
Niklas Gollenstede
2025-10-31 22:37:36 +01:00
commit 174fe17e89
197 changed files with 79558 additions and 0 deletions

67
kernel/sync/bellringer.cc Normal file
View File

@@ -0,0 +1,67 @@
#include "./bellringer.h"
#include "../arch/lapic.h"
#include "../debug/assert.h"
#include "../interrupt/guard.h"
#include "arch/core_interrupt.h"
// check: Checks whether bells are running out of time and rings them if
// necessary
void Bellringer::check(Vault &vault) {
if (Bell *bell = bells.first()) {
bell->counter--;
while ((bell = bells.first()) && bell->counter == 0) {
bells.dequeue();
vault.scheduler.ready(bell->thread);
}
}
}
// job: Give a bell to the bellringer & ring it when the specified time ran out.
void Bellringer::sleep(Vault &vault, unsigned int ms) {
Bell bell_tmp;
Bell *bell = &bell_tmp;
bell->thread = vault.scheduler.active();
assert(bell != nullptr);
unsigned int ticks = ms * 1000 / LAPIC::Timer::interval();
assert(ticks != 0);
if (bells.first() == nullptr) {
// queue is empty; enqueue new bell as first element
bells.enqueue(*bell);
} else {
// To reduce the amount of work done in Bellringer::check (which
// is called frequently), we sort the queue by relative waiting
// times: Each bell is assigned the delta between its waiting
// time and its predecessor's waiting time.
// This allows us to only "tick" the first bell.
Bell *next = nullptr;
Bell *pred = nullptr;
for (Bell *p = bells.first(); p != nullptr; p = bells.next(*p)) {
if (ticks < p->counter) {
next = p;
break;
}
ticks -= p->counter;
pred = p;
}
// If there is a predecessor, enqueue the bell after pred;
// otherwise insert at the beginning.
if (pred != nullptr) {
bells.insertAfter(*pred, *bell);
} else {
bells.insertFirst(*bell);
}
// If we have a predecessor, reduce its waiting time by our waiting
// time, as _next_ will have to wait until we finished waiting.
if (next != nullptr) {
next->counter -= ticks;
}
}
bell->counter = ticks;
vault.scheduler.resume(false);
}
// Are there bells in the queue?
bool Bellringer::bellPending() const { return !bells.is_empty(); }

79
kernel/sync/bellringer.h Normal file
View File

@@ -0,0 +1,79 @@
/*! \file
* \brief \ref Bellringer that manages and activates time-triggered activities.
*/
#pragma once
#include "../object/queue.h"
#include "../thread/thread.h"
#include "../types.h"
struct Vault;
/*! \brief Manages and activates time-triggered activities.
* \ingroup ipc
*
* The Bellringer is regularly activated and checks whether any of the bells
* should ring. The bells are stored in a Queue<Bell> that is managed by the
* Bellringer. A clever implementation avoids iterating through the whole list
* for every iteration by keeping the bells sorted and storing delta times. This
* approach leads to a complexity of O(1) for the method called by the timer
* interrupt in case no bells need to be rung.
*/
class Bellringer {
// Prevent copies and assignments
Bellringer(const Bellringer&) = delete;
Bellringer& operator=(const Bellringer&) = delete;
/**
* @brief Contains a Thread and its remaining waiting time in timer ticks
*
*/
struct Bell {
// link pointer to the next bell in the bellringer's bell list
Bell* queue_link = nullptr;
Thread* thread;
size_t counter;
};
/*! \brief List of bells currently managed.
*
* This list contains non-expired bells enqueued by job().
* These bells will be checked on every call to check().
*
* All elements that should be inserted into a Queue instance
* are required to be derived from Queue<Bell>::Node.
*/
Queue<Bell> bells;
public:
// constructor
Bellringer() {}
/*! \brief Checks whether there are bells to be rung.
*
* Every call to check elapses a tick. Once such a tick reduces a bell's
* remaining time to zero, the bell will be rung (i.e., its thread is
* ready).
*
* \param vault The vault containing the bellringer instance
*
*/
void check(Vault& vault);
/*! \brief Puts the calling thread to sleep for `ms` milliseconds by
* enqueuing a \ref Bell.
*
* \param vault The vault containing the bellringer instance
* \param ms Number of milliseconds that should be waited before
* ringing the bell
*
*/
void sleep(Vault& vault, unsigned int ms);
/*! \brief Checks whether there are enqueued bells.
* \return true if there are enqueued bells, false otherwise
*
*/
bool bellPending() const;
};

44
kernel/sync/message.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "../object/queue.h"
#include "../types.h"
struct Message {
const size_t pid; ///< Sender Thread ID of message
const uintptr_t sbuffer; ///< Send buffer
const size_t ssize; ///< Send buffer size
const uintptr_t rbuffer; ///< Receive buffer
const size_t rsize; ///< Receive buffer size
Message *queue_link = nullptr; ///< Next message in the message queue
/*! \brief Constructor
* \param pid Sender Thread ID of message
* \param sbuffer Send buffer
* \param ssize Send buffer size
* \param rbuffer Receive buffer
* \param rsize Receive buffer size
*/
explicit Message(int pid, uintptr_t sbuffer = 0, size_t ssize = 0,
uintptr_t rbuffer = 0, size_t rsize = 0)
: pid(pid),
sbuffer(sbuffer),
ssize(ssize),
rbuffer(rbuffer),
rsize(rsize) {}
/*! \brief Helper to retrieve (and remove) a message from the queue
* \param pid Thread id of message
* \param queue Queue with message
* \return Pointer to message or nullptr
*/
static Message *dequeueByPID(size_t pid, Queue<Message> &queue) {
for (Message *m : queue) {
if (m->pid == pid) {
return queue.remove(m);
}
}
return nullptr;
}
};

22
kernel/sync/semaphore.cc Normal file
View File

@@ -0,0 +1,22 @@
#include "./semaphore.h"
#include "../interrupt/guard.h"
#include "../thread/thread.h"
void Semaphore::p(Vault &vault) {
if (counter == 0) {
waiting.enqueue(*vault.scheduler.active());
vault.scheduler.resume(false);
} else {
counter--;
}
}
void Semaphore::v(Vault &vault) {
Thread *customer = waiting.dequeue();
if (customer != nullptr) {
vault.scheduler.ready(customer);
} else {
counter++;
}
}

53
kernel/sync/semaphore.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include "../object/queue.h"
#include "../types.h"
/*! \file
* \brief \ref Semaphore for synchronization of threads.
*/
/*!
* \defgroup ipc Inter-Process Communication
* \brief Communication between threads
*/
// Forward declarations to break cyclic includes
struct Vault;
class Thread;
/*! \brief Semaphore used for synchronization of threads.
* \ingroup ipc
*
* The class Semaphore implements the concept of counting semaphores.
* Waiting threads are parked inside a \ref Queue.
*/
class Semaphore {
// Prevent copies and assignments
Semaphore(const Semaphore&) = delete;
Semaphore& operator=(const Semaphore&) = delete;
unsigned counter;
Queue<Thread> waiting;
public:
/*! \brief Constructor; initialized the counter with provided value `c`
* \param c Initial counter value
*/
explicit Semaphore(unsigned c = 0) : counter(c) {}
/*! \brief Wait for access to the critical area.
*
* Enter/decrement/wait operation: If the counter is greater than 0, then
* it is decremented by one. Otherwise the calling thread will be enqueued
* to wait until it can do that.
*
*/
void p(Vault& vault);
/*! \brief Leave the critical area.
*
* Leave/increment operation: If there are threads waiting, wake the
* first one; otherwise increment the counter by one.
*
*/
void v(Vault& vault);
};