写点什么

你的程序为何卡顿?从 LINUX I/O 三大模式寻找答案

作者:poemyang
  • 2025-10-16
    湖南
  • 本文字数:1647 字

    阅读完需:约 5 分钟

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/Osocket = accept(); // 阻塞,直到新连接到达data = read(socket); // 阻塞,直到数据被读取process(data);
复制代码


非阻塞 I/O

非阻塞 I/O(Non-Blocking I/O)允许 I/O 操作在没有数据可用时立即返回,而不会阻塞执行线程。在非阻塞 I/O 模式下,如果数据未准备好,系统通常会返回一个错误码(如 EAGAIN 或 EWOULDBLOCK),指示操作需要稍后重试。进程可以通过轮询监控多个文件描述符的就绪状态。非阻塞 I/O 的优点是提高程序的并发性,因为它允许线程在等待 I/O 操作完成的同时,执行其他任务。然而,这种模式也带来了更高的编程复杂度,程序需要不断检查文件描述符的状态,以确保在数据可用时及时处理。这种轮询机制不仅增加了代码的复杂性,还可能导致处理器资源的浪费。



以下伪代码,展示了非阻塞 I/O 的执行过程。


// 伪代码: 非阻塞I/Owhile (true) {    data = read(socket);    if (data != EAGAIN) {        process(data);        break;    }    // do other things...}
复制代码


异步 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 的实现和调试复杂度较高,且在某些平台上的支持不够完善。



// 伪代码: 异步I/O// 定义一个I/O操作完成后的回调函数void on_read_complete(data, error) {    if (error) {        handle_error(error);    } else {        process(data);    }    // 可以在回调中发起下一次异步读    aio_read(socket, buffer, on_read_complete);}
// 1. 主程序发起异步读操作,并注册回调函数// aio_read会立即返回,不会阻塞aio_read(socket1, buffer1, on_read_complete);aio_read(socket2, buffer2, on_read_complete);
// 2. 主线程可以继续执行其他任务,或进入一个等待退出的循环do_other_work();event_loop_wait_for_shutdown(); // 例如,等待信号
复制代码


未完待续


很高兴与你相遇!如果你喜欢本文内容,记得关注哦

发布于: 刚刚阅读数: 5
用户头像

poemyang

关注

让世界知道我的存在 2018-03-05 加入

技术/人文, 互联网

评论

发布
暂无评论
你的程序为何卡顿?从LINUX I/O三大模式寻找答案_RPC_poemyang_InfoQ写作社区