220 likes | 234 Views
Exceptions and Interrupts. How does Linux handle service- requests from the cpu and from the peripheral devices?. The ‘fetch-execute’ cycle. Normal programming assumes this ‘cycle’: 1) Fetch the next instruction from ram 2) Interpret the instruction just fetched
E N D
Exceptions and Interrupts How does Linux handle service- requests from the cpu and from the peripheral devices?
The ‘fetch-execute’ cycle Normal programming assumes this ‘cycle’: • 1) Fetch the next instruction from ram • 2) Interpret the instruction just fetched • 3) Execute this instruction as decoded • 4) Advance the cpu instruction-pointer • 5) Go back to step 1
But ‘departures’ may occur • Circumstances may arise under which it would be inappropriate for the cpu to proceed with this fetch-execute cycle • Examples: • An ‘external device’ may ask for service • An interpreted instruction may be ‘illegal’ • An instruction ‘trap’ may have been set
Faults • If the cpu detects that an instruction it has just decoded would be illegal to execute, it cannot proceed with the fetch-execute cycle • This type of situation is known as a ‘fault’ • It is detected BEFORE incrementing the IP • The cpu will react by: 1) saving some info on its stack, then 2) switching control to a special fault-handling routine
Fault-Handling • The causes of ‘faults’ may often be fixed • A few examples: • 1) Writing to a ‘read-only’ segment • 2) Reading from a ‘not present’ segment • 3) Executing an out-of-bounds instruction • 4) Executing a ‘privileged’ instruction • If the problem can be remedied, the cpu can resume executing from where it left off
Traps • The cpu may have been programmed to automatically switch control to a ‘debugger’ Program after it has executed an instruction • This type of situation is known as a ‘trap’ • It is activated AFTER incrementing the IP • It is accomplished by setting the TF flag • Just as with faults, the cpu will react: save return-info, and jump to trap-handler
Faults versus Traps Both ‘faults’ and ‘traps’ occur at points within a computer program which are ‘predictible’ (i.e., triggered by pre-planned instructions), so they are ‘in sync’ with the program (and thus are called ‘synchronous’ interruptions in the normal fetch-execute cycle) The cpu responds in a similar way to faults and to traps – yet what gets saved differs!
Faults vs Traps (continued) • With a ‘fault’: the saved address is for the instruction that was the cause of the fault – so that instruction will get re-fetched after the cause of the problem has been fixed • With a ‘trap’: the saved address is for the instruction following the one which triggered the trap
Synchronous vs Asynchronous • Devices which are ‘external’ to the cpu may under go certain changes-of-state that the system needs to take notice of • These changes occur independently of what the cpu is doing, and so cannot be predicted from reading a program’s code • They are ‘asynchronous’ to the program, and are known as ‘interrupts’
Interrupt Handling • As with faults and traps, the cpu responds to interrupt-requests by saving some info on the stack and then jumping to a special ‘interrupt-handler’ routine designed to take appropriate action for the particular device which caused the interrupt to occur
The ‘Interrupt Controller’ • Special hardware is responsible for telling the cpu when an external device wants to ‘interrupt’ the current program • This hardware is the ‘Interrupt Controller’ • It needs to let the cpu know which among several devices is the one needing attention • It also needs to prioritize multiple requests
Cascaded 8259 PICs Master PIC IRQ0 Slave PIC IRQ8 IRQ1 IRQ9 CPU IRQ2 IRQ10 INTR IRQ3 IRQ11 IRQ4 IRQ12 INTA IRQ5 IRQ13 IRQ6 IRQ14 IRQ7 IRQ15 PC Design (for single-processor systems)
Two Crucial Data Tables • The Global Descriptor Table (GDT) defines memory segments, and enforces their access-privileges (by using ‘segment descriptors’) • The Interrupt Descriptor Table (IDT) defines entry-points for the code-routines which handle ‘interrupts’ and ‘exceptions’
How does CPU find GDT/IDT? • Two dedicated registers: GDTR and IDTR • Both have identical 48-bit format: Segment Base Address Segment Limit 47 16 15 0 Kernel must setup these registers during system startup (set-and-forget) Privileged instructions: LGDT and LIDT used to set these register-values Unprivileged instructions: SGDT and SIDT used for reading register-values
Segment-Descriptor Format 56 39 63 32 Base[31…24] Limit [19..16] Access attributes Base[23…16] Base[ 15 … 0 ] Limit[ 15 ... 0 ] 31 16 15 0 Quadword (64-bits)
Gate-Descriptor Format 63 32 Entrypoint Offset[ 31…16 ] Gate type code (Reserved) Code-segment Selector Entrypoint Offset[ 15…0 ] 0 31 Quadword (64-bits)
Intel-Reserved ID-Numbers • Of the 256 possible interrupt ID-numbers, Intel reserves the first 32 for ‘exceptions’ • Operating systems such as Linux are free to use the remaing 224 available interrupt ID-numbers for their own purposes (e.g., for service-requests from external devices, or for other purposes such as system-calls
Some Intel-defined exceptions • 0: divide-overflow fault • 1: debug traps and faults • 2: non-maskable interrupts • 3: debug breakpoint trap • 4: INTO detected overflow • 5: BOUND range exceeded • 6: Undefined Opcode • 7: Coprocessor Not Available • 8: Double-Fault • 9: (reserved) • 10: Invalid Task-State Segment • 11: Segment-Not-Present fault • 12: Stack fault • 13: General Protection Exception • 14: Page-Fault Exception • 15: (reserved)
Error-Codes • A few of the Intel-defined exceptions also push a diagnostic ‘error-code’ onto the cpu stack (in addition to pushing the EFLAGS, CS and EIP register-values); the ‘handler’ must discard that error-code before it can execute the ‘iret’ instruction to resume the program that got interrupted (Exceptions 8 and 10 through 14 generate error-codes)
Using our ‘physmem’ driver We can look at the kernel’s GDT/IDT tables • We can find them (using ‘sgdt’ and ‘sidt’) • We can ‘read’ them by using ‘physmem’ Demo program on course website: showidt.cpp It prints out the 256 IDT Gate-Descriptors
In-Class Exercise • Write a similar program (showgdt.cpp) • Two ‘big’ issues: size of GDT isn’t known in advance (whereas the IDT is of a known size) labeling for GDT entries is by offset (whereas the IDT entries use index) • There could be some ‘other’ issues as well
Some Questions • How many “non-null” descriptors in GDT? (There’s always at least one that’s “null”) • What kinds of descriptors does Linux use? Sizes? Access-rights? Code/Data? TSS?