操作系统的 IO 模型有哪些?

#计算机网络#操作系统#面试八股

操作系统的 IO 模型有哪些?

一次网络读(recv)实际分两个阶段:等待数据就绪和把数据从内核缓冲区拷到用户缓冲区。 按这两个阶段谁来等、谁来拷,历史上归纳出五种模型:阻塞、非阻塞、IO 多路复用、信号驱动、异步 IO。 前四种都至少要自己完成拷贝,只有最后的异步 IO 两个阶段全交给内核。

1. 五种模型的时间线

阻塞 IO      app: recv──────(睡)──────────┬─拷贝─┬─返回
             kernel: 等待数据就绪             └──────┘

非阻塞 IO    app: recv──EAGAIN──recv──EAGAIN──recv─┬─拷贝─┬─返回
             kernel: 等待数据就绪  (用户轮询烧 CPU)  └──────┘

多路复用     app: select──(睡)──就绪返回──recv─┬─拷贝─┬─返回
             kernel: 同时等待 N 个 fd            └──────┘

信号驱动     app: 注册 SIGIO──继续干活────────(信号)──recv─┬─拷贝─┬─返回
             kernel: 等待数据就绪 ──────────────┘        └──────┘

异步 IO      app: aio_read──返回──(完全不管)──────────────┬─完成事件
             kernel: 等待数据就绪 ──────── 拷贝到用户缓冲区 ┘

2. 对比表

模型等待阶段拷贝阶段系统调用次数并发能力典型接口
阻塞 IO进程睡眠进程参与1差,一连接一线程read/recv
非阻塞 IO用户轮询进程参与N(反复)差,空转烧 CPUO_NONBLOCK
IO 多路复用内核代等(可等多个)进程参与2好,单线程管万连接select/poll/epoll
信号驱动内核代等,信号通知进程参与2一般,信号难管理SIGIO + fcntl
异步 IO内核全包内核全包1~2最好,真异步io_uring、IOCP、POSIX AIO

判断"异步"的硬标准只有一条:函数返回时数据是否已经躺在你的缓冲区里。 epoll 返回时只是告诉你"可读了",所以你写的仍是同步代码。

3. 代码:多路复用的骨架

int ep = epoll_create1(0);
struct epoll_event ev = {.events = EPOLLIN | EPOLLET, .data.fd = listen_fd};
epoll_ctl(ep, EPOLL_CTL_ADD, listen_fd, &ev);

struct epoll_event evs[64];
for (;;) {
    int n = epoll_wait(ep, evs, 64, -1);        // 阻塞在这一处,只为等待"就绪"
    for (int i = 0; i < n; i++) {
        int fd = evs[i].data.fd;
        if (fd == listen_fd) {
            accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK);   // 新连接设非阻塞
        } else {
            ssize_t r;
            while ((r = read(fd, buf, sizeof buf)) > 0) {    // 拷贝仍由本线程做
                handle(buf, r);
            }
            if (r == 0 || (r < 0 && errno != EAGAIN)) close(fd);
        }
    }
}
# 快速确认某个进程用的是哪种模型
strace -f -e trace=epoll_wait,read,write,io_uring_enter -p 1234 2>&1 | head -30
cat /proc/1234/stack          # 阻塞在哪个内核函数

4. 常见追问

Q:epoll 能不能监听普通文件? 不能。普通文件的读写不经过 socket 层,poll 永远返回就绪(磁盘数据总"可读"), 多路复用对文件没意义。文件 IO 想异步只能用 io_uring 或线程池 + 阻塞读。

Q:为什么说 Redis 单线程也很快? 它把"等待"全部交给 epoll,把"处理"做成纯内存操作,单次处理只有微秒级,瓶颈根本不在 CPU; 真正的耗时是网络往返,用多线程反而增加锁和上下文切换。

Q:io_uring 和 epoll 的本质区别? epoll 是就绪通知(readiness),内核只告诉你"能读了";io_uring 是完成通知(completion), 你把"读 4KB 到 buf"整条命令投进提交队列,内核做完把结果写进完成队列,用户态通过共享内存环收割, 连系统调用都能批量省掉。

小结

五种模型的分水岭是"拷贝阶段归谁"。工程上 99% 的网络服务选多路复用 + 非阻塞 fd, 因为它在并发和可控性之间最平衡;对极致吞吐或文件 IO,才上异步 IO。 把上面的时间线和对比表记住,再补一句"epoll 只解决等待,不解决拷贝",这一题就很稳。

笔记加载中…