This commit is contained in:
Niklas Gollenstede
2025-04-14 11:20:52 +02:00
commit 5a2e32aaeb
126 changed files with 16742 additions and 0 deletions

12
interrupt/epilogues.cc Normal file
View File

@@ -0,0 +1,12 @@
#include "epilogues.h"
#include "guard.h"
namespace Epilogues {
void keyboard(Vault& g) { (void)g; }
void timer(Vault& g) { (void)g; }
void assassin(Vault& g) { (void)g; }
}; // namespace Epilogues

50
interrupt/epilogues.h Normal file
View File

@@ -0,0 +1,50 @@
/*! \file
* \brief XXX: Write summary
*/
#pragma once
#include "../types.h"
struct Vault;
/*! \brief A handler function for an epilogue.
* \ingroup interrupts
*
* It receives the vault directly, because it is executed on level 1/2 (by the
* \ref Guard) .
*
* \note Since it does only receive one parameter, other data must be passed
* in a different way.
*/
using Epilogue = void (*)(Vault&);
namespace Epilogues {
/*!
* @brief The keyboard epilogue.
*
* Handle the keyboard Key that has been fetched during the prologue.
*
* \todo(13) print the stored character
* \todo(15) Store the key to the keyboard buffer for user threads. Wake user
* threads waiting for a key using the key semaphore.
*
* @param g
*/
void keyboard(Vault& g);
/*!
* @brief Timer epilogue
* \todo(15) Preemptively reschedule threads
* \todo(16) Check the bellringer
* \todo(17) Refresh screen with fixed FPS rate
* @param g
*/
void timer(Vault& g);
/*! \brief Examine the `dying flag` of the current thread and reschedule if
* it is set.
*
* \todo(15) Implement the rescheduling (in \MPStuBS only)
*/
void assassin(Vault& g);
}; // namespace Epilogues

30
interrupt/guard.cc Normal file
View File

@@ -0,0 +1,30 @@
#include "guard.h"
#include "../arch/core.h"
#include "../debug/output.h"
#include "../object/bbuffer.h"
#include "../sync/ticketlock.h"
#include "epilogues.h"
#define FOR_CURRENT_CORE [Core::getID()]
//! \brief The protected data for the epilogue level
static Vault global_vault;
// lists of pending epilogues
static BBuffer<Epilogue, 32> epilogue_queue[Core::MAX] = {};
// Big Kernel Lock (BKL) for the epilogue level
constinit Ticketlock global_lock;
constinit bool epi_flag[Core::MAX] = {false};
Vault::Vault() {}
Guarded::~Guarded() { Guard::leave(); }
Guarded Guard::enter() { while (true); }
void Guard::leave() {}
void Guard::relay(Epilogue handler) { (void)handler; }
const Vault &Guard::unsafeConstAccess() { return global_vault; }

121
interrupt/guard.h Normal file
View File

@@ -0,0 +1,121 @@
/*! \file
* \brief \ref Guard synchronizes access to epilogue level
*/
#pragma once
#include "../object/bbuffer.h"
#include "../object/key.h"
#include "../types.h"
#include "epilogues.h"
//! \brief The epilogue vault contains the protected data for the epilogue level
struct Vault {
Vault();
// no copy
Vault(const Vault&) = delete;
Vault& operator=(const Vault&) = delete;
};
/*! \brief Lock guard that provides access to the epilogue \ref Vault
*
* This object automatically unlocks the \ref Guard when it goes out of scope.
*/
class Guarded {
public:
//! This constructor should only be used by the \ref Guard
explicit Guarded(Vault& vault) : _vault(vault) {}
//! Leave the critical section
~Guarded();
//! Access the epilogue vault
Vault& vault() { return _vault; }
const Vault& vault() const { return _vault; }
// no copy
Guarded(const Guarded&) = delete;
Guarded& operator=(const Guarded&) = delete;
private:
Vault& _vault;
};
/*! \brief Synchronizes the kernel with interrupts using the Prologue/Epilogue
* Model \ingroup interrupts
*
* The Guard is used to synchronize between "normal" core activities (currently
* just the text output, later system calls) and interrupt handling routines.
* For this purpose, \ref Guard has to contain one ore more \ref BBuffer
* "queues", in which \ref Epilogue functions can be added. This is necessary if
* the critical section is occupied at the time when an interrupt occurs, and
* the
* \ref Epilogue cannot be executed immediately. The queued epilogues are
* processed when leaving the critical section.
*
* **Hints:**
* - The epilogue queue is a central data structure, whose consistency
* must be ensured. The implementation provided by the \ref BBuffer is not
* entirely safe against concurrency. You need to disable
* interrupts during operations on the buffer.
* - In \MPStuBS, you need a separate epilogue queue for each core,
* in which each processor serializes *its* epilogues. However, epilogues
* on different cores could then be executed in parallel, since the
* critical section is managed separately on a per-core base. This must be
* prevented by using a global \ref Ticketlock to avoid concurrent
* execution of epilogues -- there must never be more than one epilogue
* active on the whole system at the same time!<br>
* *Please note:* This [giant lock](https://en.wikipedia.org/wiki/Giant_lock)
* (synchronizing all cores) should not be confused with the (core-specific)
* flag variable that marks only the entry to the epilogue level on the
* corresponding core!
* - Interrupts should be disabled for as short as possible. Due to this
* reason, the prologue/epilogue model allows epilogues to be interrupted
* by prologues. This means that interrupts should be
* \ref Core::Interrupt::enable "enabled" again before the epilogue is
* executed (this includes notifying the APIC about the
* \ref LAPIC::endOfInterrupt() "End-Of-Interrupt")
*/
namespace Guard {
/*! \brief Entering the critical section from level 0.
*
* Entering the critical section has to be handled differently depending on
* the system: In a single-core system it is sufficient to mark the entry
* by just setting a flag variable (since only one control flow can enter
* the critical section at the same time). However, as soon as there are
* multiple cores, this is no longer the case. If a core wants to enter the
* critical section while *another* core is already in there, it should
* (actively) wait in this method until the critical area is released again.
*
* \todo(13) Implement Method
*/
Guarded enter();
/*! \brief Leaving the critical section.
*
* Leaves the critical section and processes all remaining (enqueued) epilogues.
* This may only be called while in level 1/2 after calling \ref enter().
*
* Note: Usually, this method is called by the destructor of the \ref
* Guarded.
*
* \todo(13) Implement Method
*/
void leave();
/*! \brief A prologue wants its epilogue to be processed (entering from level
* 1).
*
* This method is called by the interrupt handlers.
* Whether this is done immediately or the epilogue just enqueued to the
* epilogue queue depends on whether the critical section on *this* Core is
* accessible or not.
*
* \todo(13) Implement Method
*/
void relay(Epilogue handler);
/*! \brief Access the epilogue vault without taking the lock.
* Beware race conditions!
*/
const Vault& unsafeConstAccess();
} // namespace Guard

14
interrupt/handlers.asm Normal file
View File

@@ -0,0 +1,14 @@
[SECTION .text]
[EXTERN handle_keyboard]
[GLOBAL handle_keyboard_asm]
; entry point for an interrupt to trigger a kernelpanic
;
align 16
handle_keyboard_asm:
; The interrupt may be triggered asynchronously, therefore the whole context
; has to be saved and restored, or the interrupted code might not be able to
; continue. The C++ compiler will only generates code to preserve
; non-scratch registers in the high-level interrupt handler -- the scratch
; registers have to be saved (and restored later) manually!
; TODO(12): Implement the context save and restore for the keyboard interrupt

102
interrupt/handlers.cc Normal file
View File

@@ -0,0 +1,102 @@
#include "handlers.h"
#include "../arch/core_cr.h"
#include "../arch/idt.h"
#include "../arch/lapic.h"
#include "../arch/system.h"
#include "../debug/kernelpanic.h"
#include "../debug/output.h"
void printContext(const InterruptContext *context) {
DBG << "ip: " << hex << context->cs << ':' << context->ip
<< " sp: " << context->ss << ':' << context->sp << " flags" << bin
<< context->flags << endl;
}
[[gnu::interrupt]] void handle_invalid_opcode(InterruptContext *context) {
DBG << "Invalid opcode encoutered" << endl;
printContext(context);
kernelpanic("Invalid opcode!");
}
[[gnu::interrupt]] void handle_double_fault(InterruptContext *context,
uint64_t error) {
(void)error;
DBG << "Double fault encoutered" << endl;
printContext(context);
kernelpanic("Double fault!");
}
[[gnu::interrupt]] void handle_invalid_tss(InterruptContext *context,
uint64_t error) {
DBG << "Invalid tss encoutered. Offending selector idx: " << dec << error
<< endl;
printContext(context);
kernelpanic("Invalid TSS!");
}
[[gnu::interrupt]] void handle_general_protection_fault(
InterruptContext *context, uint64_t error) {
DBG << "General protection fault encoutered. Error code: " << dec << error
<< endl;
printContext(context);
kernelpanic("General protection fault!");
}
enum PAGE_FAULT_ERROR {
PF_ERR_PRESENT = 0x1,
PF_ERR_WRITE = 0x2,
PF_ERR_USER = 0x4,
PF_ERR_RESERVED = 0x8,
PF_ERR_IFETCH = 0x10,
};
[[gnu::interrupt]] void handle_page_fault(InterruptContext *context,
uint64_t error) {
(void)error;
DBG << "Page fault encoutered at linear address " << hex
<< Core::CR<2>::read() << endl
<< (error & PF_ERR_PRESENT ? "present" : "non-present") << " page|"
<< (error & PF_ERR_WRITE ? "write" : "read") << " access|"
<< (error & PF_ERR_USER ? "user" : "supervisor") << "|"
<< (error & PF_ERR_RESERVED ? "reserved bit int pte" : "") << "|"
<< (error & PF_ERR_IFETCH ? "instrution" : "data") << " fetch|" << endl;
printContext(context);
kernelpanic("Page fault!");
}
void handle_keyboard() {}
[[gnu::interrupt]] void handle_panic(InterruptContext *context) {
(void)context;
}
[[gnu::interrupt]] void handle_timer(InterruptContext *context) {
(void)context;
}
[[gnu::interrupt]] void handle_assassin(InterruptContext *context) {
(void)context;
}
[[gnu::interrupt]] void handle_wakeup(InterruptContext *context) {
(void)context;
}
void initInterruptHandlers() {
// Some handlers that are useful for debugging
IDT::set(Core::Interrupt::Vector::INVALID_OPCODE,
IDT::InterruptDescriptor::Returning(handle_invalid_opcode));
IDT::set(Core::Interrupt::Vector::DOUBLE_FAULT,
IDT::InterruptDescriptor::DivergingWithError(handle_double_fault));
IDT::set(Core::Interrupt::Vector::INVALID_TSS,
IDT::InterruptDescriptor::ReturningWithError(handle_invalid_tss));
IDT::set(Core::Interrupt::Vector::GENERAL_PROTECTION_FAULT,
IDT::InterruptDescriptor::ReturningWithError(
handle_general_protection_fault));
IDT::set(Core::Interrupt::Vector::PAGE_FAULT,
IDT::InterruptDescriptor::ReturningWithError(handle_page_fault));
// TODO: Add more handlers here
// Load the idt pointer
IDT::load();
}

109
interrupt/handlers.h Normal file
View File

@@ -0,0 +1,109 @@
/*! \file All interrupts need to start somewhere. This file contains the entry
* points for all interrupts handled by StuBS.
* \brief The Interrupt Subsystem
* \defgroup interrupts Interrupt Handling
*/
#pragma once
#include "../types.h"
/*! \brief Initialize the IDT.
*
* The interrupt subsystem of StubBS contains all functionality to accept
* interrupts from the hardware and process them.
* In later exercises the interrupts will enable applications to
* execute core functionality (system calls).
* The entry point for the interrupt subsystem is the function
* 'interrupt_entry_VECTOR' (in `interrupt/handler.asm`).
*
* \todo(12) Register your own interrupt handlers
*/
void initInterruptHandlers();
struct InterruptContext;
/*!
* @brief Helper function for printf-debugging the InterruptContext
*/
void printContext(const InterruptContext *context);
/*!
* @brief An interrupt handler for the INVALID_OPCODE trap
*/
[[gnu::interrupt]] void handle_invalid_opcode(InterruptContext *context);
/*!
* @brief A double fault occurs when another exception occurs during exception
* handling.
*
* In this case, the OS cannot recover anymore. This can happen e.g.
* during page fault handling.
*/
[[gnu::interrupt]] void handle_double_fault(InterruptContext *context,
uint64_t error);
/*!
* @brief If the task state segment is configured incorrectly, the kernel cannot
* switch the privilege levels during interrupts.
*/
[[gnu::interrupt]] void handle_invalid_tss(InterruptContext *context,
uint64_t error);
/*!
* @brief When the CPU tried to execute an unprivileged opcode or exceeds
* segmentation bounds, the GPF exception is raised.
*/
[[gnu::interrupt]] void handle_general_protection_fault(
InterruptContext *context, uint64_t error);
/*!
* @brief With paging enabled, an invalid access to a memory page causes a page
* fault.
*/
[[gnu::interrupt]] void handle_page_fault(InterruptContext *context,
uint64_t error);
extern "C" { // disable C++ name mangling for asm function
/*! \brief Assembly interrupt handler for the keyboard.
*
* On keyboard interrupt, the register state is saved to and restored from the
* stack. This function wraps the handle_keyboard C-function.
*
* \todo(12) Implement in assembly
*/
[[gnu::interrupt]] void handle_keyboard_asm(InterruptContext *context);
/*! \brief Higher-level Interrupt handler for the keyboard.
*
* On keyboard interrupt, the PS2-Controller may contain a valid Key that has to
* be fetched.
*
* \todo(12) Fetch a single key
* \todo(13) Extend to use the Prologue-Epilogue pattern
*/
void handle_keyboard();
}
/*! \brief handle_panic
*
* \todo(12) Trigger a kernel panic
*/
[[gnu::interrupt]] void handle_panic(InterruptContext *context);
/*! \brief handle_timer
*
* \todo(15) Handle the timer interrupt
*/
[[gnu::interrupt]] void handle_timer(InterruptContext *context);
/*! \brief handle_assassin
*
* Handler for the assassin IPI, i.e. a thread shall be killed.
*
* \todo(15) Handle the assassin interrupt (in \MPStuBS only)
*/
[[gnu::interrupt]] void handle_assassin(InterruptContext *context);
/*! \brief handle_wakeup
*
* In Multicore systems, an IPI is used to wake a sleeping core.
*
* \todo(16) Handle the wakeup interrupt (in \MPStuBS only)
*/
[[gnu::interrupt]] void handle_wakeup(InterruptContext *context);