An Introduction to Interrupts in an Operating System

Jack Walker
5 min readApr 29, 2023

--

a hand interrupting dominoes from continuing to fall in sequence

What are interrupts? What do they do? How do they work?

Before we can answer those questions, there’s some context we need to understand first. In a modern operating system, the CPU (central processing unit) is responsible for running tons of processes to keep the OS stable, keep user programs running, etc. These processes are basically a program in execution. They have a series of instructions that are executed by the CPU.

To better understand processes, think of a chef who is following a recipe to cook a meal: the chef is the CPU and the recipe is the process. The recipe tells the chef what ingredients to use, how to use and prepare them, and how to cook them to create the meal. Similarly, a process is a set of instructions that tell the CPU what to do. These instructions can include calculations, input/output operations, and even interactions with other processes. Just like a chef can (and often does) prepare multiple dishes at the same time, a CPU can run multiple processes simultaneously.

Process Scheduling

In a non-preemptive operating system (such as First-Come-First-Serve), each process is allowed to run to completion or until it voluntarily yields to another process, regardless of its execution time. This means that a process with a high execution time could monopolize the CPU and prevent other shorter processes from running during that time. This can result in poor system performance and slow response times.

In contrast, in a preemptive multithreading operating system (specifically in round-robin scheduling), each process is given a certain amount of time quantum to run before another process is allowed to run (aka yielded to). It’s important to note, though, that time quantum has nothing to do with quantum computing or quantum mechanics, it’s simply a slot of CPU time allotted for a process to run. This means that each process will run for n time quantum regardless of how important the process may be. This prevents processes from monopolizing the CPU and ensures each process has a fair chance to run, regardless of its execution time.

Here’s a graphic that shows the difference between the two:

a graphic that shows the difference between a non-preemptive and preemptive process scheduling algorithm

Preemptive process scheduling requires something to stop/interrupt the current process. More specifically, each process needs to be “interrupted”, or stopped, when its time quantum is up. Interrupts, more broadly, are signals that are sent from hardware or software to the CPU, indicating that an event has occurred requiring the CPU’s attention. There are many different things that cause interrupts aside from quantum time expiry, like user input, system events, etc.

In the case of time quantum expiry, the OS sets a timer to a specific value (n time quantum) and when that timer is up an interrupt signal is generated to tell the CPU that the current process’s time quantum is up. The CPU will then stop executing the current process, save the processes’ state (context), and (context) switch to the next process that’s ready to be executed for n time quantum.

ISRs, IRQs, and IDTs

Most interrupts have what’s called an Interrupt Service Routine (ISR), which handles what actions need to be taken for that specific interrupt. Each interrupt also has its own interrupt vector number that uniquely identifies it and allows a specific ISR to be run. The OS has a table of ISRs in a table called the Interrupt Descriptor Table (IDT) that ties each interrupt vector number to an ISR. So when an interrupt signal is generated and sent to the CPU with a vector number, the OS indexes into the IDT given the vector number and runs the ISR to handle the interrupt.

For example, in the case of keyboard input, the OS receives a keyboard interrupt (usually vector number 0x21), indexes into the IDT with the vector number, and runs the keyboard interrupt handler. This handler will read data from the keyboard controller to know what key was pressed. Here’s a graphic that explains the events that occur when a key is pressed on a keyboard:

But where are these interrupt signals generated from? Well, each modern computer has either a Programmable Interrupt Controller (PIC) for the whole CPU or a Local Advanced Programmable Interrupt Controller (LAPIC) for each core in a multi-core CPU. The PIC and LAPIC are responsible for managing and prioritizing different Interrupt Requests (IRQs) that are generated by software or hardware and sending a signal to the CPU to stop its current task and start processing the interrupt. These IRQ signals sent to the PIC or LAPIC typically include vector numbers (which identify what type of interrupt it is), the priority of the interrupt, and the source of the interrupt.

Very broadly this is the general sequence of events that occur for an interrupt:

  1. Some sort of event occurs in software or hardware which requires an interrupt
  2. An IRQ is generated at the source of the event and is sent to the PIC/LAPIC
  3. The PIC/LAPIC receives this IRQ and determines its priority and source
  4. The PIC/LAPIC sends an interrupt signal to the CPU to stop its current task and start processing the interrupt
  5. The CPU saves its current process’ context and calls the appropriate ISR from the IDT, found by the vector number
  6. The ISR is executed (with interrupts disabled) and handles the interrupt
  7. After the ISR finishes, interrupts are re-enabled and the CPU restores its saved state and returns to executing the original process

So interrupts really are wonderful things. They are an essential part of modern operating systems that allow the CPU to multitask and run multiple processes simultaneously. Overall, they enhance the efficiency of an operating system and improve user experience.

--

--