当前位置: 首页 > news >正文

Linux五种I/O模型

什么是I/O

I/O = Input/Output,输入/输出。本质上就是:数据的流动

  • 输入:数据从外部设备 → 进入内存(比如读文件、收网络包、键盘输入)
  • 输出:数据从内存 → 到外部设备(比如写文件、发网络包、屏幕显示)

常见的I/O设备

设备输入输出
磁盘读文件写文件
网卡接收数据包发送数据包
键盘按键输入-
显示器-显示内容
鼠标点击/移动-

I/O在计算机中的位置

┌─────────────────────────────────────┐ │ 应用程序 │ │ (用户空间) │ └─────────────┬───────────────────────┘ │ 系统调用 ↓ ┌─────────────────────────────────────┐ │ 操作系统内核 │ │ (内核空间) │ │ ┌─────────────────────────┐ │ │ │ I/O子系统 │ │ │ │ (文件系统、网络协议栈) │ │ │ └──────────┬──────────────┘ │ └───────────────┼─────────────────────┘ │ 设备驱动 ↓ ┌─────────────────────────────────────┐ │ 硬件设备 │ │ (磁盘、网卡、显卡...) │ └─────────────────────────────────────┘

为什么I/O慢?

I/O是计算机中最慢的操作,原因:

操作速度量级对比
CPU指令纳秒级🚀🚀🚀
内存访问~100ns🚀🚀
SSD读取~100μs🚀
机械硬盘~10ms🐢
网络传输1-100ms+🐌🐌🐌

CPU太快,I/O太慢,差距在10⁵-10⁸倍

所以I/O模型的核心问题就是:怎么让CPU不等I/O?

I/O的两个核心动作

对于一次读操作:

1. 等待数据准备好

磁盘/网卡 → 内核缓冲区

2. 拷贝数据

内核缓冲区 → 用户缓冲区(应用程序内存)

所有I/O模型的差异,都在于如何处理这两个阶段

内核态 vs 用户态

这是理解I/O的关键:

  • 用户态:应用程序运行的权限级别,不能直接访问硬件
  • 内核态:操作系统运行的权限级别,可以访问一切

一次I/O必定涉及用户态↔内核态切换

切换开销:

  • 保存/恢复寄存器
  • 切换页表
  • 刷新缓存

频繁的系统调用是性能杀手

缓冲区的作用

内核缓冲区:操作系统维护的缓冲区

用户缓冲区:应用程序自己的缓冲区

写入流程:

用户缓冲区 → 内核缓冲区 → 磁盘/网卡

读取流程:

磁盘/网卡 → 内核缓冲区 → 用户缓冲区

为什么有缓冲区?

  • 减少实际I/O次数(合并小请求)
  • 解耦速度差异(生产者-消费者模式)

零拷贝技术

传统I/O需要多次拷贝:

磁盘 → 内核缓冲区 → 用户缓冲区 → 内核缓冲区 → 网卡 (DMA拷贝) (CPU拷贝) (CPU拷贝) (DMA拷贝)

零拷贝技术(sendfile、mmap)可以减少拷贝次数:

磁盘 → 内核缓冲区 → 网卡 (DMA拷贝) (DMA拷贝)

应用场景:Kafka、Nginx等高性能服务器

概念核心理解
I/O本质数据在内存与外部设备间流动
I/O之痛速度慢,CPU要等
I/O模型解决"怎么等"的问题
核心矛盾CPU太快,I/O太慢
优化方向减少等待、减少拷贝、减少切换

Linux的五种I/O模型是理解网络编程和高并发服务器的核心。

对于一次I/O操作(比如read),会经历两个阶段:

  1. 等待数据准备:数据从网卡/磁盘读取到内核缓冲区
  2. 将数据从内核拷贝到用户空间:从内核缓冲区拷贝到应用程序缓冲区

五种I/O模型的区别主要在这两个阶段如何处理。

1. 阻塞I/O(Blocking I/O)

最简单的模型,默认行为。

应用进程调用recvfrom ↓ 阻塞等待... ↓ 数据准备好 ↓ 内核拷贝数据到用户空间 ↓ 返回成功

特点:

  • 调用后进程挂起,直到数据准备好并拷贝完成
  • 简单直观,代码容易理解
  • 缺点:一个线程只能处理一个连接,效率极低

典型场景:简单的客户端程序

2. 非阻塞I/O(Non-blocking I/O)

不断轮询,不等待。

应用进程调用recvfrom ↓ 数据未准备好 → 立即返回EAGAIN错误 ↓ 再次调用recvfrom ↓ 再次返回EAGAIN... ↓ (循环轮询) ↓ 数据准备好了 → 拷贝数据 → 返回成功

特点:

  • 设置socket为NONBLOCK,调用立即返回
  • 数据未准备好返回错误(EAGAIN/EWOULDBLOCK)
  • 缺点:CPU空转严重,轮询消耗大量资源

典型场景:很少单独使用,通常配合I/O多路复用

3. I/O多路复用

用select/poll/epoll同时监控多个连接

多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select,,select会监听所有注册进来的IO。

如果select监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可读数据时,select调用就会返回;而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
Linux中IO复用的实现方式主要有Select,Poll和Epoll:

Select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_ SIZE(1024)。
Poll:原理和Select相似,没有数量限制,但IO数量大,扫描线性性能下降。
Epoll :事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6后内核支持。

应用进程调用select/epoll_wait ↓ 阻塞等待任意一个socket可读 ↓ 有socket可读了 ↓ 返回就绪的socket列表 ↓ 对就绪socket调用recvfrom ↓ 内核拷贝数据到用户空间 ↓ 返回成功

特点:

  • 一个线程可以监控多个文件描述符
  • select/poll/epoll是系统调用,本身也会阻塞
  • 优点:单线程处理大量连接,性能好
  • 缺点:需要两次系统调用(select + recvfrom)

三种实现对比:

  • select:有fd数量限制(1024),O(n)遍历
  • poll:无数量限制,仍需O(n)遍历
  • epoll:事件驱动,只返回就绪的fd,O(1)

典型场景:Nginx、Redis、Netty等高性能服务器

4. 信号驱动I/O(Signal-driven I/O)

数据准备好时发信号通知。

应用进程建立SIGIO信号处理函数 ↓ 调用sigaction注册信号 ↓ 进程继续执行其他任务... ↓ 数据准备好 → 内核发送SIGIO信号 ↓ 信号处理函数中调用recvfrom ↓ 内核拷贝数据到用户空间

特点:

  • 等待数据阶段不阻塞
  • 数据准备好后通过信号通知
  • 缺点:信号处理复杂,TCP场景信号过多,实际很少使用

典型场景:UDP应用较多,TCP很少用

5. 异步I/O(Asynchronous I/O)

真正的异步,两个阶段都不阻塞。

应用进程调用aio_read ↓ 立即返回,进程继续执行 ↓ (同时)内核等待数据 + 拷贝数据 ↓ 全部完成后,通知应用进程

特点:

  • 两个阶段都不阻塞
  • 应用进程只需发起请求,完成后会被通知
  • 优点:真正的异步,性能最优
  • 缺点:Linux的aio支持长期不完善,编程模型复杂

典型场景:Windows的IOCP用得比较多,Linux上用较少

五种模型对比

模型第一阶段(等待数据)第二阶段(拷贝数据)特点
阻塞I/O阻塞阻塞简单但低效
非阻塞I/O非阻塞(轮询)阻塞CPU空转
I/O多路复用阻塞(在select上)阻塞高并发首选
信号驱动I/O非阻塞(信号通知)阻塞TCP下较少用
异步I/O非阻塞非阻塞真正异步

同步 vs 异步

  • 同步I/O:导致请求进程阻塞,直到I/O操作完成(前四种都是同步)
  • 异步I/O:不导致请求进程阻塞(只有AIO是真正的异步)

实际开发中,I/O多路复用(epoll)是Linux高性能服务器的标准选择,Redis、Nginx都用这个。阻塞I/O适合简单场景,异步I/O理论上最优但Linux支持不够成熟。

http://www.cnnetsun.cn/news/1874607.html

相关文章:

  • 手把手调试RH850G3KH中断控制器:INTC1/INTC2寄存器配置避坑手册
  • 低成本DIY家庭监控:基于ESP32-CAM和OV2640的无线视频流方案实战
  • SITS2026闭门报告泄露:下一代Agent架构已锁定“语义内核+动态插槽”范式,5家头部企业正联合验证,你的团队还停留在Prompt Engineering?
  • HC-SR501人体感应模块的5个隐藏功能:90%的人不知道的调节技巧
  • Spring Boot 注解求生指南
  • 3个瑜伽姿势缓解背痛,办公室久坐族必备
  • 别再吹牛了,% Vibe Coding 存在无法自洽的逻辑漏洞!松
  • 2026山东大学软件学院创新实训——IntelliHealth(一)
  • 从Focal Loss到ASL:深入聊聊多标签分类损失函数的‘进化史’与调参心得
  • 使用Dify平台部署Qwen3-TTS-12Hz-1.7B-CustomVoice模型服务
  • 终极指南:Porcupine如何通过离线语音检测保护你的隐私安全
  • 如何将HRNet.pytorch集成到你的应用中:Webcam、视频和图像处理完整指南
  • 7个实用技巧!Deep High-Resolution Net.pytorch损失函数与训练策略完全解析
  • Undotree完全配置手册:20个实用技巧让你的Vim撤销更高效
  • 如何扩展Intern:自定义报告器、执行器和插件开发终极指南
  • 组件-RocketMQ
  • Python爬虫零基础入门:30分钟爬取软科中国大学排名,新手复制粘贴就能跑
  • Apache Lucene-Solr终极指南:为什么它是企业级搜索的首选解决方案
  • mysql8之单次查询结果太大
  • Windows 10环境下Sentinel的快速部署指南
  • 如何用jsPDF-AutoTable从HTML表格一键生成PDF文档
  • 如何实现RE2正则表达式引擎的优雅错误恢复:编译失败时的降级策略
  • Windows-Hacks快速入门:如何在5分钟内运行你的第一个桌面特效
  • 解锁Visio泳道图标题布局:从默认到自定义的文字方向调整
  • Oniguruma 快速上手:5分钟构建你的第一个正则表达式程序
  • 2026届必备的十大AI论文平台推荐
  • 如何免费使用draw.io桌面版:离线安全绘图终极指南
  • 封面设计:提升内容吸引力的核心逻辑与实用方法
  • 从EMI到电源噪声:用PowerSI做谐振分析时90%人会忽略的3个设置
  • Helpy社区贡献指南:参与开源项目开发与本地化翻译