你的程序为何卡顿?从 LINUX I/O 三大模式寻找答案
I/O 交互流程
在 LINUX 中,内核空间和用户空间都位于虚拟内存中。LINUX 采用两级保护机制:0 级供内核使用,3 级供用户程序使用。每个进程都有独立的用户空间(0~3G),对其他进程不可见,而最高的 1G 虚拟内核空间则由所有进程和内核共享。操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。由于 LINUX 使用虚拟内存机制,两者之间不能直接通过指针传递数据。用户空间必须通过系统调用请求内核协助完成 I/O 操作。内核会为每个 I/O 设备维护缓冲区,而用户空间的数据可能被换出,因此内核无法直接使用用户空间的指针。对于一个输入操作,进程发起 I/O 系统调用后,内核会先检查缓冲区是否有缓存数据。如果没有,则从设备读取数据;如果有,则直接将数据复制到用户空间。因此,网络输入操作通常分为两个阶段:1)内核空间阶段:内核通过协议栈和设备驱动程序接收数据,并将其存储在内核缓冲区;2)用户空间阶段:数据从内核缓冲区复制到用户进程的缓冲区后,用户进程即可处理这些数据。
I/O 操作方式
在操作系统中,通常有三种主要的 I/O 操作方式,每种方式都有其独特的特性和适用场景。
阻塞 I/O
阻塞 I/O(Blocking I/O)是最简单的 I/O 模型。当进程发起 I/O 操作(如 read 或 write)时,当前线程会被阻塞,直到 I/O 操作完成。这种模型是标准的同步 I/O 实现,例如 POSIX 标准中的默认 read 和 write 系统调用。阻塞 I/O 的优点是实现简单,适合低并发的场景,因为内核已经对这些系统调用进行了高度优化。然而,在并发场景下,阻塞 I/O 的性能瓶颈会显现出来:每个 I/O 操作都会阻塞一个线程,导致内核需要频繁地进行线程切换,这会增加上下文切换的开销,降低处理器缓存的利用率,并可能使依赖线程本地存储(Thread-Local Storage, TLS)的代码性能下降。
非阻塞 I/O
非阻塞 I/O(Non-Blocking I/O)允许 I/O 操作在没有数据可用时立即返回,而不会阻塞执行线程。在非阻塞 I/O 模式下,如果数据未准备好,系统通常会返回一个错误码(如 EAGAIN 或 EWOULDBLOCK),指示操作需要稍后重试。进程可以通过轮询监控多个文件描述符的就绪状态。非阻塞 I/O 的优点是提高程序的并发性,因为它允许线程在等待 I/O 操作完成的同时,执行其他任务。然而,这种模式也带来了更高的编程复杂度,程序需要不断检查文件描述符的状态,以确保在数据可用时及时处理。这种轮询机制不仅增加了代码的复杂性,还可能导致处理器资源的浪费。
以下伪代码,展示了非阻塞 I/O 的执行过程。
异步 I/O
异步 I/O (Asynchronous I/O)是一种真正的异步模型,进程在发起 I/O 操作后立即返回,并通过回调函数或事件通知机制在操作完成后得到通知。典型的实现包括 Windows 的 OVERLAPPED 和 I/O 完成端口(IOCP),以及 LINUX 的原生异步 I/O(AIO)。需要注意的是,LINUX 的原生 AIO 仅对文件 I/O 有效,对网络 I/O 的支持有限。异步 I/O 的优点是能够最大限度地提高并发性能,同时减少线程阻塞和上下文切换的开销。然而,异步 I/O 的实现和调试复杂度较高,且在某些平台上的支持不够完善。
未完待续
很高兴与你相遇!如果你喜欢本文内容,记得关注哦
版权声明: 本文为 InfoQ 作者【poemyang】的原创文章。
原文链接:【http://xie.infoq.cn/article/55ad6aff38b0ba63f70a53d9b】。文章转载请联系作者。







评论