Lesson 34 of 48 intermediate

Interrupt Basics and the Interrupt Vector Table

Understand how the 8086 responds to events — the interrupt system that connects software to hardware

Open interactive version (quiz + challenge)

Real-world analogy

Interrupts are like a doorbell system in an apartment building. Each apartment has a numbered buzzer (interrupt type 0-255). When someone presses buzzer 33 (INT 21h), the intercom (CPU) looks up apartment 33 in the directory (Interrupt Vector Table) to find the address. It pauses whatever it was doing (saves state), goes to that apartment (ISR), handles the visitor, and then returns to resume what it was doing. The directory at the lobby entrance is always at the same place — address 0000:0000.

What is it?

An interrupt is a mechanism that causes the 8086 to suspend its current program, save the flags, CS, and IP on the stack, and jump to an Interrupt Service Routine (ISR) identified by a type number (0-255). The Interrupt Vector Table (IVT) occupies the first 1024 bytes of memory (0000:0000 to 0000:03FF) and contains 256 four-byte entries, each holding the CS:IP address of an ISR. IRET returns from the ISR by restoring IP, CS, and FLAGS.

Real-world relevance

The interrupt system is the backbone of all I/O in computing. Every keystroke generates a hardware interrupt. Every disk read completes via an interrupt. Your OS scheduler uses a timer interrupt to switch between processes. Modern x86 systems still use an evolved version of this same IVT concept (the IDT — Interrupt Descriptor Table). Understanding interrupts is essential for OS development, driver writing, and embedded systems.

Key points

Code example

; Program: Read and display the INT 21h vector from the IVT
; Then install a custom INT 60h handler and invoke it
.MODEL SMALL
.STACK 100h
.DATA
  msg1 DB 'INT 21h vector: $'
  msg2 DB 0Dh, 0Ah, 'Custom INT 60h called!$'
  msg3 DB 0Dh, 0Ah, 'Calling INT 60h...$'
  hexch DB '0123456789ABCDEF'

.CODE
MAIN PROC
  MOV AX, @DATA
  MOV DS, AX

  ; --- Read INT 21h vector from IVT ---
  MOV AH, 09h
  LEA DX, msg1
  INT 21h

  XOR AX, AX
  MOV ES, AX            ; ES = 0000 (IVT segment)
  MOV BX, 84h           ; INT 21h: type 21h * 4 = 84h

  MOV DX, ES:[BX+2]     ; segment of INT 21h handler
  CALL print_hex_word    ; display segment

  MOV AH, 02h
  MOV DL, ':'
  INT 21h               ; print colon separator

  MOV DX, ES:[BX]       ; offset of INT 21h handler
  CALL print_hex_word    ; display offset

  ; --- Install custom INT 60h handler ---
  CLI                    ; disable interrupts
  MOV WORD PTR ES:[180h], OFFSET my_int60
  MOV WORD PTR ES:[182h], CS
  STI                    ; re-enable

  ; --- Invoke our custom interrupt ---
  MOV AH, 09h
  LEA DX, msg3
  INT 21h

  INT 60h               ; calls our handler!

  MOV AH, 4Ch
  INT 21h
MAIN ENDP

; Custom ISR for INT 60h
my_int60 PROC FAR
  PUSH AX
  PUSH DX
  PUSH DS

  MOV AX, @DATA
  MOV DS, AX
  MOV AH, 09h
  LEA DX, msg2
  INT 21h

  POP DS
  POP DX
  POP AX
  IRET                   ; return from interrupt
my_int60 ENDP

; Print DX as 4-digit hex
print_hex_word PROC NEAR
  PUSH CX
  PUSH BX
  MOV CX, 4             ; 4 hex digits
  LEA BX, hexch
hex_loop:
  ROL DX, 4             ; rotate top nibble to bottom
  MOV AL, DL
  AND AL, 0Fh           ; isolate nibble
  XLAT                   ; AL = hexch[AL]
  PUSH DX
  MOV DL, AL
  MOV AH, 02h
  INT 21h
  POP DX
  LOOP hex_loop
  POP BX
  POP CX
  RET
print_hex_word ENDP
END MAIN

Line-by-line walkthrough

  1. 1. XOR AX, AX / MOV ES, AX — point ES to segment 0000h where the IVT resides
  2. 2. MOV BX, 84h — INT 21h is type 33 decimal (21h). Vector address = 33 * 4 = 132 = 84h
  3. 3. MOV DX, ES:[BX+2] — read the segment part of the INT 21h vector from IVT offset 86h
  4. 4. MOV DX, ES:[BX] — read the offset part of the INT 21h vector from IVT offset 84h
  5. 5. CLI — disable interrupts before modifying the IVT. Without this, a timer interrupt could fire while we have written only half the vector
  6. 6. MOV WORD PTR ES:[180h], OFFSET my_int60 — write our handler's offset into the INT 60h vector (60h * 4 = 180h)
  7. 7. MOV WORD PTR ES:[182h], CS — write the current code segment as the INT 60h handler's segment
  8. 8. STI — re-enable interrupts. The new vector is now complete and safe to use
  9. 9. INT 60h — triggers our custom interrupt. CPU pushes FLAGS/CS/IP, loads CS:IP from IVT[180h], and jumps to my_int60
  10. 10. IRET in my_int60 — pops IP, CS, and FLAGS from the stack, returning to the instruction after INT 60h

Spot the bug

; Install custom INT 60h handler
MOV ES, 0
MOV WORD PTR ES:[180h], OFFSET handler
MOV WORD PTR ES:[182h], CS
INT 60h

handler PROC FAR
  MOV AH, 09h
  LEA DX, msg
  INT 21h
  RET
handler ENDP
Need a hint?
Three issues: segment register loading, interrupt safety, and how the handler returns.
Show answer
Bug 1: MOV ES, 0 is illegal — cannot load immediate into segment register. Use XOR AX, AX / MOV ES, AX. Bug 2: CLI is missing before modifying IVT entries. Bug 3: The handler uses RET instead of IRET. Since INT pushes FLAGS, CS, and IP, a regular RET leaves FLAGS on the stack, corrupting the stack and crashing.

Explain like I'm 5

Imagine you are doing homework. The phone rings (interrupt). You stick a bookmark in your page (save IP), remember which subject you were doing (save CS), and note your mood (save FLAGS). You answer the phone (run the ISR). When the call ends, you pick up your bookmark, open the right textbook, and recall your mood — you continue exactly where you left off (IRET). The phone book that maps numbers to people is the Interrupt Vector Table.

Fun fact

The IVT design was simple but had a major flaw — any program could overwrite any vector, including critical ones like the timer or keyboard. A buggy program could accidentally write to address 0000:0000 and corrupt the divide-by-zero handler. This is one reason the 80286 introduced Protected Mode with a proper Interrupt Descriptor Table (IDT) that has access-level checks.

Hands-on challenge

Write a program that: (1) saves the current INT 1Ch (timer tick) vector, (2) installs your own handler that increments a counter variable on each tick, (3) waits for a keypress, (4) restores the original vector, and (5) displays how many timer ticks occurred while waiting. The timer fires approximately 18.2 times per second.

More resources

Open interactive version (quiz + challenge) ← Back to course: Microprocessor A–Z