Demilade Sonuga's blog

All posts

A Few Things About Interrupts

2023-01-17

Interrupts are signals sent to the processor telling it to immediately attend to something else. To understand x86's interrupt system, there are three main players we look at:

  1. The interrupt sources
  2. The programmable interrupt controller
  3. The Interrupt Descriptor Table

The Interrupt Sources

3 things generate interrupts on x86 systems:

  1. The processor
  2. Hardware devices
  3. Software

Interrupts generated by the processor itself are exceptions. These interrupts happen when an event considered an error occurs. For example, division-by-zero exceptions are raised when the processor attempts to divide a number by zero.

Interrupts generated by hardware devices, like the keyboard, are hardware interrupts. These interrupts happen when a device needs attention or when some event occurs, like a key press or a movement of the mouse.

The last interrupt source is software and interrupts generated by software are software interrupts. Interrupt signals can be generated by software. This is mainly done just to transfer control from the software to the OS.

Okay, so these are where the interrupt signals come from. After interrupts are generated, they are sent to the processor. Interrupts generated by the processor itself or by software are immediately handled by the processor because they are already in the processor. As for the hardware interrupts, the signals first have to get to the processor before anything else happens. How the interrupts are sent to the processor is what leads us to the next player: the programmable interrupt controller.

The Programmable Interrupt Controller (PIC)

This is the device that stands in between the processor and the hardware devices. When hardware interrupts are generated, this device receives the interrupts from the hardware devices and notifies the processor. There are two main PICs in the x86 system: the 8259 PIC and the APIC. The 8259 PIC is old and the APIC has replaced it but it's much easier to set up and lots of systems support it for backward compatibility, so we'll use the 8259 PIC instead.

Hardware devices have interrupt lines that they use to send their interrupt signals. For example, when a key on a keyboard is pressed, the keyboard generates an interrupt and sends it along its interrupt line. The interrupt lines of the hardware devices are connected in a predetermined way to the PIC.

A single PIC has 8 lines for getting signals from external hardware devices. A single PIC looks something like this:

         --------
Line 0 --|      |
Line 1 --|      |    -------
Line 2 --|      |    |     |
Line 3 --|  PIC |----| CPU |
Line 4 --|      |    |     |
Line 5 --|      |    -------
Line 6 --|      |
Line 7 --|      |
         --------

These lines are then connected to external hardware devices for receiving interrupts.

The x86 systems we're programming for actually have 2 PICs, one connected directly to the processor and the other connected to the first PIC. These 2 PICs act as one to the processor. This works because the second PIC is connected to a line on the first. The PICs we'll be dealing with look like this:

         ----------                ---------
Line 0 --|        |       Line 0 --|       |
Line 1 --|        |       Line 1 --|       |        -------
Line 2 --| Second |----------------| First |        |     |
Line 3 --|  PIC   |       Line 3 --| PIC   |--------| CPU |
Line 4 --|        |       Line 4 --|       |        |     |
Line 5 --|        |       Line 5 --|       |        -------
Line 6 --|        |       Line 6 --|       |
Line 7 --|        |       Line 7 --|       |
         ----------                ---------

As indicated above, the first PIC's line 2 is used to cascade the second's signals to the processor.

The job of the PIC is to receive an interrupt and translate it into an interrupt vector number. This interrupt vector number is a number in the range 0..=255 that the processor uses to determine what should be done when it receives the interrupt.

The lines the hardware devices are connected to are predetermined, as shown in this table:

PICLineVector NumberDevice
First00Timer
11Keyboard
33Serial Port 2
44Serial Port 1
55Parallel Port 2/3
66Floppy Disk
77Parallel Port 1
Second08Real Time Clock
19ACPI
210Any
311Any
412Mouse
513Co-Processor
614Primary ATA
715Secondary ATA

Lines 2 and 3 on the second PIC can be configured to receive interrupts from any device.

The interrupt vector numbers associated with a PIC's lines can also be changed through remapping. This is something we'll eventually have to do, so we'll get to it later.

Anyways, after the PIC receives an interrupt and sends the interrupt vector number to the processor, the processor has to use that number to determine what should be done next. The means by which the processor does this is the Interrupt Descriptor Table.

The Interrupt Descriptor Table (IDT)

This is a structure in memory that the processor uses to determine what should be done when it receives an interrupt vector number. It is essentially an array of 256 function pointers. The index in this array is the interrupt vector number. This is why these numbers can only be in the range 0..=255 because there are only 256 entries.

The functions in the IDT are called interrupt service routines. It is one of these routines that the CPU immediately starts executing when it receives an interrupt.

The x86 architecture dictates that the first 32 entries, that is entries 0..=31, in the IDT are for exceptions. Exceptions have their own dedicated interrupt vector numbers. For example, the division by zero error has a vector number of 0. So, if the processor ever encounters a division by zero situation, the generated interrupt will result in an execution of the function at entry 0 of the IDT.

The remaining 256 - 32 entries are free for use for anything else.

If you take a good look at this setup, you'll see that there is already something wrong. If the entries 0..=31 are dedicated to exceptions, then whenever a hardware device generates an interrupt, the PIC will send an interrupt vector number in the range 0..=15 to the CPU, which will result in the execution of one of those exception functions. It's because of this problem that we'll eventually have to change the vector numbers of the PICs, that is, remap the PICs.

A Clear Picture

From this brief description, I believe a somewhat clear picture of how this interrupts stuff works should be forming. To see how everything fits together, let's take a look at a sequence followed when a key on the keyboard is pressed.

  1. A key is pressed.
  2. A signal is sent along the keyboard's interrupt line to the PIC.
  3. The PIC receives the interrupt and, according to its configuration, determines an interrupt vector number to send to the CPU and sends it.
  4. The CPU stops whatever it's doing, receives the interrupt vector number and uses it to index into the IDT.
  5. The function gotten from the table is executed.
  6. The CPU continues doing whatever it was doing before.

The description I've given here is a very simplified one, but it's good enough for our purposes. For more detailed descriptions, you can take a look at the references.

Take Away

  • Interrupts are generated by the processor, software and hardware devices.
  • Programmable Interrupt Controllers are devices that receive interrupts from hardware devices, like keyboards, and give the CPU an interrupt vector number.
  • The CPU uses interrupt vector numbers to index into the Interrupt Descriptor Table.
  • The Interrupt Descriptor Table is an array of 256 functions that the processor uses to determine what to do upon an interrupt.

In The Next Post

We'll start getting interrupts into our code.

References

  • https://wiki.osdev.org/IRQ
  • https://wiki.osdev.org/8259_PIC
  • https://wiki.osdev.org/Exceptions
  • https://wiki.osdev.org/Interrupt_Descriptor_Table
  • https://wiki.osdev.org/Interrupt_Service_Routines
  • https://os.phil-opp.com/hardware-interrupts/