Instiq
Chapter 5 · Embedded software development·v1.0.0·Updated 7/10/2026·~17 min

What's changed: Initial version

5.4Interrupt handlers & resource management

Key points

Covers the design principle of keeping an ISR (interrupt service routine) short and deferring follow-up work, designing reentrant functions and using a critical section for mutual exclusion, and addressing memory exhaustion and leaks to protect limited stack/heap resources.

Interrupts are the core mechanism by which an embedded system reacts to external events in real time, but packing too much processing into an interrupt handler (ISR) stalls other interrupts and normal processing for that duration, harming the responsiveness of the system as a whole. Furthermore, if the ISR and normal processing share the same variable, data can become corrupted depending on timing. This section covers how an ISR should be designed and how to protect shared resources—judgments that determine the reliability of embedded software.

5.4.1ISR design principles (keep short, defer processing)

  • An ISR (interrupt service routine) is the processing the CPU executes when an interrupt occurs. While an ISR is running, interrupts at the same or a lower level are masked and cannot be accepted, so the longer an ISR runs, the more other interrupt handling is delayed, degrading the real-time responsiveness of the system as a whole. The principle is: "do only the bare minimum inside the ISR (clearing the interrupt cause, saving critical data, etc.), and defer time-consuming processing to normal (task-level) processing."
  • A common deferral implementation pattern is for the ISR to only store received data in a buffer and set a flag or semaphore, while the actual data processing and communication handling are done on the normal task side (main loop or RTOS task). This minimizes interrupt-response delay (interrupt latency) while still allowing time-consuming processing to run.

5.4.2Reentrancy and critical sections

  • Reentrant (re-entrant) describes a property where, if a function is interrupted mid-execution and the same function is then invoked again from the interrupt handler (or another task), both invocations execute correctly without affecting each other's results. The basic way to ensure reentrancy is to avoid dependence on static or global variables and complete the work using only local variables (on the stack). A shared function that might be called from an ISR must be designed to be reentrant.
  • When an ISR and normal processing (or multiple tasks) read and write the same global variable or shared buffer, if one process is interrupted in an incomplete state and the other accesses that variable, data can be corrupted (a race condition). To prevent this, access to a shared variable is wrapped in a critical section (an interrupt-disabled region or a mutual-exclusion lock), temporarily blocking interruption by other processing. However, making the critical section too long degrades real-time responsiveness, since interrupts cannot be responded to during that time, so the design essential is to keep it to the minimum necessary scope.
Exam point

Most-tested: "keep the ISR short and defer time-consuming processing to normal processing", "reentrant means it works correctly even when re-invoked during an interrupt, by not depending on static/global variables", and "keep a critical section to the minimum necessary scope to protect a shared variable." Also learn to spot a race condition on a shared variable between an ISR and normal processing.

Suppose a firmware developer builds a system where a timer interrupt periodically aggregates data arriving from multiple sensors, and the normal main loop reads that aggregated result to send it to an upstream system, and then encounters a data-corruption bug. Investigating, they find that the timer-interrupt ISR reads and writes a 32-bit global variable (the aggregate value), and the main loop also directly reads the same global variable. Because the CPU in use is an 8-bit MCU, where access to a 32-bit variable is split into multiple instructions (multi-byte reads/writes), they judge that a race condition can occur where a timer interrupt fires while the main loop is mid-read of that variable, causing the loop to read an inconsistent value with the upper byte already updated but the lower byte still old. Based on this diagnosis, they wrap the moment just before and after the main loop reads the aggregate value in a critical section (temporarily disabling interrupts), so the timer interrupt cannot rewrite the aggregate value during the read. However, extending the critical section over the entire aggregation process would block the timer interrupt itself for an extended time and disrupt other real-time processing, so they narrow its scope to just the minimum few instructions needed to read the 32-bit variable. Furthermore, because the shared utility function invoked by this aggregation process used a static variable, they judged there was a risk of cross-contamination if it were called from both the ISR and the main loop, and fixed the implementation to be reentrant by replacing the static variable with a local variable (on the stack). In this way, the core of reliability design around interrupt handlers is to identify the resources shared between the ISR and normal processing, protect only the spots where a race condition can occur with a minimally scoped critical section, and design common functions to be reentrant.

ConceptPurposeDownside of overdoing it
Keeping the ISR shortPrevents delaying other interrupt responses(If under-applied) increased interrupt latency, worse real-time behavior
Reentrant designEnsures correct behavior even when re-invoked during an interrupt(If under-applied) cross-contamination of static/global variables
Critical sectionProvides mutual exclusion for shared-variable accessIf too broad, blocks interrupt response for an extended time
Warning

Trap: "Writing all processing inside the ISR is safe because it avoids coordination mistakes with normal processing" is wrong—the longer the ISR runs, the more other interrupts are masked and real-time behavior worsens, so time-consuming processing should be deferred to normal processing. Also wrong: "the longer the critical section, the safer"—the broader its scope, the longer interrupts cannot be responded to during it, worsening real-time behavior, so the correct design is to keep it scoped to the minimum access to the shared variable that needs protecting.

5.4.3Stack/heap constraints and memory exhaustion/leaks

  • On an embedded device, the stack (for function calls and local variables) and the heap (for dynamic allocation) are, unlike on a PC, extremely limited—often just a few KB to a few tens of KB. If the depth of recursive function calls, or the nesting of ISRs (multiple interrupts), grows deeper than expected, a stack overflow occurs (the stack region encroaches on other variable regions), leading to hard-to-diagnose undefined behavior or a crash.
  • In a design that repeatedly uses dynamic memory allocation (e.g., malloc), forgetting to free allocated memory accumulates as a memory leak, and on an embedded device that runs for long periods, this eventually leads to memory exhaustion, where new allocations fail and functionality stops. For embedded devices premised on long-term unattended operation, designs that minimize dynamic allocation in favor of static allocation, or that rigorously audit allocate/free pairs, are emphasized.
ISR, reentrancy, stack.
Sharing without corruption

5.4.4Section summary

  • Keep the ISR to the bare minimum and defer time-consuming processing to normal processing. The longer it runs, the more other interrupts are masked, worsening real-time behavior
  • Reentrant (not dependent on static/global variables) design of common functions, and a critical section (scoped to the minimum necessary) to protect shared variables, are the core of mutual exclusion
  • With limited stack/heap resources, watch for stack overflow from excessive nesting depth, and memory exhaustion resulting from a memory leak caused by forgetting to free allocations

Sign in to track progress — Log in.

Quick check

(just a quick review)

Q1. A design executes everything—including processing the received data and sending it to an upstream system—inside the timer-interrupt ISR, and as a result, responses to other important interrupts have become delayed. Which corrective action is most appropriate?

Q2. On an 8-bit MCU, both the timer-interrupt ISR and the normal main loop read and write the same 32-bit global variable, and a bug occurs where, if an interrupt fires mid-read in the main loop, the loop reads a value with a mix of old and new upper and lower bytes. Which countermeasure is most appropriate?

Q3. A common data-aggregation utility function, called from both the ISR and the main loop, internally uses a static variable to retain the previous value. Which fix is most appropriate to allow this function to be safely called from both?

Check your understandingPractice questions for Chapter 5: Embedded software development

Keep track of your progress

The full study guide is free to read. Sign up free to practice with the question bank, track what you have read, review your mistakes, and highlight passages.