Interrupts

View slides

Lecture objectives

  • Interrupts and exceptions (x86)
  • Interrupts and exceptions (Linux)
  • Deferrable work
  • Timers

What is an interrupt?

An interrupt is an event that alters the normal execution flow of a program and can be generated by hardware devices or even by the CPU itself.

Interrupts can be grouped into two categories based on the source of the interrupt:

  • synchronous, generated by executing an instruction
  • asynchronous, generated by an external event
  • maskable
    • can be ignored
    • signalled via INT pin
  • non-maskable
    • cannot be ignored
    • signalled via NMI pin

Synchronous interrupts, usually named exceptions, handle conditions detected by the processor itself in the course of executing an instruction. Divide by zero or a system call are examples of exceptions.

Asynchronous interrupts, usually named interrupts, are external events generated by I/O devices. For example a network card generates an interrupts to signal that a packet has arrived.

Exceptions

There are two sources for exceptions:

  • processor detected
    • faults
    • traps
    • aborts
  • programmed
    • int n

Processor detected exceptions are raised when an abornmal condition is detected while executing an instruction.

A fault is a type of exception that is reported before the execution of the instruction and can be usually corrected. The saved EIP is the address of the instruction that caused the fault, so after the fault is corrected the program can re-execute the faulty instruction. (e.g page fault).

A trap is a type of exception that is reported after the execution of the instruction in which the exception was detected. The saved EIP is the address of the instruction after the instuction that caused the trap. (e.g debug trap).

Hardware

Programmable Interrupt Controller

../_images/ditaa-f2e7fcf366f75599766f77babb74ffebbc7f79e3.png

A device supporting interrupts has an output pin used for signalling an Interrupt ReQuest. IRQ pins are connected to a device named Programmable Interrupt Controller (PIC) which is connected to CPU’s INTR pin.

A PIC usually has a set of ports used to exchange information with the CPU. When a device connected to one of the PIC’s IRQ lines needs CPU attention the following flow happens:

  • device raises an interrupt on the corresponding IRQn pin
  • PIC converts the IRQ into a vector number and writes it to a port for CPU to read
  • PIC raises an interrupt on CPU INTR pin
  • PIC waits for CPU to acknowledge an interrupt
  • CPU handles the interrupt

Will see later how the CPU handles the interrupt. Important to notice is that by design PIC won’t raise another interrupt until the CPU acknowledged the current interrupt.

Each IRQ line can be individually disabled. This allows simplifying design by making sure that interrupt handlers are always executed serially.

Advanced Programmable Interrupt Controller

../_images/ditaa-f0124fcd03ddd32134f414c160e247355ab306bc.png

With multicore systems, each core has a local APIC used to process interrupts from locally connected devices like timers or thermals sensors.

I/O APIC is used to distribute IRQ from external devices to CPU cores.

After discussing the hardware, now let’s see how the processor handles an interrupt.

  • at the PIC level
    • programming the PIC
    • PIC can be programmed to disable a given IRQ line
  • at the CPU level
  • cli (CLear Interrupt flag)
  • sti (SeT Interrupt flag)

Interrupt Descriptor Table

The interrupt descriptor table (IDT) associates each interrupt or exception identifier with a descriptor for the instructions that service the associated event. We will name the identifier as vector number and the associated instructions as interrupt/exception handler.

An IDT has the following characteristics:

  • it is used as a jump table by the CPU when a given vector is triggered
  • it is an array of 256 x 8 bytes entries
  • may reside anywhere in physical memory
  • processor locates IDT by the means of IDTR

Below we can find Linux IRQ vector layout. The first 32 entries are reserved for exceptions, vector 128 is used for sycall interface and the rest are used mostly for hardware interrupts handlers.

../_images/ditaa-dc1532523c503b0c569468d4ed1d7aeb11ed0ed0.png

On x86 an IDT entry has 8 bytes and it is named gate. There can be 3 types of gates:

  • interrupt gate, holds the address of an interupt or exception handler. Jumping to the handler disables maskable interrupts (IF flag is cleared).
  • trap gates, similar with an interrupt gate but it does not disable maskable interrupts while jumping to interupt/exception handler.
  • task gates (not used in Linux)

Lets have a look at several fields of an IDT entry:

  • segment selector, index into GDT/LDT to find the start of the code segment where the interupt handlers resides
  • offset, offset inside the code segment
  • T, represents the type of gate
  • DPL, minimum privilege required for using the segments content.
../_images/ditaa-a9d35ab5597ae26c7755a800c13ff62d6b506ebd.png

Interrupt handler address

In order to find the interrupt handler address we first need to find the start address of the code segment where interrupt handler resides. For this we use the segment selector to index into GDT/LDT where we can find the corresponding segment descriptor. This will provide the start address kept in the ‘base’ field. Using base address and the offset we can now go at the start of the interrupt handler.

../_images/ditaa-9b2dd926bf730364e2ca37aeea66fea341a741bf.png

Stack of interrupt handler

Similar with control transfer to a normal function, a control transfer to an interrupt or exception handler uses the stack to store the information needed for returning to the interrupted code.

As can be seen in the figure below, an interrupt pushes the EFLAGS register before saving the address of the interrupted instruction. Certain types of exceptions also cause an error code to be pushed on the stack to help debug the exception.

../_images/ditaa-1e5c09a5a3c85a247e3e154c2ab664ba5ebc134c.png

Interrupt handler execution

  • check current privilege level

  • if need to change privilege level

    • change stack with the one associated with new privilege
    • save old stack information on the new stack
  • save EFLAGS, CS, EIP on stack

  • save error code on stack in case of an abort

  • execute interrupt handler

Returning from an interrupt

IRET is used to from an interrupt handler. IRET is similar with RET except that IRET increments ESP by extra four bytes (because of the flags on stack) and moves the saved flags into EFLAGS register.

  • pop the eror code (in case of an abort)
  • use IRET
    • pops CS, EIP, EFLAGS
    • if privilege level changed returns to the old stack and old privilege level

Nested interrupts and exceptions

An interrupt handler may preempt both other interrupt handlers and exception handlers. On the other hand, an exception handler never preempts an interrupt handler.

../_images/ditaa-e64238e31c9d1500c15e6d92c6a543aef95515aa.png
  • runs as a result of an IRQ (not of an exception)
  • there is no ‘process’ context associated
  • can’t trigger a context switch
    • no sleep
    • no schedule
    • no user memory access
../_images/ditaa-5a402cd7f81a0ddbe1a435bc3ab7949828f731f0.png

Deferrable actions

  • implemented using deferrable functions
  • softIRQ
    • runs in interrupt context
    • statically allocated
    • same handler may run in parallel on multiple cores
  • tasklet
    • runs in interrupt context
    • can be dinamically allocated
    • same handler runs are serialized
  • workqueues
    • run in process context
  • init: open_softirq()
  • activation: raise_softirq()
  • execution: do_softirq()
  • it runs
    • after an interrupt handler
    • from the ksoftirqd kernel thread
  • HI_SOFTIRQ
  • TIMER_SOFTIRQ
  • NET_TX_SOFTIRQ
  • NET_RX_SOFTIRQ
  • BLOCK_SOFTIRQ
  • TASKLET_SOFTIRQ
  • HRTIMER_SOFTIRQ
  • minimum priority kernel thread
  • runs self raised softirqs
  • trade-off between softirqs and process/kernel threads
  • implemented on top of soft IRQ
    • TASKLET_SOFITIRQ, HI_SOFTIRQ
  • init: tasklet_init
  • activation: tasklet_schedule
  • masking: tasklet_disable(), tasklet_enable()
  • implemented on top of kernel threads
    • TASKLET_SOFITIRQ, HI_SOFTIRQ
  • init: INIT_WORK
  • activation: schedule_work()
  • implemented on top of TIMER_SOFTIRQ
  • init: setup_timer
  • activation: mod_timer