文章

面试题学习笔记 | 操作系统 - IO 模型

I/O模型有哪些?

常见 I/O 模型有以下几种:

  • 同步阻塞 I/O(blocking I/O):调用 I/O 操作时,进程会被阻塞,直到数据准备好或者操作完成后才继续执行。适用于简单的应用场景,但会导致进程等待 I/O 期间无法执行其他任务,影响系统吞吐量

  • 同步非阻塞 I/O(non-blocking I/O):I/O 调用时不会阻塞进程,如果数据没有准备好,立即返回错误或状态,进程可以继续执行其他操作,并在适当的时候再次尝试读取或写入。通常需要轮询(polling)来不断检查 I/O 是否就绪,可能会浪费 CPU 资源

  • I/O 多路复用(I/O multiplexing):使用 selectpollepoll 等系统调用,允许程序同时等待多个 I/O 操作。当其中任意一个就绪时进行处理,而不必轮询所有的 I/O 资源,提高了效率。适用于需要处理大量连接的服务器,如 Web 服务器和代理服务器

  • 信号驱动 I/O(signal-driven I/O):进程向内核注册一个信号处理函数,并在 I/O 资源准备好时,内核发送 SIGIO 信号通知进程。进程在接收到信号后可以执行 I/O 操作,而不必主动轮询,减少了 CPU 资源的浪费,但需要正确处理信号的异步特性

  • 异步 I/O(asynchronous I/O):进程发起 I/O 请求后立即返回,内核在后台完成 I/O 操作,并在操作完成时通知进程。进程不需要等待 I/O 完成就可以继续执行其他任务,避免了轮询和阻塞,适用于高性能应用,但实现较复杂,Linux 中的 io_uring 就是现代异步 I/O 机制之一

Select、Poll、Epoll 之间有什么区别?

它们都是操作系统提供的多路复用 I/O 机制,用于同时监听多个文件描述符的状态变化,但在实现和性能上有所不同:

  • select

    • 使用固定大小的 fd_set(通常是 1024 个文件描述符)存储文件描述符集合,受 FD_SETSIZE 限制,无法处理更多的连接

    • 每次调用 select 时都需要重新初始化文件描述符集合,并遍历整个集合,导致性能较低

    • 适用于少量连接的场景,例如早期的 UNIX 网络编程

  • poll

    • 用链表结构存储文件描述符集合,因此没有 select 的最大连接数限制

    • 仍然需要遍历整个文件描述符集合,性能在高并发场景下仍然不理想

    • 适用于比 select 规模稍大的网络应用,但在超大规模并发场景下仍然效率低下

  • epoll

    • Linux 提供的高效 I/O 多路复用机制,采用事件驱动模型,不需要遍历整个文件描述符集合,而是基于事件触发处理已就绪的描述符

    • 采用 水平触发(LT)边缘触发(ET) 两种模式,其中 ET 模式减少了 I/O 事件的重复通知,提高了性能

    • 适用于高并发网络服务器,如 Nginx、Redis 等

为什么网络 I/O 会被阻塞?

网络 I/O 可能会被阻塞的原因主要包括以下几点:

  1. 等待数据到达或发送完成

    • 读取数据时,如果数据尚未到达,进程会被阻塞,直到数据可用

    • 发送数据时,如果发送缓冲区已满,进程会被阻塞,直到数据被成功发送到网络

  2. 系统资源限制

    • 如果系统文件描述符资源不足,进程可能会因为无法分配新的 socket 而被阻塞

    • 网络带宽或 CPU 资源不足时,数据的传输和处理可能会变慢,导致 I/O 操作等待更长时间

  3. 默认阻塞行为

    • 大多数系统调用默认是阻塞的,如 read()write()accept(),进程调用这些函数时会被挂起,直到 I/O 完成

    • 需要使用 fcntl(fd, F_SETFL, O_NONBLOCK) 将 socket 设置为非阻塞模式,否则默认情况下会出现阻塞情况

  4. TCP 的流量控制和拥塞控制

    • TCP 需要保证数据可靠传输,如果网络拥塞,TCP 可能会触发流量控制(如窗口大小调整),导致发送方的 I/O 操作被阻塞

    • 在慢启动(slow start)阶段,TCP 发送速率受限,数据传输速率较低,也可能影响 I/O 进程的执行

在高并发服务器开发中,通常会使用非阻塞 I/O 结合 I/O 多路复用(如 epoll)来避免阻塞,提高服务器吞吐量

License:  CC BY 4.0