What you need to know about interrupts

If you are from my generation or older, you remember the wonderful days of manually configuring hardware by using jumpers and then connecting the hardware to the ISA bus on a computer.

Before I got my first PC, I owned a more user friendly system(an Atari 65XE), so new hardware was magically configured. Well the few things that you could add to that setup, the options were very limited at that time.

Nowadays it is seldom that one buys a piece of hardware that needs to be physically installed in the motherboard. Most hardware uses the USB bus or another similar technology.

What is an interrupt and why do I care?

Let’s think about a simple thing like typing on your keyboard. Every time you press a key the keyboard will produce three events:

  • a key pressed event
  • the key
  • a key release event

How does the CPU find out about these events? One simple answer is to have a poll loop, in which the CPU waits for some time and checks the data lines from the keyboard. If no data has arrived, then the CPU waits and checks again.

It is easy to see that this mode of interaction is not very effective. It might work in very simple systems where the CPU has literally nothing better to do than wait for that input, but it does not scale to a fully multitasking OS.

In order to solve this issue, a CPU has special input lines that are either high or low. When a device wants to communicate with the CPU, then the device raises its input line, which causes the CPU to interrupt its normal flow and call a special routine to take care of the device. It is worth mentioning that a CPU has only a few external interrupt lines, most interrupts come from devices inside the CPU.

Cool, tell me more!

During the bootstrap of a CPU, we need to configure the different control registers, for example the ones that configure the address, data and chip selects for the different devices, and at the same time we need to create what is called an interrupt table.

An interrupt table is nothing fancier than an array of addresses of routines that need to be called when the corresponding interrupt is raised.

In my previous post I talked about the hardware of a serial port and we will continue with that example in this post. As explained a serial port is a device that can transfer bytes serially bit by bit. Typically a serial port will raise an interrupt when new data has arrived and is ready to be collected or when data has been sent and the device is ready to send a new data.

Configuring interrupts

Configuring interrupts in many CPUs is a privileged operation that can only be performed during bootstrap. Some CPUs will allow to remap interrupts after bootstrap, but usually only some of them.

A typical technique used by an OS is to install a default interrupt handler for all interrupts and then have its own internal table to call the corresponding handler. This way, the handler can be configured at run time without any restrictions.

To complicate things, devices might share an interrupt. Think of devices that are attached to a data bus which transmit data to the CPU through the bus. The bus usually has one interrupt that is raised when the bus needs to communicate with the CPU. In this case the bus will raise the interrupt and the CPU will read the source of the interrupt from some special register in the bus master.

Top and bottom halves

From the point of view of the CPU, an interrupt is very expensive. The CPU has to stop processing what it was processing and call the corresponding handler. The handler will run in a special mode in which other interrupts are not allowed (usually with some exceptions for what is called the unmaskable interrupts).

Because of this, interrupt handlers are divided into two parts: the top half and the bottom half.

The top half is the code that gets called when the interrupt is raised. This code needs to be as quick as possible and have no side effects. The aim of this code is to deal with whatever is urgent and then schedule the lower half to take care of the rest.

In the case of our serial port, the top half will first check the status register from the UART and based on that it will for example copy the received byte to memory. This way the device is ready to keep receiving data. The memory should be allocated before hand, the interrupt handler cannot allocate memory.

The lower half of the interrupt handler might be scheduled long after the top half was run. The aim of the lower half is to take care of any work that is not urgent. For example in the case of the serial port, the lower half can go through the data that has been received and check which process is waiting for data. It copies the data to the process space and then awakes the process.

What’s next?

In my next post I will change gears again and I will talk about data stream processing using Kafka.

Published by carlosware

Busy dad of three with a passion for fly fishing and computers.

Leave a comment