Interrupts and IRQs
Interrupts is the way to do I/O.
Interrupt handler is a procedure (a subroutine) attached via slot in a Interrupt Table to a particular hardware or software event. It is an isolated (withing a CPU context) lightweight process (with its own stack). This is the right way to do I/O. Not threads or async nonsense (over-abstraction).
Most of the time there is nothing to do when data isn't here, so it is OK to block on receive. The main point is that blocking the whole process is too much (and threads are crap) so very lightweight, share-nothing coroutines, like interrupt handlers, is the right way.
Since blocking on receive is OK, cooperative multitasking is also OK. This means that there is no need to explicitly schedule these processes by the kernel as truly independent entities - a program could do it itself. This simplifies everything and with simplicity comes robustness.
see also Buzzwords/Async