Handout
This commit is contained in:
67
kernel/sync/bellringer.cc
Normal file
67
kernel/sync/bellringer.cc
Normal 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
79
kernel/sync/bellringer.h
Normal 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
44
kernel/sync/message.h
Normal 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
22
kernel/sync/semaphore.cc
Normal 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
53
kernel/sync/semaphore.h
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user