Interrupt Handling
An interrupt notifies the CPU of some event. Much of the work of an operating system relates to interrupts in one way or another. For our purposes, we classify interrupts into two broad categories:
- Internal interrupts, that is, interrupts caused directly by CPU instructions.
- System calls, attempts at invalid memory access (page faults), and attempts to divide by zero are some activities that cause internal interrupts.
- Because they are caused by CPU instructions, internal interrupts are synchronous or synchronized with CPU instructions.
intr_disable()
does not disable internal interrupts.
- External interrupts, that is, interrupts originating outside the CPU.
- These interrupts come from hardware devices such as the system timer, keyboard, serial ports, and disks.
- External interrupts are asynchronous, meaning that their delivery is not synchronized with instruction execution. Handling of external interrupts can be postponed with
intr_disable()
and related functions (see section Disabling Interrupts).
The CPU treats both classes of interrupts largely the same way, so PintOS has common infrastructure to handle both classes.
- The following section describes this common infrastructure.
- The sections after that give the specifics of external and internal interrupts.
Interrupt Infrastructure
When an interrupt occurs, the CPU saves its most essential state on a stack and jumps to an interrupt handler routine.
- The 80x86 architecture supports 256 interrupts, numbered 0 through 255, each with an independent handler defined in an array called the interrupt descriptor table or IDT.
The Interrupt Handling Process
- In PintOS,
intr_init()
inthreads/interrupt.c
sets up the IDT so that each entry points to a unique entry point inthreads/intr-stubs.S
namedintrNN_stub()
, where NN is the interrupt number in hexadecimal. - Because the CPU doesn’t give us any other way to find out the interrupt number, this entry point pushes the interrupt number on the stack. Then it jumps to
intr_entry()
, which pushes all the registers that the processor didn’t already push for us, and then callsintr_handler()
, which brings us back into C inthreads/interrupt.c
. - The main job of
intr_handler()
is to call the function registered for handling the particular interrupt. (If no function is registered, it dumps some information to the console and panics.) It also does some extra processing for external interrupts* (see section External Interrupt Handling). - When
intr_handler()
returns, the assembly code inthreads/intr-stubs.S
restores all the CPU registers saved earlier and directs the CPU to return from the interrupt.
Types and Functions for Interrupt
Types and Functions for Interrupt
- Type:
void intr_handler_func (struct intr_frame *frame)
- This is how an interrupt handler function must be declared.
- Its frame argument (see below) allows it to determine the cause of the interrupt and the state of the thread that was interrupted.
- Type:
struct intr_frame
- The stack frame of an interrupt handler, as saved by the CPU, the interrupt stubs, and
intr_entry()
. Its most interesting members are described below. uint32_t edi
,uint32_t esi
,uint32_t ebp
,uint32_t esp_dummy
,uint32_t ebx
,uint32_t edx
,uint32_t ecx
,uint32_t eax
,uint16_t es
,uint16_t ds
- Register values in the interrupted thread, pushed by
intr_entry()
. Theesp_dummy
value isn’t actually used.
- Register values in the interrupted thread, pushed by
uint32_t vec_no
- The interrupt vector number, ranging from 0 to 255.
uint32_t error_code
- The “error code” pushed on the stack by the CPU for some internal interrupts.
void (*eip) (void)
- The address of the next instruction to be executed* by the interrupted thread.
void *esp
- The interrupted thread’s stack pointer.
- The stack frame of an interrupt handler, as saved by the CPU, the interrupt stubs, and
- Function:
const char *intr_name (uint8_t vec)
- Returns the name of the interrupt numbered vec, or
"unknown"
if the interrupt has no registered name.
- Returns the name of the interrupt numbered vec, or
Internal Interrupt Handling
Internal interrupts are caused directly by CPU instructions executed by the running kernel thread or user process. An internal interrupt is therefore said to arise in a “process context.”*
- In an internal interrupt’s handler, it can make sense to examine the
struct intr_frame
passed to the interrupt handler, or even to modify it. When the interrupt returns, modifications instruct intr_frame
become changes to the calling thread or process’s state. For example, the PintOS system call handler returns a value to the user program by modifying the saved EAX register. - There are no special restrictions on what an internal interrupt handler can or can’t do. Generally they should run with interrupts enabled*, just like other code, and so they can be preempted by other kernel threads. Thus, they do need to synchronize with other threads on shared data and other resources (see section Synchronization).
- Internal interrupt handlers can be invoked recursively.* For example, the system call handler might cause a page fault while attempting to read user memory. Deep recursion would risk overflowing the limited kernel stack (see section Struct thread), but should be unnecessary.
Types and Functions for Internal Interrupt Handling
Types and Functions for Internal Interrupt Handling
- Function:
void intr_register_int (uint8_t vec, int dpl, enum intr_level level, intr_handler_func *handler, const char *name)
- Registers
handler
to be called when internal interrupt numbered vec is triggered. Names the interruptname
for debugging purposes. - If
level
isINTR_ON
, external interrupts will be processed normally during the interrupt handler’s execution, which is normally desirable. - Specifying
INTR_OFF
will cause the CPU to disable external interrupts when it invokes the interrupt handler. The effect is slightly different from callingintr_disable()
inside the handler, because that leaves a window of one or more CPU instructions in which external interrupts are still enabled. This is important for the page fault handler; refer to the comments inuserprog/exception.c
for details. dpl
determines how the interrupt can be invoked.- If
dpl
is 0, then the interrupt can be invoked only by kernel threads. Otherwisedpl
should be 3, which allows user processes to invoke the interrupt with an explicit INT instruction. - The value of
dpl
doesn’t affect user processes’ ability to invoke the interrupt indirectly, e.g. an invalid memory reference will cause a page fault regardless ofdpl
.
- If
- Registers
External Interrupt Handling
External interrupts are caused by events outside the CPU. They are asynchronous, so they can be invoked at any time that interrupts have not been disabled. We say that an external interrupt runs in an “interrupt context”.
- In an external interrupt, the
struct intr_frame
passed to the handler is not very meaningful. It describes the state of the thread or process that was interrupted, but there is no way to predict which one that is. It is possible, although rarely useful, to examine it, but modifying it is a recipe for disaster. - Only one external interrupt may be processed at a time. Neither internal nor external interrupt may nest within an external interrupt handler. Thus, an external interrupt’s handler must run with interrupts disabled* (see section Disabling Interrupts).
- An external interrupt handler must not sleep or yield*, which rules out calling
lock_acquire()
,thread_yield()
, and many other functions. Sleeping in interrupt context would effectively put the interrupted thread to sleep, too, until the interrupt handler was again scheduled and returned. This would be unfair to the unlucky thread, and it would deadlock if the handler were waiting for the sleeping thread to, e.g., release a lock. - An external interrupt handler effectively monopolizes the machine and delays all other activities. Therefore, external interrupt handlers should complete as quickly as they can. Anything that require much CPU time should instead run in a kernel thread, possibly one that the interrupt triggers using a synchronization primitive.
- External interrupts are controlled by a pair of devices outside the CPU called programmable interrupt controllers, PICs for short.
- When
intr_init()
sets up the CPU’s IDT, it also initializes the PICs for interrupt handling. - The PICs also must be “acknowledged” at the end of processing for each external interrupt.
intr_handler()
takes care of that by callingpic_end_of_interrupt()
, which properly signals the PICs.
- When
Types and Functions for External Interrupt Handling
The following functions relate to external interrupts.
Types and Functions for External Interrupt Handling
- Function:
void* intr_register_ext (uint8_t vec, intr_handler_func *handler, const char *name)
- Registers handler to be called when external interrupt numbered vec is triggered. Names the interrupt name for debugging purposes.
- The handler will run with interrupts disabled.
- Function:
bool* intr_context (void)
- Returns true if we are running in an interrupt context, otherwise false.
-
Mainly used in functions that might sleep or that otherwise should not be called from interrupt context, in this form:
ASSERT (!intr_context ());
- Function:
void intr_yield_on_return (void)
- When called in an interrupt context, causes
thread_yield()
to be called just before the interrupt returns. - Used in the timer interrupt handler when a thread’s time slice expires, to cause a new thread to be scheduled.
- When called in an interrupt context, causes