Much of the functionality embedded inside a personal computer is implemented by hardware devices other than the processor. For example, serial communication is handled by an Intel 8251A chip (or a 16550AF in newer computers), which is located on the computer's motherboard (usually inside an ASIC) while the keyboard is controlled by a 8042A microprocessor.
Since each device operates at its own pace, a method is needed for synchronizing the operation of the processor with these devices. One solution is for the processor to sit in a tight loop, asking each device about its current state (a method known as polling). When data is available in one of the devices, the processor can then read and process the incoming bytes. While this method works, it has many disadvantages. First, it is very wasteful in terms of processing power, since the processor is constantly busy reading the status of the attached devices instead of executing some useful code. And second, when the rate of data transfer is extremely high, the processor might lose data bytes arriving from the hardware devices.
So, a different approach was devised. Instead of polling hardware devices to wait for their response, each device is responsible for notifying the processor about its current state. When a hardware device needs the processor's attention, it simply sends an electrical signal (hardware interrupt) through a dedicated pin in the interrupt controller chip (located on the computer's motherboard).
The interrupt controller serves as an intermediate between the hardware devices and the processor. Its responsibility is to alert the processor when one of the hardware devices needs its immediate attention. In this case, the processor stops its current activity and jumps to execute a function (interrupt handler) which was previously associated with the calling device (or more accurately, associated with the interrupt vector of the device).
While hardware interrupts have a significant importance, software interrupts also play a major role in the normal operation of the PC. These interrupts are synchronous rather than asynchronous, since they are generated by the processor itself when it encounters an INT instruction inside the code stream. Software interrupts are usually used by the operating system to publish its internal functions, thereby allowing applications to take advantage of these services.
Exceptions belong to a special type of software interrupts. They are generated by the processor itself whenever some unexpected critical event occurs. For instance, a page fault exception (interrupt 14) is triggered when the processor attempts to access a page, which is marked as not-present. The exception handler can then reload the page from disk (virtual memory) and restart the instruction which generated the exception.
Three types of exceptions can be generated by the processor: faults, traps and aborts. When a fault exception occurs, the CS and (E)IP registers which are pushed on the stack, point to the address of the instruction, which generated the exception. This gives the exception handler a chance to fix the condition which caused the exception to occur, before restarting the faulting instruction. Traps are similar to interrupts in the sense that they make the processor push the address of the next instruction to the stack, while aborts neglect to specify the location of the faulting instruction, since they are usually used to indicate severe errors (such as hardware errors or illegal system tables) which are not recoverable.
Real-ModeIf you've ever programmed for DOS, you probably know that the INT 21h interface has a salient importance in linking applications with operating system services. For instance, calling an operating system function for opening a file, simply requires issuing an INT 21h interrupt with the correct parameters set in the processor registers.
The BIOS (Basic Input Output System) uses a similar software communication mechanism. The INT 10h, 13h and 16h are all interfaces to internal BIOS functions, which control the screen, the disk controller and the keyboard. Instead of accessing hardware devices directly, DOS uses the BIOS services to control the operation of the system. Modern operating systems such as Windows NT drop their reliance upon the BIOS in favor of faster mechanisms (device drivers) for accessing the PC's hardware.
When the processor bumps into an INT instruction, it pushes the address of the next instruction (CS and IP registers) and the contents of its flags register to the stack and then jumps to execute the interrupt handler. This ensures that when the handler code ends, the processor returns automatically to the original code stream (the code which was executing before the interrupt occured).
Here are the precise steps taken by the processor when it encounters an INT instruction:
Many applications (especially TSRs - Terminate and Stay Resident programs) hook software interrupts to supply additional services on top of those offered by DOS and the BIOS. One good example is Netware 3.1, which hooks INT 21h in order to add support for network file operations (such as reading a file located on a remote computer). Other pieces of code (mostly device drivers) hook hardware interrupts to monitor various devices and ensure their proper operation.
To hook an interrupt, a program need only to replace a specific interrupt table entry with the address of its own interrupt handler. Whenever this interrupt occurs, the handler is automatically invoked by the processor. A good programming practice is to place a call to the previous handler inside the interrupt handling function. This ensures that all previously installed hooks will get a chance to handle the incoming interrupt.
Note that when loading an application which uses software interrupts to call operating system services, there is no need to patch the application code with the actual memory addresses of the OS services. Therefore, the operating system can freely relocate its services in memory by simply updating the corresponding interrupt table entries with the new addresses.
Hardware InterruptsHardware interrupts are not very different in behavior. But still, when considering the path traveled by a hardware interrupt from the instant it leaves the hardware device until it reaches the processor, you must take into account the actions taken by the interrupt controller (or the 8259A if you prefer).
When the processor executes an INT instruction, it retrieves the interrupt code from the opcode itself. This code is later used by the processor to index the IVT (interrupt vector table) and find the address of the interrupt handler. On the other hand, when a hardware interrupt is detected, the interrupt controller sends the interrupt code to the processor via the data bus. When the code is finally acquired by the processor (either from the INT opcode or from the interrupt controller) the steps taken are equivalent to those discussed earlier.
Table 1 presents the interrupt vectors occupied by the interrupt controller chips under different operating systems:
OS | Vectors occupied by the master 8259A | Vectors occupied by the slave 8259A |
---|---|---|
|
|
|
|
|
|
|
|
|
When executing in real-mode, the following exceptions are supported:
Exception | Vector | Condition |
---|---|---|
|
|
Attempting to execute a DIV or an IDIV instruction with a divisor which equals zero. |
|
|
Used in conjunction with the debug registers to indicate a breakpoint hit. The processor also issues this interrupt after executing every instruction when the TRAP flag is set. |
|
|
The INT 3 instruction generates this exception. Since the opcode of this instruction is only one byte long, it is often used by debuggers to set a breakpoint in application code. The debugger needs only to replace the first byte of the instruction with the INT 3 opcode (11001100b) and wait for the breakpoint exception to occur. After the exception handler resumes execution and before returning to the debugged application, the debugger sets the INT 3 opcode to the previously overwritten byte and issues an IRET (interrupt return) instruction. |
|
|
When performing arithmetic instructions with signed operands, the processor set the OF flag to indicate an overflow. The INTO instruction tests this flag and if it is set - generates an exception. |
|
|
The BOUND instruction is used to verify that an array index does not exceed a certain limit. If the limit is exceeded, an exception is generated. |
|
|
Occurs when the processor executes one of the reserved opcodes or uses the LOCK prefix improperly. Can also indicate an invalid operand following an opcode. |
|
|
Attempting to execute a floating-point instruction when there is no coprocessor installed. The EM bit of the CR0 register can be cleared to disable this exception. |
|
|
An interrupt occurs which has no corresponding entry in the IVT or a second exception is generated while the processor is executing a previously activated exception handler. |
|
|
Stack operation exceeds offset FFFFh or a selector pointing to a non-present segment is loaded into SS. |
|
|
Word memory access at offset FFFFh or an attempt to execute past the end of the code segment. |
|
|
An error with the numeric coprocessor (Divide-by-Zero, Underflow, Overflow...). |
Other exceptions exist only in Virtual 8086 Mode:
Exception | Vector | Condition |
---|---|---|
|
|
Indicates that one of the checks made during a task switch failed. |
|
|
Loading one of the segment registers with a selector to a segment marked as not-present. |
|
|
Accessing a supervisor page from user privileged code or attempting to access a page which is marked as not-present. |
|
|
Memory access to an unaligned memory location (can only occur while executing ring 3 code). |
Handling interrupts in protected-mode requires setting up a table in memory known as the IDT (Interrupt Descriptor Table). The IDT is somewhat different from the interrupt vector table that exists in real-mode. Instead of being "stuck" at physical address 0, the protected-mode IDT can float around in the linear address space with absolute freedom (although it is possible to change the address of the IVT while in real-mode, it is incompatible with the implementation of the 8086 processor). The linear address of the IDT is determined by a value set into the IDTR register using the LIDT instruction. Each entry in the IDT is 8 bytes long (as opposed to the 4 bytes entries of the IVT) and can contain one of three gate descriptors:
The following figure presents the internal structure of a trap gate entry:
Figure 1 - Structure of a trap gate entry
Trap and interrupt gates have much in common since they are both used for control transfers between different privilege levels. The selector and offset fields specified in these gate descriptors are used to locate and execute the interrupt handler code. A control transfer is only permitted to a code residing in the same or a higher privilege level but never to a lower privileged one.
Software InterruptsSoftware interrupts are still widely used by protected-mode operating systems in order to publish their internal services to the external world. However, most of these interfaces are now wrapped by functions and cannot be accessed directly (although it's possible, it's not recommended). For instance, Windows NT uses the undocumented interrupt 2Eh to allow control transfers between its user mode and kernel mode components. Whenever a non-privileged Windows application issues a call to an API function, the system routes the call until it reaches an INT 2Eh instruction. The IDT gate corresponding to this interrupt points to a handler inside ntoskrnl, which performs the required privileged operations on behalf of the application. Windows 95 and 98 use a similar technique to hop between user and kernel mode, but rely upon INT 30h instead of using INT 2Eh.
Hardware InterruptsHardware interrupts in protected-mode behave in the same manner as described in the real-mode section. The only major difference is that when executing in protected-mode, the processor consults its IDT rather than searching the real-mode IVT for the address of the interrupt handler.
ExceptionsMost of the information which was presented in the previous sections, applies to protected-mode as well. However, exception 13 (General Protection Fault) has a wider meaning in protected-mode and can indicate additional conditions than those defined for real-mode.
That's it. More information about the implementation of interrupts and exceptions in protected-mode can be obtained from the "Pentium Processor User's Manual" available from Intel.
Copyright © 1997, 1998 Yariv Kaplan
yariv@internals.com