Search topics...

Explain how interrupts work. What are some things that you should never do in an interrupt function?

0 upvotes
Practice with AISoon

How interrupts work

An interrupt is an asynchronous (hardware) or synchronous (software/exception) event that diverts the CPU from its current instruction stream to run a dedicated handler (ISR). The typical sequence:

  1. Request: A peripheral asserts an interrupt line (or a software event raises an exception). The signal is routed through an interrupt controller (e.g., the ARM NVIC on Cortex-M, or a GIC on Cortex-A, or the legacy PIC/APIC on x86).
  2. Prioritization / masking: The controller checks whether the interrupt is enabled and whether its priority is high enough to preempt the current execution level. Globally maskable interrupts can be disabled (e.g., PRIMASK/BASEPRI on Cortex-M, cli/sti on x86). Some interrupts are non-maskable (NMI).
  3. Acknowledge & vector: The CPU finishes/abandons the current instruction to a safe point, then looks up the handler address in the interrupt vector table and branches to it.
  4. Context save: Enough state is saved to resume later. On Cortex-M the hardware automatically stacks a subset of registers (xPSR, PC, LR, R0–R3, R12) onto the active stack; the rest is preserved by the handler/ABI. On other architectures more is done in software.
  5. Execute ISR: The handler runs, typically: read the peripheral status, clear the interrupt source/flag (otherwise it re-fires), do the minimal work, and signal the rest of the system (set a flag, post to a queue, give a semaphore).
  6. Return: An interrupt-return (e.g., BX LR with the special EXC_RETURN value on Cortex-M, iret on x86) restores context and resumes the interrupted code (or, in an RTOS, may trigger a context switch to a now-ready higher-priority task, e.g., via PendSV).

Related concepts worth mentioning: interrupt latency (time from assertion to first ISR instruction), nesting/preemption (higher-priority IRQs interrupting lower-priority ISRs), tail-chaining (Cortex-M optimization that skips redundant unstack/stack between back-to-back IRQs), edge vs. level triggering, and shared interrupt lines.

Things you should never (or rarely) do in an ISR

  • Block or wait: No busy-wait loops, no blocking I/O, no waiting on a mutex/semaphore that another task holds, no sleep/delay. ISRs must run to completion quickly.
  • Run long / heavy work: Keep ISRs short and deterministic. Defer heavy processing to a bottom half — a task, work queue, or DPC. (Top-half/bottom-half or "deferred procedure" pattern.)
  • Call non-reentrant / non-ISR-safe APIs: Avoid printf, malloc/free, most of the C standard library, and any function not documented as ISR-safe. In an RTOS, only call the "FromISR" variants (e.g., FreeRTOS xQueueSendFromISR).
  • Take ordinary blocking locks: Acquiring a mutex meant for thread context can deadlock or cause priority issues. Use lock-free signaling or ISR-safe primitives.
  • Forget to clear the interrupt source: If you don't acknowledge/clear the flag in the peripheral (and sometimes in the controller), the ISR re-enters immediately — an interrupt storm.
  • Ignore volatile / memory ordering: Shared variables between ISR and main code must be volatile (and access must be atomic or protected), or the compiler may cache/reorder them. Also insert memory/peripheral barriers where required (e.g., DSB/ISB, or clearing an NVIC pending flag before returning).
  • Do floating-point unsafely: If the FPU context isn't saved (or lazy stacking is misconfigured), using FP in an ISR can corrupt a task's FP state. Know your platform's rules.
  • Assume a big stack: ISRs may run on a limited interrupt stack; avoid large local arrays and deep recursion.
  • Re-enable interrupts carelessly or leave them disabled too long: Long critical sections inflate interrupt latency for the whole system.
  • Throw exceptions / use constructs with unbounded runtime (in C++ contexts).

The guiding principle: do the minimum to service the hardware and hand off the rest — read/clear the source, capture data, signal a task, and return.