The full picture
- How it fits together on PCIe: firmware/OS enumerate devices and program BARs (base address registers) — carving each device's register windows into the physical address map; the driver
ioremaps them and pokes structs-at-addresses.lspci -vshows these windows on any Linux box — a nice 'I've actually looked' detail. - Why side-effectful addresses change the rules: reading a status register can pop a FIFO; writing a doorbell launches a command. Hence uncacheable mappings, explicit read/write barriers, and 'every access exactly as written' — the same reasoning as
volatile, at its origin (this is literally where C's volatile came from). - Doorbell + ring idiom completes the picture: driver writes descriptors in RAM (cacheable, fast), then one MMIO write to the doorbell says 'go' — minimizing slow uncached accesses. NVMe submission works exactly like this; it's why understanding MMIO explains modern storage stacks.
- User-space I/O is MMIO democratized: DPDK/SPDK map device BARs into a process and drive NICs/NVMe without the kernel — the logical endpoint of 'registers are just memory'.