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
Internal Interrupt Handling
Internal interrupts are caused directly by CPU instructions executed by the running kernel thread or user process (from project 2 onward). 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
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.
Types and Functions
The following functions relate to external interrupts.
Last updated