Linux có 5 I/O model, từ đơn giản nhất đến hiệu suất cao nhất:
- Blocking I/O: syscall (read/write) block thread cho đến khi data sẵn sàng — đơn giản nhưng thread không làm gì được trong lúc chờ.
- Non-blocking I/O: syscall trả về EAGAIN ngay nếu chưa có data — application phải poll liên tục (busy waiting, tốn CPU).
- I/O Multiplexing (select/poll/epoll): một thread monitor nhiều file descriptors, block trên select/epoll_wait cho đến khi ít nhất 1 fd ready — đây là nền tảng của event loop (Node.js, Nginx).
- Signal-driven I/O (SIGIO): kernel gửi signal khi fd ready — khó dùng, hiếm gặp.
- Async I/O (AIO/io_uring): initiate I/O và tiếp tục làm việc khác, được notify qua callback/completion queue khi xong — truly async, không block.
io_uring (Linux 5.1+) là next-gen: zero-copy, batching syscalls, giảm context switch đáng kể; Node.js 18+ đang integrate. Thực tế: Node.js libuv dùng epoll (Linux)/kqueue (macOS)/IOCP (Windows); Java NIO/Netty cũng dùng epoll; Go runtime có custom network poller trên epoll/kqueue.
Linux defines 5 I/O models, from simplest to highest-performing:
- Blocking I/O: a syscall (read/write) blocks the thread until data is available — simple, but the thread cannot do anything else while waiting.
- Non-blocking I/O: the syscall returns EAGAIN immediately if data is not yet available — the application must poll continuously (busy waiting, wasting CPU).
- I/O Multiplexing (select/poll/epoll): a single thread monitors multiple file descriptors and blocks on select/epoll_wait until at least one fd is ready — the foundation of event loops (Node.js, Nginx).
- Signal-driven I/O (SIGIO): the kernel sends a signal when an fd is ready — difficult to use and rarely seen in practice.
- Async I/O (AIO/io_uring): the application initiates I/O and continues other work, receiving notification via callback or completion queue when done — truly asynchronous, never blocks.
io_uring (Linux 5.1+) is the next generation: zero-copy, syscall batching, dramatically reduced context switches; Node.js 18+ is integrating it. In practice: Node.js libuv uses epoll (Linux)/kqueue (macOS)/IOCP (Windows); Java NIO/Netty also leverage epoll; the Go runtime has a custom network poller built on epoll/kqueue.