Interrupts: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
Line 42: Line 42:


==From the OS's perspective==
==From the OS's perspective==
When an interrupt comes in, the IDT (which is setup by the OS in advance) is used to jump to code portion of the OS, which handles the interrupt (and therefore called the "interrupt handler" or "[[Interrupt Service Routines]]"). Usually the code interacts with the device, then returns to whatever it was doing previously with an <tt>iret</tt> instruction (which tells the CPU to load the state information it saved, from the stack). Before the <tt>ret</tt>, this code is executed, to tell the PIC that it's OK to send any new or pending interrupts, because the current one is done. The PIC doesn't send any more interrupts until the cpu acknowledges the interrupt:
When an interrupt comes in, the [[IDT]] (which is setup by the OS in advance) is used to jump to code portion of the OS, which handles the interrupt (and therefore called the "interrupt handler" or "[[Interrupt Service Routines]]"). Usually the code interacts with the device, then returns to whatever it was doing previously with an <tt>iret</tt> instruction (which tells the CPU to load the state information it saved, from the stack). Before the <tt>ret</tt>, this code is executed, to tell the PIC that it's OK to send any new or pending interrupts, because the current one is done. The PIC doesn't send any more interrupts until the cpu acknowledges the interrupt:


<pre>
<pre>

Revision as of 00:49, 6 June 2012

Interrupts in a nutshell

An interrupt is a message from a device, such as the keyboard, to the CPU, telling it to immediately stop whatever it is currently doing and do something else. For example, the keyboard controller sends an interrupt when a key is pressed. To know what to do when a specific interrupt arise, the CPU has a table called the IDT which is setup by the OS, and stored in memory. There are 256 interrupts, numbered from 0 to 255, but only 16 are used by devices. These are called IRQs (Interrupt ReQuest) or hardware interrupts. The 16 IRQs are numbered from 0 to 15. Applications can call interrupts with the INT instruction, such as:

int 80h

Non-IRQ interrupts are often used for APIs, or sets of functions that can be called by applications. The Unix API is 0x80, or 80h and the DOS API is 0x21, or 21h. These are called software interrupts. Note that the first 32 (0-31) interrupts are reserved and used by the CPU, as exceptions, so don't use those for APIs or IRQs.

From the keyboard's perspective

Basically, when a key is pressed, the keyboard controller tells a device called the Programmable Interrupt Controller, or PIC, to cause an interrupt. Because of the wiring of keyboard and PIC, IRQ #1 is the keyboard interrupt, so when a key is pressed, IRQ 1 is sent to the PIC. The role of the PIC will be to decide whether the CPU should be immediately notified of that IRQ or not and to translate the IRQ number into an interrupt vector (i.e. a number between 0 and 255) for the CPU's table.

The OS is supposed to handle the interrupt by talking to the keyboard, via in and out instructions (or inportb/outportb, inportw/outportw, and inportd/outportd in C, see Inline Assembly/Examples), asking what key was pressed, doing something about it (such as displaying the key on the screen, and notifying the current application that a key has been pressed), and returning to whatever code was executing when the interrupt came in.


From the PIC's perspective

There are actually two PICs on most systems, and each handles 8 different interrupts. Actually, IRQ 2 and IRQ 9 (handled by the master and slave PICs, respectively) are connected, so whenever IRQ 2 occurs, IRQ 9 also occurs (this can be changed, but most devices expect IRQ 2 to be reserved for this purpose, and therefore do not use it). A device sends the PIC an interrupt, and the PIC tells the CPU which interrupt number (between 00h and FFh, or 0 and 255 decimal) is to be serviced. When the system first starts up, the IRQs 0-7 are set to interrupts 08h-0Fh. IRQs 8-F are set to interrupts 70h-77h. Therefore, if IRQ 6 is sent to the PIC by a device, the PIC would tell the CPU to service INT 0Eh, which presumably has code for interacting with whatever device sent the interrupt in the first place. Of course, there can be trouble when two or more devices share an IRQ; if you wonder how this works, check out Plug and Play. Note that interrupts are handled by priority level: 0, 1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 3, 4, 5, 6, 7. So, if IRQ 8 and IRQ 3 come in simultaneously, IRQ 8 is sent to the CPU. When the CPU finishes handling the interrupt, it tells the PIC that it's OK to resume sending interrupts:

mov al,20h
out 20h,al

or if the interrupt came from the slave PIC:

mov al, 20h
out A0h, al
out 20h, al

and the PIC sends the interrupt assigned to IRQ 3, which the CPU handles (using the IDT to look up the handler for that interrupt).

Alert readers will notice that the CPU has reserved interrupts 0-31, yet IRQs 0-7 are set to interrupts 08-0Fh. Now the reserved interrupts are called when, for example, a dreadful error has occurred that the OS must handle. Now when the computer first starts up, most errors of this type won't occur. However, when you enter protected mode (and every OS should use protected mode, real mode is obsolete), these errors may occur at any time, and the OS needs to be able to handle them. How's the OS going to tell the difference between INT 9, Exception: Coprocessor segment overrun, and INT 9: IRQ 1? Well, it can ask the device whether there is really an interrupt for that device. But this is slow, and hackish, and not all devices are able to do this type of thing. The best way to do it is to tell the PIC to map the IRQs to different interrupts, such as INT 78h-7Fh. For information on this, see the PIC FAQ. Note that IRQs can only be mapped to INTs that are multiples of 08h: 00h-07h, 08h-0Fh, 10h-17h, 17h-1Fh. And you probably want to use 20h-27h, or greater, since 00h-1Fh are reserved by the CPU. Also, each PIC has to be programmed separately. You can tell the Master PIC to map IRQs 0-7 to INTs 20h-27h, but IRQs 8-F will still be INTs 70h-77h, unless you tell the Slave PIC to put them elsewhere as well.

See programming the PIC chips for detailed information.


From the CPU's perspective

Every time the CPU is done with one machine instruction, it will check if the PIC's pin has notified an interrupt. If that's the case, it stores some state information on the stack (so that it can return to whatever it is doing currently, when the INT is done being serviced by the OS) and jumps to a location pointed to by the IDT. The OS takes over from there. The current program can, however, prevent the CPU from being disturbed by interrupts by the mean of the interrupt flag (IF in status register). As long as this flag is cleared, the CPU ignores the PIC's requests and continues running the current program. Assembly instructions cli and sti can control that flag.


From the OS's perspective

When an interrupt comes in, the IDT (which is setup by the OS in advance) is used to jump to code portion of the OS, which handles the interrupt (and therefore called the "interrupt handler" or "Interrupt Service Routines"). Usually the code interacts with the device, then returns to whatever it was doing previously with an iret instruction (which tells the CPU to load the state information it saved, from the stack). Before the ret, this code is executed, to tell the PIC that it's OK to send any new or pending interrupts, because the current one is done. The PIC doesn't send any more interrupts until the cpu acknowledges the interrupt:

mov al,20h
out 20h,al

In the case of the keyboard input, the interrupt handler asks the keyboard what key was pressed, does something with the information, then acknowledges and return:

push eax    ;; make sure you don't damage current state
in al,60h   ;; read information from the keyboard

mov al,20h
out 20h,al  ;; acknowledge the interrupt to the PIC
pop eax     ;; restore state
iret        ;; return to code executed before.

Whatever the CPU was previously doing is then resumed (unless another INT was received by the PIC while servicing this one, in which case the PIC tells the CPU about it and a new interrupt handler is executed, once the CPU saves state information on the stack again).

So how do I program this stuff?

Step by step, now that you've grabbed the whole thing and know what's to be done:

  • Make space for the interrupt descriptor table
  • Tell the CPU where that space is (see GDT Tutorial: lidt works the very same way as lgdt)
  • Tell the PIC that you no longer want to use the BIOS defaults (see Programming the PIC chips)
  • Write a couple of ISR handlers (see Interrupt Service Routines) for both IRQs and exceptions
  • Put the addresses of the ISR handlers in the appropriate descriptors
  • Enable all supported interrupts in the IRQ mask (of the PIC)


Magic Numbers

Interrupt Requests

IRQ Description
0 Programmable Interrupt Timer Interrupt
1 Keyboard Interrupt
2 Cascade (used internally by the two PICs. never raised)
3 COM2 (if enabled)
4 COM1 (if enabled)
5 LPT2 (if enabled)
6 Floppy Disk
7 LPT1 / Unreliable "spurious" interrupt (usually)
8 CMOS real-time clock (if enabled)
9 Free for peripherals / legacy SCSI / NIC
10 Free for peripherals / SCSI / NIC
11 Free for peripherals / SCSI / NIC
12 PS2 Mouse
13 FPU / Coprocessor / Inter-processor
14 Primary ATA Hard Disk
15 Secondary ATA Hard Disk

Interrupts

Int Description
0-31 Protected Mode Exceptions (Reserved by Intel)
8-15 Default mapping of IRQ0-7 by the BIOS at bootstrap
70h-78h Default mapping of IRQ8-15 by the BIOS at bootstrap

Ports

Port Description
20h & 21h control/mask ports of the master PIC
A0h & A1h control/mask ports of the slave PIC
60h data port from the keyboard controller
64h command port for keyboard controller - use to enable/disable kbd interrupts, etc.

See Also

Articles

Threads

External Links