Understanding IO Models: From Blocking to Epoll and Asynchronous I/O
This article explains the fundamentals of input/output (IO) in operating systems, covering the basic IO concept, the role of the OS, the two-stage IO process, and various IO models such as blocking, non‑blocking, select/poll/epoll, signal‑driven and asynchronous approaches.
What is IO?
IO stands for Input/Output; the IO model describes how data is read from or written to devices such as disks or networks.
What is Operating System IO?
Applications cannot directly perform IO operations; they must request the OS through APIs. Each process has a user space and a protected kernel space. The OS mediates all read/write actions.
Two Stages of an IO Operation
IO Call: The application process invokes an OS kernel call.
IO Execution: The kernel completes the actual IO.
OS‑Managed IO Process
Data preparation: the kernel waits for the device (e.g., network card) to place data into a kernel buffer.
Data copy: the kernel copies data from its buffer to the user‑process buffer.
A complete IO flow includes:
Application requests IO.
OS loads data from the external device into the kernel buffer.
OS copies data from the kernel buffer to the user buffer.
<code>// Essence of an IO operation: wait + copy</code>IO Models
1. Blocking IO
Server code blocks on
accept()and
read()until data is ready.
<code>listenfd = socket(); // open socket
bind(listenfd); // bind
listen(listenfd); // listen
while (true) {
buf = new buf[1024];
connfd = accept(listenfd); // block until connection
int n = read(connfd, buf); // block until data
doSomeThing(buf);
close(connfd);
}</code>2. Non‑Blocking IO
Read returns immediately with
-1if data is not ready; the program must poll the descriptor.
<code>arr = new Arr[];
listenfd = socket();
bind(listenfd);
listen(listenfd);
while (true) {
connfd = accept(listenfd); // block for connection
arr.add(connfd);
}
// Asynchronous thread checks readability
new Thread(){
for (connfd : arr){
buf = new buf[1024];
int n = read(connfd, buf); // non‑blocking read
if (n != -1){
newThreadDeal(buf);
close(connfd);
arr.remove(connfd);
}
}
};
void newThreadDeal(buf){
doSomeThing(buf);
}</code>3. I/O Multiplexing (select, poll, epoll)
Multiplexing lets a single thread monitor many file descriptors, reducing the need for one thread per descriptor.
select
select() asks the kernel to check a set of descriptors for readiness.
<code>listenfd = socket();
bind(listenfd);
listen(listenfd);
while (true) {
connfd = accept(listenfd);
arr.add(connfd);
}
new Thread(){
while (select(arr) > 0) {
for (connfd : arr) {
if (connfd can read) {
newThreadDeal(connfd);
arr.remove(connfd);
}
}
}
};
void newThreadDeal(connfd){
buf = new buf[1024];
int n = read(connfd, buf);
doSomeThing(buf);
close(connfd);
}</code>Advantages:
Reduces system calls.
Kernel performs readiness checks.
Drawbacks:
Copying descriptor arrays between user and kernel space can be costly under high concurrency.
Kernel must traverse the entire descriptor set, which is slow for large arrays.
After kernel detection, user space may need another traversal.
poll
Similar to select but removes the 1024‑descriptor limit by using a dynamic array.
epoll
epoll solves the above problems by keeping descriptors in the kernel, using an event‑driven wake‑up mechanism, and returning only ready descriptors.
<code>listenfd = socket();
bind(listenfd);
listen(listenfd);
int epfd = epoll_create(...);
while (1) {
connfd = accept(listenfd);
epoll_ctl(connfd, ...); // add to epoll set
}
new Thread(){
while ((arr = epoll_wait()) != null) {
for (connfd : arr) {
newThreadDeal(connfd);
}
}
};
void newThreadDeal(connfd){
buf = new buf[1024];
int n = read(connfd, buf);
doSomeThing(buf);
close(connfd);
}</code>LT vs. ET Modes
LT (Level‑Triggered): The kernel repeatedly notifies when a socket remains readable or writable until the application consumes the data.
ET (Edge‑Triggered): The kernel notifies only once when a state change occurs; the application must read all available data in that notification.
Signal‑Driven IO
When data becomes ready, the kernel sends a SIGIO signal to the process, allowing the thread to continue without blocking.
Asynchronous IO
The application issues a read request and returns immediately; the kernel notifies the process when the data is ready and copies it to user space.
Synchronous vs. Asynchronous
Synchronous IO requires the caller to wait for the operation to complete, while asynchronous IO lets the caller proceed and receive a notification once the operation finishes.
JD Cloud Developers
JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.