← Back to list
Computing & Embedded
#인터럽트#DMA#ISR#사이클 스틸링#IOMMU
Last updated · 2026-10-27

Interrupt and DMA (Direct Memory Access)

1. Overview

An Interrupt is an asynchronous control-transfer mechanism in which, while the CPU is executing a program sequentially, an event that demands immediate handling—I/O completion, an exception, an external signal—arises, so the CPU temporarily suspends its current flow, hands control to an interrupt service routine (ISR), and then returns to the original location. DMA (Direct Memory Access) is an I/O technique in which a dedicated controller transfers bulk I/O data directly between the I/O device and main memory without going through the CPU, freeing the CPU from the low-value work of copying data.

The background for the emergence of interrupts and DMA lies in the speed gap between the CPU and I/O devices. The CPU processes instructions in nanoseconds, but peripherals such as disks, networks, and keyboards operate in milliseconds to microseconds, so a speed difference of thousands to millions of times exists between them. The Programmed I/O used by early computers was a polling scheme in which the CPU repeatedly read the device status register while waiting for completion; in this case the CPU does no productive work until the slow device is ready and wastes hundreds of millions of cycles on busy waiting. Even though the CPU could process millions of other instructions during the several milliseconds it takes to read one disk block, it throws them all away.

The interrupt solves this waste with an event-driven approach of "notify the CPU when the device is ready." The CPU requests I/O and then immediately switches to other work, and when the device completes, it sends an interrupt signal to draw the CPU's attention. This eliminates wasted CPU cycles and lays the foundation for multitasking and real-time response. Yet interrupts alone are not enough, because the data transfer itself (moving bytes between memory and the device buffer) must still be performed by the CPU. Transferring 1KB requires an interrupt per byte (or word) or a CPU loop, so in high-speed, high-volume transfers the interrupt-handling overhead itself becomes the bottleneck.

Here a common misconception must be corrected. The interrupt is not a technique for reducing the amount of data transferred but one that eliminates the time the CPU spends waiting, and DMA is a technique that eliminates the labor the CPU spends on transfer. With interrupts alone, the CPU does not wait but wakes up at every transfer unit to carry bytes itself. With DMA alone, confirming completion by polling, there is no transfer labor but the CPU is tied up again waiting for completion. Only when the two techniques are combined is the ideal I/O—where the CPU "neither waits nor carries"—completed. On the premise of this complementarity, we examine each mechanism below.

DMA complements exactly this point. The CPU is involved only in "transfer start" and "transfer completion," and the actual byte movement in between is handled entirely by the DMA controller (DMAC). As a result, the interrupt "frees the CPU from waiting," and DMA "frees the CPU from copying data." The two techniques are not in competition but in a complementary relationship, and every modern high-performance I/O path (NVMe SSD, 100GbE NIC, GPU) operates with the combination of "transfer a block in bulk via DMA, and when the transfer finishes, notify the CPU with a single interrupt." Below we treat the internal operation of and interaction between each mechanism at an advanced level.

2. The Interrupt Handling Mechanism

When an interrupt occurs, the CPU enters the interrupt cycle right after finishing execution of the current instruction (the instruction boundary). The essence is the transparency requirement that "it must be possible to return exactly to the suspended point," and for this a process of saving and then restoring the current context is necessarily entailed. If this process goes wrong, the interrupt becomes a bug that corrupts the program, so hardware and the operating system cooperate meticulously.

The responsibility for ensuring this transparency is divided between hardware and software. The hardware (CPU) handles up to instruction-boundary identification, automatic saving of the PC and PSW, and branching through the vector table, while the software (ISR) handles the saving and restoring of the general-purpose registers it will clobber and the actual event handling. If this division is ambiguous, the ISR overwrites registers used by ordinary code and the program malfunctions after return. Therefore the compiler and operating system apply to the ISR special rules different from the calling convention (save all used registers, use only local state so it can be reentrant), and even in C the ISR function is compiled distinctly with an interrupt attribute or a separate prologue/epilogue.

The typical interrupt handling procedure is as follows. First, the device sends an interrupt request (IRQ) signal to the interrupt controller (e.g., the APIC on x86). Second, the controller judges priority and activates the CPU's INTR pin. Third, after finishing the current instruction, the CPU checks whether to accept the interrupt (whether it is masked). Fourth, if accepted, it saves the current program counter (PC) and status register (PSW) on the stack and additionally saves general-purpose registers (some saved by the ISR). Fifth, from the interrupt vector table it reads the ISR address corresponding to that interrupt number and loads it into the PC. Sixth, the ISR runs, checks the device status, performs the necessary handling, and then notifies the controller of completion (EOI, End Of Interrupt). Seventh, it restores the saved context and returns to the suspended point with the IRET instruction.

flowchart TB
  START["Normal program execution"] --> CHK{"Check interrupt at instruction boundary"}
  CHK -->|"No request"| START
  CHK -->|"Request present"| MASK{"Interrupts enabled?"}
  MASK -->|"Masked"| START
  MASK -->|"Allowed"| SAVE["Save context(PC·PSW·registers)"]
  SAVE --> VEC["Obtain ISR address from vector table"]
  VEC --> ISR["Run ISR(device handling·EOI notice)"]
  ISR --> REST["Restore context"]
  REST --> IRET["Return to suspended point via IRET"]
  IRET --> START

Here the role of the vectored interrupt is important. The early scheme had a single ISR poll all devices in turn to find the cause, but this increases latency in proportion to the number of devices. The vectored approach has the device provide its identification number (vector) along with the interrupt, so the CPU branches directly to the corresponding ISR from the vector table. For example, x86 has 256 vectors (0–255), of which 0–31 are CPU exceptions and 32 and above are assigned to external devices. Thanks to this structure, interrupt latency is kept constant even with hundreds of devices.

When designing a real-time system, interrupt latency must be managed by decomposing it into components. Latency is divided into ① the interrupt-disabled latency of waiting for an interrupt-disabled interval to end, ② the hardware latency of the hardware delivering the signal and the CPU reaching an instruction boundary, ③ the dispatch latency spent on context saving and vector branching, and ④ the handling latency until the ISR finishes the actual processing. In domains such as automotive and aviation where the worst-case latency is directly tied to safety, to keep ① short the interrupt-disabled interval in critical sections is limited to within tens of cycles, and the ISR is designed to perform only the strictly necessary processing and hand the rest to the bottom-half. In short, "interrupts are fast" must be read not as an average but as a guarantee of the upper bound of each decomposed component.

Also, nested interrupts and priority govern real-time behavior. If a higher-priority interrupt arrives while an ISR is running, the current ISR must be suspended again and the higher ISR handled first; for this, the mask level is adjusted at ISR entry to allow only higher interrupts. For example, in an industrial motor controller "power-loss detection" is placed as the top-priority non-maskable interrupt (NMI) so that an immediate safe stop executes under any circumstance. Conversely, an interrupt requiring long processing minimizes the ISR and hands the rest to deferred processing (Linux softirq·tasklet·workqueue, the so-called top-half/bottom-half split), keeping the interrupt-disabled interval short to guarantee the responsiveness of other devices.

3. Types of Interrupts

Interrupts are classified along several axes by their cause, and each type differs in handling method and priority. Classifying types accurately avoids confusing exceptions with external interrupts and lets one correctly design the operating system's trap handling and device handling. In particular, the distinction of "synchronous/asynchronous" and "internal/external" is practically important in debugging and real-time design.

What arises internally and synchronously with CPU execution is an exception (trap/exception). Division by zero, page faults, privilege violations, and system calls (software interrupts) belong here; because a particular instruction's execution is the cause, they are reproducible and occur deterministically at that instruction point. In contrast, a hardware interrupt comes in asynchronously from an external device regardless of CPU execution, with disk completion, network packet arrival, and timer ticks being representative. Because it is asynchronous, one cannot predict when it will arrive, so a concurrency problem (a race between the interrupt and ordinary code) occurs when accessing shared data structures, and to prevent this interrupts are disabled over a short interval or a spinlock is used.

Among exceptions, the page fault is an example that shows the sophistication of operating system design. If a page accessed in virtual memory is not in physical memory, the CPU raises a page-fault exception, the OS loads that page from disk, and then re-executes "the very instruction that caused the fault." That is, exceptions are used not only for error handling but also as a means of implementing normal functions such as demand paging and copy-on-write. Meanwhile, a system call is a software interrupt (trap) intentionally raised by an application program, the only gateway for safely entering kernel mode from user mode. Understanding that "exceptions/traps" are thus not mere accidents but active control transfers underpinning protection and virtualization is necessary to correctly explain how the operating system works.

Classification axis Type Example Characteristic
Origin External (hardware) Disk·NIC·timer Asynchronous, I/O completion notice
Internal (exception/trap) div-by-zero·page fault·syscall Synchronous, caused by instruction execution
Masking Maskable (IRQ) Ordinary device interrupt Software can temporarily block
Non-maskable (NMI) Power anomaly·hardware fault Cannot block, top-priority handling
Signaling Level-triggered Shared line of multiple devices Persists while signal is held
Edge-triggered One-shot event Detected only at the moment of state change

Among the distinctions in the table, the difference between maskable and non-maskable is decisive in safety design. An ordinary device interrupt can be blocked briefly to protect a critical section, but fatal events such as a power anomaly or memory parity error must not be blocked, so they are classified as NMI to be handled immediately in any case. Also, the choice of edge-trigger versus level-trigger affects interrupt sharing and loss prevention. Level-triggering has the advantage that when multiple devices share one line the signal is held and not missed, but it requires polling to identify the causing device; edge-triggering detects only the moment of change and so is fast, but if the ISR does not clear the signal in time it can lose subsequent interrupts. A representative case is the real Linux-kernel failure in which a shared PCI interrupt stalls a device as a "lost interrupt," originating from this very difference in mechanism.

4. The Operating Principle and Transfer Modes of DMA

The core insight of DMA is that "data movement does not necessarily have to pass through the CPU's registers." Since memory and device buffers are both connected to the system bus, any device that can act as the bus master can read from and write to memory directly. The DMA controller (DMAC) is the dedicated hardware that performs exactly this bus-master function; once it receives from the CPU only the command to transfer "from where (device), to where (memory start address), how many bytes," it handles the rest on its own.

For DMA to operate, the cache coherence problem must be addressed without fail. Because the CPU reads from and writes to memory through the cache, when DMA updates memory directly an inconsistency arises with the old value remaining in the CPU cache. Therefore, before a DMA receive (device→memory) the cache of that region must be invalidated, and before a DMA send (memory→device) the cache must be flushed to memory so the latest value is reflected. Modern x86 guarantees DMA coherence automatically in hardware (coherent DMA), but on ARM embedded systems and the like the driver must explicitly manage the cache or allocate a non-cacheable DMA buffer, and omitting this becomes a hard-to-trace bug of "intermittently corrupted data."

The transfer procedure is as follows. The CPU sets in the DMAC's registers the transfer direction, memory start address, and transfer length (count), directs the transfer, and then immediately switches to other work. When the device has data ready, the DMAC obtains the bus grant from the CPU, writes bytes directly to memory and decrements the count, and when the count reaches zero it raises a single transfer-completion interrupt to the CPU. The CPU thus intervenes only twice—at the beginning and the end—for the entire transfer of thousands to millions of bytes.

sequenceDiagram
  participant CPU
  participant DMAC as "DMA controller"
  participant DEV as "I/O device"
  participant MEM as "Main memory"
  CPU->>DMAC: "Set transfer(address·length·direction)"
  Note over CPU: "Do other work(not blocked)"
  DEV->>DMAC: "Data ready(DREQ)"
  DMAC->>CPU: "Request bus grant(HOLD)"
  CPU-->>DMAC: "Yield bus(HLDA)"
  loop "Block transfer"
    DEV->>DMAC: "Data"
    DMAC->>MEM: "Write directly to memory"
    DMAC->>DMAC: "Decrement count"
  end
  DMAC->>CPU: "Transfer-completion interrupt"
  CPU->>CPU: "Handle completion in ISR"

Depending on how the DMAC shares the bus with the CPU, there are three transfer modes. First, cycle stealing transfers one word at a time by "stealing" idle cycles in which the CPU is not using the bus; it has almost no effect on CPU performance but transfers slowly. Second, burst/block mode has the DMAC monopolize the bus to transfer an entire block at once; the transfer is fastest, but during that time the CPU may stall because it cannot access memory. Third, transparent mode transfers only in intervals where the CPU is clearly not using the bus, so it has no effect on the CPU at all but is the slowest. For example, real-time-critical audio streaming chooses cycle stealing while large-volume disk backup chooses burst mode, selecting the mode to match the application's characteristics.

In modern systems, beyond the simple DMAC, bus-mastering DMA—in which each device itself becomes the bus master—is standard, and PCIe devices (NVMe SSD, GPU, NIC) all embed their own DMA engine. In particular, descriptor-based scatter-gather DMA processes several physically scattered memory regions bundled into a single transfer command, so it can transfer even a user buffer that is non-contiguous due to virtual memory all at once. This is the hardware foundation of zero-copy networking and high-performance storage.

High-speed devices have the CPU and DMA engine cooperate asynchronously through a descriptor ring in shared memory. The CPU (driver) fills the ring with descriptors holding the address and length of the buffer to transfer and advances the "producer index," while the device's DMA engine follows the "consumer index," reads descriptors, performs the transfer, and then writes back a completion flag. Thanks to this structure, the CPU and device can overlap thousands of transfers like a pipeline without waiting for each other, and the NIC's TX/RX ring and NVMe's submission/completion queues (SQ/CQ) all follow this principle. In the end, "DMA is fast" must be understood not as a single transfer being fast but as being able to progress many transfers concurrently (outstanding I/O) without waiting for completion.

5. Comparison of I/O Methods

The three I/O methods—programmed (polling), interrupt-driven, and DMA—must be understood as an evolutionary process that has progressively freed the CPU along the axis of "how much the CPU is involved." The practical implication is not simply "DMA is best" but that the appropriate method differs according to the trade-offs of data size, real-time requirements, and implementation complexity.

Category Programmed (polling) Interrupt-driven DMA
CPU involvement Entire process (busy wait) Per data unit Only twice: start·completion
Data-movement agent CPU CPU DMA controller
CPU efficiency Very low Medium Very high
Response latency Bound to polling period Interrupt-latency level Low (per block)
Suitable situation Ultra-fast·small, simple device Low-mid speed·irregular events High-speed·large block transfer
Overhead source Busy wait Context save/restore Bus contention·setup cost

The choice among the three methods can be summarized as determined by the combination of two variables, "device speed" and "size of the transfer unit." A simple device that is slow and small-volume yet needs immediacy (reading one byte from a temperature sensor) is served concisely by polling with no setup overhead; a device that is low-mid speed with sparse events (keyboard·mouse) suits interrupts; and high-speed, large-volume block transfer (disk·network·GPU) makes DMA the only realistic choice. What matters is that this boundary ceaselessly moves with hardware advances—an example being how 1GbE, for which interrupts once sufficed, sped up to 100GbE and shifted back to a combination of DMA and polling (NAPI). Rather than memorizing a particular method as the correct answer, the professional engineer must have the mindset to judge trade-offs along the axes of speed and transfer unit.

Polling is mostly inefficient, but it can actually be advantageous for very short, immediate transfers. Interrupts cost hundreds to thousands of cycles in context save/restore and vector branching, so if the data to transfer is only a byte or two this overhead exceeds the transfer itself. Paradoxically, on 10GbE and 100GbE high-speed networks, when packets pour in so fast that interrupts occur millions of times per second, an interrupt storm arises in which the CPU saturates on ISR handling alone. To prevent this, Linux NAPI, once it receives an interrupt, switches thereafter to polling to batch-process many packets, and DPDK reverted to a scheme that turns off interrupts entirely and has a dedicated core continuously poll. That is, "polling → interrupt → DMA" is not a simple replacement but a spectrum that coexists and reverts depending on the situation.

In concrete numbers, when reading a 4KB block from disk, polling has the CPU check status throughout several milliseconds and discard millions of cycles, whereas DMA requires the CPU to set up the transfer in tens of cycles and then handle only a single completion interrupt (a few thousand cycles). NVMe SSDs deliver millions of IOPS per second because, in addition to this DMA + completion-interrupt combination, deep command queues (up to 64K queues × 64K depth) progress many transfers concurrently.

As a concrete application case, one can cite the broker of Apache Kafka. When Kafka transfers a disk log segment to a consumer it uses the sendfile system call, which delivers the data placed in the page cache by disk DMA directly to the NIC via scatter-gather DMA without going through user space. Throughout the transfer, all the CPU does is set up the transfer and handle the completion interrupt, so a single broker relays hundreds of thousands of messages per second while keeping CPU utilization low. This is a typical structure in which the interrupt (completion notice) and DMA (the actual movement) combine to raise throughput.

6. Advanced: The Evolution of Interrupts and DMA in Modern Systems

In modern high-performance computing, interrupts and DMA have evolved greatly to meet the demands of multicore, virtualization, and security. This is because the old single-PIC (8259) and single-DMAC (8237) structures cannot cope with dozens of cores and millions of IOPS. This section highlights the key developments that govern performance and security in practice.

First, MSI/MSI-X (Message Signaled Interrupt) is a scheme in which, instead of a physical interrupt pin, the device delivers the interrupt by writing a specific value to an agreed memory address. The pin-based scheme had the problems of identification polling due to shared lines and of line scarcity, but MSI-X supports up to 2,048 independent vectors per device, letting multi-queue NICs and NVMe have a dedicated interrupt queue per core. For example, a 100GbE NIC in a 64-core server connects each of its 64 receive queues to the MSI-X vector of a different core (RSS, Receive Side Scaling), distributing interrupt handling across all cores and improving cache locality.

Second, IOMMU (Input-Output MMU, Intel VT-d·AMD-Vi) solves the security and virtualization problems of DMA. Traditional DMA accesses physical addresses directly, so a DMA attack in which a malicious or faulty device overwrites arbitrary memory (e.g., DMA hijacking via Thunderbolt) was possible. The IOMMU translates the I/O virtual address the device uses into the actual physical address and limits the access range, isolating the device so it accesses only the permitted region. This is the key technology that protects memory between VMs while assigning a physical device directly to a virtual machine (PCI passthrough), and it becomes the foundation of the cloud's SR-IOV-based network virtualization.

Third, interrupt coalescing and hybrid polling are the practical solutions to the interrupt-storm problem. By having the device gather several events and raise one interrupt (coalescing), or poll for a certain time and then switch to interrupts, they balance between throughput and latency. The NVMe driver's hybrid polling turns off the completion interrupt and polls briefly in high-IOPS situations, reducing both interrupt overhead and latency at once. As of 2024, the Linux kernel's io_uring is a representative case that combines such completion polling (IOPOLL) with DMA to achieve millions of IOPS in user space.

Fourth, interrupt delivery in virtualized environments governs performance. When a physical interrupt occurs in a virtual machine, traditionally the hypervisor intercepted and injected it into the guest, and this VM entry/exit (VM-exit) was a large overhead. Virtual-interrupt hardware support such as Intel APICv and AMD AVIC and posted interrupts remove this cost by delivering the interrupt directly to the guest vCPU without hypervisor intervention. The combination of assigning a physical NIC's virtual function (VF) directly to a VM via SR-IOV, isolating that VF's DMA with the IOMMU, and delivering interrupts directly via posted interrupts is today's standard path for cloud high-performance networking.

7. Considerations and Implications

Interrupts and DMA are foundational concepts more than half a century old, but from the professional engineer's perspective of designing high-performance, real-time, and secure systems they remain a source of key trade-offs. The following items must be considered comprehensively.

  • Balance of interrupt latency vs. throughput: Real-time control (robots·automobiles) lives or dies by deterministic interrupt latency (worst-case latency), so the ISR must be kept extremely short and higher priority guaranteed. Conversely, a high-throughput server benefits from reducing the number of interrupts via interrupt coalescing·NAPI·polling. When one system has both demands (e.g., an autonomous-driving ECU), priority separation and core isolation must be pursued together.

  • DMA security and the trust boundary: Because DMA is inherently direct memory access, it can break the trust boundary. For external ports (Thunderbolt·USB4) and the cloud's passthrough devices the IOMMU must be enabled without fail, and in confidential-computing environments encryption and verification of device DMA (e.g., PCIe IDE/TDISP) must be applied. The practice of "turning off the IOMMU for performance" becomes an opening for security incidents, so it must be judged carefully.

  • Multicore scalability and interrupt affinity: If interrupts pile onto a single core, only that core saturates and overall performance is limited. Distribute the load with MSI-X multiple vectors and interrupt-affinity settings, and match the core that handles the interrupt with the core that consumes the data (cache locality) to minimize remote memory access in a NUMA environment.

  • Power and energy perspective: Because polling keeps the CPU awake and consumes power, in battery-based IoT and embedded systems an interrupt-based design that places the CPU in low-power sleep and wakes it only on an event is essential. Conversely, latency-sensitive data-center workloads accept the power cost and choose polling. Power and performance are two ends of the same axis, and workload characteristics determine the choice.

  • Integrated design with related technologies: DMA extends to zero-copy I/O, RDMA (remote DMA), GPU Direct, and SmartNIC·DPU. In particular, in AI data centers GPUDirect RDMA has the NIC DMA directly into GPU memory without going through the CPU or system memory, removing the communication bottleneck of distributed training. The professional engineer must be able to optimize the entire data path from a node-to-node DMA (RDMA) perspective, beyond single-node DMA.

References


In one line: The interrupt is an event-driven control transfer that frees the CPU from waiting on slow devices, and DMA is a technique that entrusts data movement to a dedicated controller to free the CPU from copying work; modern high-performance I/O extends, atop the combination of "transfer a block via DMA and a single interrupt on completion," with MSI-X·IOMMU·a return to polling.