The full picture
- NIC receive path in one breath: driver pre-posts empty buffers in a ring → packet arrives → NIC DMAs it into the next buffer + writes a completion descriptor → interrupt (or NAPI poll) → kernel builds skb, TCP/IP processing → data waits in socket buffer → your
read()copies to user space (or io_uring completes). The 'zero-copy' question is about eliminating that last copy. - IOMMU earns a mention: devices see I/O virtual addresses — the IOMMU translates and, crucially, contains them (a buggy/malicious device or a DMA-capable Thunderbolt port can't scribble over arbitrary RAM). Also what makes safe device passthrough to VMs (SR-IOV) possible.
- Descriptor rings are the pattern to recognize: producer/consumer circular buffers of pointers, doorbell registers, batched completions — NVMe queues, NIC rings and io_uring's SQ/CQ are one idea, and it's the same ring-buffer pattern as Disruptor/Kafka at other layers.
- Cost accounting: DMA doesn't make data movement free — it still spends memory bandwidth and cache effects (DDIO puts incoming packets straight into L3). On saturated boxes, 'who is eating my memory bandwidth' includes your NIC.