Unix/Linux下5种I/O模型

本文介绍了五种I/O模型,包括阻塞I/O、非阻塞I/O、多路复用I/O、信号驱动I/O及异步I/O。详细解释了每种模型的特点及其在网络编程中的应用。

1. 五种I/O模型

在网络编程,经常接触下述的I/O相关的概念:

  • 同步(Synchronous)
  • 异步(Asynchronous)
  • 阻塞(Blocking)
  • 非阻塞(Non-blocking)
  • 信号驱动(Signal driven)

他们之间的关系如下图:
io模型

引用网上一个比喻:

你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果。如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

2. 阻塞I/O模型

阻塞I/O模型是最常见的I/O模型了,对于所有的“慢速设备”(socket、pipe、fifo、terminal)的I/O默认的方式都是阻塞的方式。阻塞就是进程放弃cpu,让给其他进程使用cpu。进程阻塞最显著的表现就是“进程睡眠了”。阻塞的时间通常取决于“数据”是否到来。


这里写图片描述

3. 非阻塞I/O模型

非阻塞IO就是设置IO相关的系统调用为non-blocaking,随后进行的IO操作无论有没有可用数据都会立即返回,并设置errno为EWOULDBLOCK或者EAGAIN。我们可以通过主动check的方式(polling,轮询)确保IO有效时,随之进行相关的IO操作。
这种方式有一个很大的缺点就是浪费太多的CPU时间用在做轮询上。


这里写图片描述

4. 多路复用I/O模型

多路复用是让阻塞发生在我们的多路复用IO操作的系统调用上面,而不是我们真正去执行IO的系统调用。使用这个方式的好处就是可以同时监控多个用于IO的文件描述符。


这里写图片描述

5. 信号驱动I/O模型

所谓信号驱动,就是利用信号机制,安装信号SIGIO的处理函数(进行IO相关操作),通过监控文件描述符,当其就绪时,通知目标进程进行IO操作(signal handler)。


这里写图片描述

6. 异步I/O模型


这里写图片描述

### 三级标题:UNIX/Linux系统下的四种主要IO模型及其特点 UNIX/Linux系统下常见的IO模型包括阻塞式IO、非阻塞式IOIO复用、信号驱动IO以及异步IO。其中,前四种是最主要的模型,它们在处理输入输出操作时具有不同的机制和适用场景。 #### 阻塞式IO(Blocking I/O) 这是最传统的IO模型,在这种模型中,应用程序发起一个系统调用后会一直等待,直到数据准备完成并被复制到用户空间才会返回。在此期间,进程或线程会被挂起,无法执行其他任务。这种方式简单直接,但在高并发场景下效率较低,因为每个连接都需要一个独立的线程或进程来处理[^1]。 #### 非阻塞式IO(Non-blocking I/O) 在这种模型中,如果请求的数据尚未准备好,系统调用不会阻塞,而是立即返回错误。应用程序需要不断轮询检查数据是否就绪,这可能会浪费CPU资源,但避免了进程被长时间挂起的问题。非阻塞IO适用于对延迟要求较高、但并发连接数不大的场景[^1]。 #### IO复用(I/O Multiplexing) IO复用允许单个线程同时监控多个文件描述符的状态变化,常用的实现方式有`select`、`poll`和`epoll`。当任何一个文件描述符准备好进行IO操作时,系统会通知应用程序。这种模型特别适合处理大量并发连接,因为它可以避免为每个连接创建单独的线程或进程,从而节省系统资源。例如,使用`epoll`可以高效地管理成千上万的连接[^2]。 ```c #include <sys/epoll.h> int epoll_fd = epoll_create(1024); struct epoll_event event; event.events = EPOLLIN; // 监听读事件 event.data.fd = sockfd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &event); struct epoll_event events[10]; int num_events = epoll_wait(epoll_fd, events, 10, -1); for (int i = 0; i < num_events; ++i) { if (events[i].data.fd == sockfd) { // 处理新连接 } } ``` #### 信号驱动IO(Signal-driven I/O) 该模型通过注册信号处理函数来异步通知应用程序数据已经就绪。当数据到达时,内核会发送一个信号(如SIGIO),应用程序可以在信号处理程序中进行相应的IO操作。这种方法减少了等待时间,但信号处理机制较为复杂,且可能带来一定的系统开销[^1]。 #### 异步IO(Asynchronous I/O) 异步IO是POSIX标准的一部分,它允许应用程序发起一个异步读写操作,并在操作完成后收到通知。与信号驱动IO不同的是,异步IO的数据复制过程也是在后台完成的,应用程序不需要主动去读取或写入数据。这种方式非常适合高性能服务器应用,因为它能够实现完全的非阻塞操作[^1]。 ```c #include <aio.h> struct aiocb aio; memset(&aio, 0, sizeof(aio)); aio.aio_fildes = fd; aio.aio_offset = 0; aio.aio_buf = buffer; aio.aio_nbytes = BUFFER_SIZE; aio_read(&aio); // 等待异步操作完成 while (aio_error(&aio) == EINPROGRESS) { // 可以做其他事情 } ssize_t bytes_read = aio_return(&aio); ``` ### 总结 - 阻塞式IO简单但效率低,适用于低并发场景。 - 非阻塞式IO避免了进程挂起,但需要频繁轮询。 - IO复用模型通过单一线程管理多个连接,提高了并发性能。 - 信号驱动IO提供了一种异步通知机制,但实现复杂。 - 异步IO提供了最彻底的非阻塞解决方案,适合高性能需求的应用。 这些IO模型各有优劣,选择合适的模型取决于具体的应用场景和性能需求。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空空的司马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值