Handout
This commit is contained in:
12
interrupt/epilogues.cc
Normal file
12
interrupt/epilogues.cc
Normal 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
50
interrupt/epilogues.h
Normal 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
30
interrupt/guard.cc
Normal 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
121
interrupt/guard.h
Normal 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
14
interrupt/handlers.asm
Normal 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
102
interrupt/handlers.cc
Normal 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
109
interrupt/handlers.h
Normal 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);
|
||||
Reference in New Issue
Block a user