05 - Interrupt Descriptor Table (IDT) Setup¶
With our operating system now running in Protected Mode, we've unlocked a whole new world of possibilities. We can now safely manage memory and, most importantly, handle interrupts.
Interrupts are signals sent to the CPU that tell it to stop what it's doing and run a special piece of code. This is how your OS can respond to a keypress, a mouse click, or a timer's tick, all while the CPU is busy with other tasks. Without interrupts, our OS would be completely deaf to the outside world, requiring it to constantly check for new input. This is called polling and it is highly inefficient.
There are two main types of interrupts:
- Hardware Interrupts (IRQs): These are sent by devices like the keyboard, mouse and timer. They are managed by a chip on the motherboard called the Programmable Interrupt Controller (PIC).
- Exceptions (Software Interrupts): These are generated by the CPU itself when something goes wrong, like a divide-by-zero error, a page fault, or a general protection fault.
To handle these interrupts, the CPU relies on a special lookup table known as the Interrupt Descriptor Table (IDT).
The Interrupt Descriptor Table (IDT)¶
The IDT is a table of up to 256 interrupt descriptors (or "gates"). Each descriptor contains the address of an interrupt handler function and other important information, such as the segment it belongs to and its privilege level. When an interrupt occurs, the CPU uses the interrupt number as an index into the IDT to find the appropriate handler to execute.
You've provided the code for the InterruptManager class, which is responsible for building and managing this table. Let's break down how it works.
Remapping the Programmable Interrupt Controller (PIC)¶
Before we set up our IDT, we have to deal with a legacy issue on x86 systems. The original PIC (the 8259A) was designed for the ancient 8086 processor and its default interrupt vectors (0x08 to 0x0F) conflict with modern CPU exceptions. This is a big problem. To fix this, we need to remap the PIC's interrupt vectors to a different range that won't conflict. We'll remap them to the range 0x20 to 0x2F.
The remapping process involves sending a series of Initialization Control Words (ICWs) to the PIC's command and data ports. We will handle this in the InterruptManager constructor.
Ensure your project directory is structured correctly to accommodate the new IDT files:
hashx86-os/
├── Makefile
├── linker.ld
├── kernel.h
├── kernel.cpp
├── console.h
├── console.cpp
├── asm/
│ ├── loader.asm
│ ├── load_gdt.asm
│ └── common_handler.asm <-- New assembly file
├── core/
│ ├── port.h
│ ├── port.cpp
│ ├── gdt.cpp
│ └── interrupts.cpp <-- New IDT implementation
└── include/
├── stdint.h
├── types.h
├── gdt.h
└── interrupts.h <-- New IDT header
Defining the IDT Structure¶
core/interrupts.h
#ifndef INTERRUPTS_H
#define INTERRUPTS_H
#include <types.h>
#include <core/ports.h>
#include <core/gdt.h>
#include <console.h>
class InterruptManager {
friend class InterruptHandler;
public:
static InterruptManager* activeInstance;
protected:
struct GateDescriptor {
uint16_t handlerAddressLowBits;
uint16_t gdt_codeSegmentSelector;
uint8_t reserved;
uint8_t access;
uint16_t handlerAddressHighBits;
} __attribute__((packed));
static GateDescriptor interruptDescriptorTable[256];
struct InterruptDescriptorTablePointer {
uint16_t size;
uint32_t base;
} __attribute__((packed));
static void SetInterruptDescriptorTableEntry(
uint8_t InterruptNumber,
uint16_t codeSegmentSelectorOffset,
void (*handler)(),
uint8_t DescriptorPrivilegeLevel,
uint8_t DescriptorType
);
static void IgnoreInterruptRequest();
static void HandleInterruptRequest0x00();
static void HandleInterruptRequest0x01();
static void HandleInterruptRequest0x02();
static void HandleInterruptRequest0x03();
static void HandleInterruptRequest0x04();
static void HandleInterruptRequest0x05();
static void HandleInterruptRequest0x06();
static void HandleInterruptRequest0x07();
static void HandleInterruptRequest0x08();
static void HandleInterruptRequest0x09();
static void HandleInterruptRequest0x0A();
static void HandleInterruptRequest0x0B();
static void HandleInterruptRequest0x0C();
static void HandleInterruptRequest0x0D();
static void HandleInterruptRequest0x0E();
static void HandleInterruptRequest0x0F();
static void HandleInterruptRequest0x31();
static void HandleInterruptRequest0x80();
static void HandleInterruptRequest0x81();
static void HandleException0x00();
static void HandleException0x01();
static void HandleException0x02();
static void HandleException0x03();
static void HandleException0x04();
static void HandleException0x05();
static void HandleException0x06();
static void HandleException0x07();
static void HandleException0x08();
static void HandleException0x09();
static void HandleException0x0A();
static void HandleException0x0B();
static void HandleException0x0C();
static void HandleException0x0D();
static void HandleException0x0E();
static void HandleException0x0F();
static void HandleException0x10();
static void HandleException0x11();
static void HandleException0x12();
static void HandleException0x13();
// I/O Ports for the Programmable Interrupt Controller (PIC)
Port8BitSlow picMasterCommand;
Port8BitSlow picMasterData;
Port8BitSlow picSlaveCommand;
Port8BitSlow picSlaveData;
public:
InterruptManager();
~InterruptManager();
void Activate();
void Deactivate();
static uint32_t handleInterrupt(uint8_t interruptNumber, uint32_t esp);
static uint32_t handleException(uint8_t interruptNumber, uint32_t esp);
uint32_t DoHandleInterrupt(uint8_t interruptNumber, uint32_t esp);
uint32_t DohandleException(uint8_t interruptNumber, uint32_t esp);
};
#endif
This header file defines the InterruptManager class, which encapsulates all of our interrupt handling logic.
- GateDescriptor: This structure is a C++ representation of an IDT entry, with fields for the handler's address, code segment selector and access flags. The
__attribute__((packed))directive is vital as it tells the compiler to lay out the fields exactly as defined, without adding any extra padding bytes, so it matches the hardware's expectations. - InterruptDescriptorTablePointer: This structure is similar to the
GDT_PTRwe used previously. It contains the size and base address of our IDT, which will be loaded into the CPU's IDTR (Interrupt Descriptor Table Register) using thelidtinstruction. - SetInterruptDescriptorTableEntry: This static helper function populates a single entry in the IDT.
- HandleInterruptRequest... and HandleException...: These are the function prototypes for the C++ handlers that will be called from assembly.
- Port8BitSlow: This class is used to interact with the PIC's hardware ports.
core/interrupts.cpp
#include <core/interrupts.h>
static uint16_t HWInterruptOffset = 0x20;
InterruptManager::GateDescriptor InterruptManager::interruptDescriptorTable[256];
InterruptManager* InterruptManager::activeInstance = 0;
void InterruptManager::SetInterruptDescriptorTableEntry(
uint8_t interruptNumber,
uint16_t codeSegmentSelectorOffset,
void (*handler)(),
uint8_t DescriptorPrivilegeLevel,
uint8_t DescriptorType)
{
const uint8_t IDT_DESC_PRESENT = 0x80;
interruptDescriptorTable[interruptNumber].handlerAddressLowBits = ((uint32_t)handler) & 0xFFFF;
interruptDescriptorTable[interruptNumber].handlerAddressHighBits = (((uint32_t)handler) >> 16) & 0xFFFF;
interruptDescriptorTable[interruptNumber].gdt_codeSegmentSelector = codeSegmentSelectorOffset;
interruptDescriptorTable[interruptNumber].access = IDT_DESC_PRESENT | ((DescriptorPrivilegeLevel & 3) << 5) | DescriptorType;
interruptDescriptorTable[interruptNumber].reserved = 0;
}
InterruptManager::InterruptManager()
: picMasterCommand(0x20),
picMasterData(0x21),
picSlaveCommand(0xA0),
picSlaveData(0xA1)
{
uint16_t CodeSegment = KERNEL_CODE_SELECTOR;
const uint8_t IDT_INTERRUPT_GATE = 0xE;
for (uint16_t i = 0; i < 256; i++){
SetInterruptDescriptorTableEntry(i, CodeSegment, &IgnoreInterruptRequest, 0, IDT_INTERRUPT_GATE);
};
SetInterruptDescriptorTableEntry(0x00, CodeSegment, &HandleException0x00, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x01, CodeSegment, &HandleException0x01, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x02, CodeSegment, &HandleException0x02, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x03, CodeSegment, &HandleException0x03, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x04, CodeSegment, &HandleException0x04, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x05, CodeSegment, &HandleException0x05, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x06, CodeSegment, &HandleException0x06, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x07, CodeSegment, &HandleException0x07, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x08, CodeSegment, &HandleException0x08, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x09, CodeSegment, &HandleException0x09, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x0A, CodeSegment, &HandleException0x0A, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x0B, CodeSegment, &HandleException0x0B, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x0C, CodeSegment, &HandleException0x0C, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x0D, CodeSegment, &HandleException0x0D, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x0E, CodeSegment, &HandleException0x0E, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x0F, CodeSegment, &HandleException0x0F, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x10, CodeSegment, &HandleException0x10, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x11, CodeSegment, &HandleException0x11, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x12, CodeSegment, &HandleException0x12, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(0x13, CodeSegment, &HandleException0x13, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x00, CodeSegment, &HandleInterruptRequest0x00, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x01, CodeSegment, &HandleInterruptRequest0x01, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x02, CodeSegment, &HandleInterruptRequest0x02, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x03, CodeSegment, &HandleInterruptRequest0x03, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x04, CodeSegment, &HandleInterruptRequest0x04, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x05, CodeSegment, &HandleInterruptRequest0x05, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x06, CodeSegment, &HandleInterruptRequest0x06, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x07, CodeSegment, &HandleInterruptRequest0x07, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x08, CodeSegment, &HandleInterruptRequest0x08, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x09, CodeSegment, &HandleInterruptRequest0x09, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x0A, CodeSegment, &HandleInterruptRequest0x0A, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x0B, CodeSegment, &HandleInterruptRequest0x0B, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x0C, CodeSegment, &HandleInterruptRequest0x0C, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x0D, CodeSegment, &HandleInterruptRequest0x0D, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x0E, CodeSegment, &HandleInterruptRequest0x0E, 0, IDT_INTERRUPT_GATE);
SetInterruptDescriptorTableEntry(HWInterruptOffset + 0x0F, CodeSegment, &HandleInterruptRequest0x0F, 0, IDT_INTERRUPT_GATE);
picMasterCommand.Write(0x11);
picSlaveCommand.Write(0x11);
picMasterData.Write(0x20);
picSlaveData.Write(0x28);
picMasterData.Write(0x04);
picSlaveData.Write(0x02);
picMasterData.Write(0x01);
picSlaveData.Write(0x01);
picMasterData.Write(0x00);
picSlaveData.Write(0x00);
InterruptDescriptorTablePointer idt;
idt.size = 256 * sizeof(GateDescriptor) - 1;
idt.base = (uint32_t)interruptDescriptorTable;
asm volatile("lidt %0" : : "m" (idt));
}
InterruptManager::~InterruptManager() {
}
void InterruptManager:: Activate() {
DEBUG_LOG("Activating InterruptManager.\n");
if (activeInstance != 0){
DEBUG_LOG("An active InterruptManager found.\n");
activeInstance->Deactivate();
}
activeInstance = this;
asm("sti");
DEBUG_LOG("InterruptManager Activated.\n");
}
void InterruptManager:: Deactivate() {
if (activeInstance == this){
DEBUG_LOG("Deactivating InterruptManager.\n");
activeInstance = 0;
asm("cli");
DEBUG_LOG("InterruptManager Deactivated.\n");
}
}
uint32_t InterruptManager::handleInterrupt(uint8_t interruptNumber, uint32_t esp) {
if (activeInstance != 0){
return activeInstance->DoHandleInterrupt(interruptNumber, esp);
} else {
return esp;
}
}
uint32_t InterruptManager::handleException(uint8_t interruptNumber, uint32_t esp) {
if (activeInstance != 0){
return activeInstance->DohandleException(interruptNumber, esp);
} else {
return esp;
}
}
uint32_t InterruptManager::DoHandleInterrupt(uint8_t interruptNumber, uint32_t esp) {
printf(RED, "UNHANDLED INTERRUPT: 0x%x\n", interruptNumber);
if(HWInterruptOffset <= interruptNumber && interruptNumber < HWInterruptOffset+16)
{
picMasterCommand.Write(0x20);
if(HWInterruptOffset + 8 <= interruptNumber)
picSlaveCommand.Write(0x20);
}
return esp;
}
uint32_t InterruptManager::DohandleException(uint8_t interruptNumber, uint32_t esp) {
printf(RED, "Exception occured 0x%d\n", (uint32_t)interruptNumber);
// Halt the CPU
while (1) {
asm volatile("hlt");
}
return esp;
}
This file contains the implementation of the InterruptManager class.
- InterruptManager::InterruptManager(): The constructor performs the crucial setup.
- It initializes all 256 IDT entries to a default IgnoreInterruptRequest handler. This is a safe practice to ensure no interrupt goes unhandled.
- It then overwrites the first 32 entries for CPU exceptions (
0x00to0x13). - Next, it overwrites the hardware interrupt entries from
0x20to0x2F. - The code then initializes the two PICs (master and slave) by writing a sequence of ICWs to their command and data ports. This is the remapping process we discussed earlier. The lines
picMasterData.Write(0x20);andpicSlaveData.Write(0x28);are the ones that actually remap the IRQ vectors. - Finally, it creates an InterruptDescriptorTablePointer and uses the assembly instruction
asm volatile("lidt %0" : : "m" (idt));to load the IDT's base address and size into the CPU's IDTR. - Activate(): This function sets the activeInstance and, most importantly, calls
asm("sti");to enable interrupts on the CPU. - Deactivate(): This function calls
asm("cli");to disable interrupts, which is useful for critical sections of code. - DoHandleInterrupt() & DoHandleException(): These are the core handlers that print a message to the screen when an interrupt or exception occurs. DoHandleInterrupt also sends an End-of-Interrupt (EOI) command to the PIC, which is necessary to let the PIC know the interrupt has been handled.
asm/common_handler.asm
%define IRQ_BASE 0x20
section .text
extern _ZN16InterruptManager15handleInterruptEhj
extern _ZN16InterruptManager15handleExceptionEhj
;----------------------------------------
; Macro for exceptions without error code
;----------------------------------------
%macro HandleExceptionWithoutError 1
global _ZN16InterruptManager19HandleException%1Ev
_ZN16InterruptManager19HandleException%1Ev:
mov byte [interruptnumber], %1
push dword 0 ; Push dummy error code
jmp exc_common_handler
%endmacro
;----------------------------------------
; Macro for exceptions with error code
;----------------------------------------
%macro HandleExceptionWithError 1
global _ZN16InterruptManager19HandleException%1Ev
_ZN16InterruptManager19HandleException%1Ev:
mov byte [interruptnumber], %1
jmp exc_common_handler
%endmacro
;----------------------------------------
; Macro for Interrupt Requests
;----------------------------------------
%macro HandleInterruptRequest 1
global _ZN16InterruptManager26HandleInterruptRequest%1Ev
_ZN16InterruptManager26HandleInterruptRequest%1Ev:
mov byte [interruptnumber], %1 + IRQ_BASE
push dword 0 ; Dummy error code for interrupts
jmp intr_common_handler
%endmacro
;---------------------
; Exceptions
;---------------------
HandleExceptionWithoutError 0x00 ; Divide Error
HandleExceptionWithoutError 0x01 ; Debug
HandleExceptionWithoutError 0x02 ; NMI
HandleExceptionWithoutError 0x03 ; Breakpoint
HandleExceptionWithoutError 0x04 ; Overflow
HandleExceptionWithoutError 0x05 ; BOUND Range Exceeded
HandleExceptionWithoutError 0x06 ; Invalid Opcode
HandleExceptionWithoutError 0x07 ; Device Not Available
HandleExceptionWithError 0x08 ; Double Fault
HandleExceptionWithoutError 0x09 ; Coprocessor Segment Overrun
HandleExceptionWithError 0x0A ; Invalid TSS
HandleExceptionWithError 0x0B ; Segment Not Present
HandleExceptionWithError 0x0C ; Stack-Segment Fault
HandleExceptionWithError 0x0D ; General Protection Fault
HandleExceptionWithError 0x0E ; Page Fault
HandleExceptionWithoutError 0x0F ; Reserved
HandleExceptionWithoutError 0x10 ; x87 FPU Error
HandleExceptionWithError 0x11 ; Alignment Check
HandleExceptionWithoutError 0x12 ; Machine Check
HandleExceptionWithoutError 0x13 ; SIMD Floating-Point Exception
;---------------------
; Interrupt Requests
;---------------------
HandleInterruptRequest 0x00
HandleInterruptRequest 0x01
HandleInterruptRequest 0x02
HandleInterruptRequest 0x03
HandleInterruptRequest 0x04
HandleInterruptRequest 0x05
HandleInterruptRequest 0x06
HandleInterruptRequest 0x07
HandleInterruptRequest 0x08
HandleInterruptRequest 0x09
HandleInterruptRequest 0x0A
HandleInterruptRequest 0x0B
HandleInterruptRequest 0x0C
HandleInterruptRequest 0x0D
HandleInterruptRequest 0x0E
HandleInterruptRequest 0x0F
HandleInterruptRequest 0x31
;------------------------
; Common exception handler
;------------------------
exc_common_handler:
push ebp
push edi
push esi
push edx
push ecx
push ebx
push eax
push esp
push dword [interruptnumber]
call _ZN16InterruptManager15handleExceptionEhj
mov esp, eax
pop eax
pop ebx
pop ecx
pop edx
pop esi
pop edi
pop ebp
add esp, 4
iret
;------------------------
; Common interrupt handler
;------------------------
intr_common_handler:
push ebp
push edi
push esi
push edx
push ecx
push ebx
push eax
push esp
push dword [interruptnumber]
call _ZN16InterruptManager15handleInterruptEhj
mov esp, eax
pop eax
pop ebx
pop ecx
pop edx
pop esi
pop edi
pop ebp
add esp, 4
iret
global _ZN16InterruptManager22IgnoreInterruptRequestEv
_ZN16InterruptManager22IgnoreInterruptRequestEv:
iret
section .data
interruptnumber: db 0
This is a critical piece of the puzzle. The CPU automatically pushes a set of registers onto the stack when an interrupt occurs, but it doesn't save all of them. This assembly file acts as a "trampoline" to do the following:
- Save the state: It pushes all general-purpose registers (
eax,ebx,ecx,edx, etc.) onto the stack, saving the CPU's full state. - Call the C++ handler: It pushes the interrupt number and a pointer to the saved stack to call our C++ functions (
handleInterruptorhandleException). This is where the control is passed from assembly to C++. - Restore the state: After the C++ function returns, the assembly code pops all the registers back into their original locations.
- Return from interrupt: Finally, the iret instruction is executed. This is a special instruction that pops the
error code,code segment,EIPandflagsfrom the stack, allowing the CPU to resume execution exactly where it left off before the interrupt.
Integration and Build¶
To integrate these new files, you need to update kernel.
kernel.h¶
Add the include for the new header file.
#include <core/interrupts.h>
kernel.cpp¶
Inside the kernelMain function, you need to instantiate the InterruptManager and activate it after the GDT is loaded.
#include <kernel.h>
extern "C" void kernelMain(void* multiboot_structure, uint32_t magicnumber) {
clearScreen(); // Clear the screen upon kernel entry
printf(combineColors(YELLOW, BLACK), "Welcome to My OS!\n");
printf(WHITE, "Initializing GDT...\n");
// Call the GDT initialization function
gdt_init();
printf(LIGHT_GREEN, "GDT initialized and loaded successfully.\n");
printf(LIGHT_CYAN, "Transitioned to Protected Mode.\n");
InterruptManager interruptManager; <--- Instantiate the InterruptManager
interruptManager.Activate(); <--- Activate it
// Keep the kernel running in an infinite loop
while (1) {
asm volatile("hlt"); // Halt the CPU
}
}
Makefile Update¶
Finally, update the Makefile to include the new object files so they are compiled and linked with your kernel.
\# ... (rest of the Makefile) ...
objects \= asm/loader.o \\
asm/load\_gdt.o \\
asm/common\_handler.o \\
kernel.o \\
console.o \\
core/ports.o \\
core/gdt.o \\
core/interrupts.o
\# ... (rest of the Makefile) ...
Building and Running Your OS¶
To build and run your OS with the new interrupt system, open your terminal, navigate to your project directory and run the run command.
make run
This will compile everything, create a new bootable ISO and launch QEMU.
Expected Output¶
Your console output should now show an additional message confirming that interrupts have been enabled.
- Welcome to My OS!
- Initializing GDT...
- GDT initialized and loaded successfully.
- Transitioned to Protected Mode.
- [DEBUG]:Activating InterruptManager.
- [DEBUG]:InterruptManager Activated.
- UNHANDLED INTERRUPT: 0x20 (Timer Interrupt)
This output confirms that your IDT setup is correct and your OS is now support interrupts.

Conclusion¶
You've successfully built the foundation for a responsive, event-driven operating system. By implementing the Interrupt Descriptor Table and remapping the Programmable Interrupt Controller, you've given your kernel the ability to handle both internal exceptions and external hardware signals. This is a significant leap forward, as your OS is no longer a passive program in an endless loop but can now actively react to the world around it. The sti instruction is the final step in this process, arming the CPU to receive and process these crucial signals.
In the next tutorial, we will put this to a practical test by creating a simple keyboard driver.
See Also¶
- OSDev: Interrupt Descriptor Table
- Lowlevel.eu: Interrupt Descriptor Table
- OSDev: IDT Problems
- Bran's Kernel Development Tutorial: Interrupt Descriptor Table