The simplest way of device-processor communication is memory mapped I/O. By allocating portion(s) of its physical address space to devices, the architecture allows communication with devices as though they were a part of main memory. Using memory mapped I/O is as simple as using straight memory accesses (via pointers in C), with perhaps some timing differences thrown in.
Some architectures also have an I/O address space separate from the usual address space, which reduces the loss of physical address space but complicates driver programming and makes some device I/O inaccessible from standard C. These platforms require the driver programmer to use inline assembly macros or functions to read from and write to the I/O address space.
Modern architectures have a small number of available hardware interrupts that devices can use to interrupt the processor when they are ready to be attended to.
Transferring data to and from devices has in the past been a very time-consuming operation. To solve this problem (and relinquish the processor for other tasks), computer designers used a Direct Memory Access, or DMA, controller, that has access to the memory bus and some devices in the computer specifically designed to use it. The processor can then tell the controller to transfer