Linux的五种IO模型

众所周知,出于对 OS 安全性的考虑,用户进程是不能直接操作 I/O 设备的。必须通过系统调用请求操作系统内核来协助完成 I/O 动作。

下图展示了 Linux I/O 的过程。
在这里插入图片描述
操作系统内核收到用户进程发起的请求后,从 I/O 设备读取数据到 kernel buffer 中,再将 buffer 中的数据拷贝到用户进程的地址空间,用户进程获取到数据后返回给客户端。

在 I/O 过程中,对于输入操作通常有两个不同的阶段:

  1. 等待数据准备好
  2. 将数据从内核缓冲区拷贝到用户进程

根据这两个阶段等待方式的不同,可以将 Linux I/O 分为 5 种模式:

  1. blocking I/O,阻塞式 I/O
  2. nonblocking I/O,非阻塞式 I/O
  3. I/O multiplexing(select and poll),I/O 多路复用
  4. signal driven I/O(SIGIO),信号驱动 I/O
  5. asynchronous I/O(the POSIX aio_functions),异步 I/O

对于 Socket 上的输入操作:

  1. 第 1 步通常是等待网络上的数据到达。当数据包到达时,它被复制到内核的缓冲区中。
  2. 第 2 步是从内核缓冲区复制数据到应用程序缓冲区。

下面详细介绍 Linux 中的 5 种 I/O 模式。
1. Blocking I/O

默认情况下,所有的 Socket 都是阻塞式的。下图展示了一个基于 UDP 的网络数据获取流程。

用户进程调用了 recvfrom 系统调用,此后一直处于等待状态,直到数据包到达并被拷贝到应用程序缓冲区,或者发生 error 才返回。整个过程从开始 recvfrom 调用到它返回一直处于阻塞状态。当 recvfrom 调用返回后,应用进程才能处理数据。
在这里插入图片描述
BIO的方式,每次客户端来一个连接,因为BIO是阻塞,所以服务端每次要创建一个线程去处理,主要是防止新的客户端连接被阻塞。

2. Nonblocking I/O

可以设置 Socket 为非阻塞模式。这种设置相当于告诉内核“当 I/O 操作时,如果请求是不可能完成的,不要把进程进入睡眠状态,返回一个错误即可“。下图展示了整个流程:在前三次调用 recvfrom 系统调用时,没有就绪的数据返回,所以内核立即返回 EWOULDBLOCK 错误。第四次调用 recvfrom 时,数据报已经准备好,它被复制到应用程序缓冲区中,然后 recvfrom 成功返回。最后应用进程对数据进行处理。当应用程序在一个非阻塞描述符上循环调用 recvfrom 系统调用时,这种方式也被称为轮询。应用程序不断轮询内核,以查看是否有某些操作准备好了。很明显,这通常会浪费 CPU 时间,但这种模式偶尔也会被使用。通常在专门用于一个功能的系统上使用。
在这里插入图片描述
由于BIO的阻塞,当连接数很多时,会创建很多线程,浪费资源,所以进行改进,将处理连接的方式改为非阻塞,出现了NIO,客户端轮询进行系统调用,向内核询问,是否准备好数据。数据未准备好返回一个BWOULDBLOCK标识,不会阻塞。

3. I/O Multiplexing

I/O 多路复用通常使用 select 或者 poll 或者 epoll 系统调用。这种方式下的阻塞只是被 select 或者 poll 或者 epoll 系统调用阻塞,而不会阻塞实际的 I/O 系统调用(即数据输入、输出不会被阻塞)。下图展示了整个过程。当调用 select 时,应用进程被阻塞。同时,系统内核会“监视”所有 select 负责的 Socket。只要其中有 1 个 Socket 的数据准备好了,select 调用就返回。然后调用 recvfrom 将数据报复制到应用程序缓冲区,最后返回给用户进程。

乍一看,这种方式和 blocking I/O 相比似乎更差,因为整个过程产生了 2 次系统调用,select 和 recvfrom。但是使用 select 的好处是可以同时等待多个描述符准备好。换句话说可以同时“聆听”多个 Socket 通道,同时处理多个连接。select 的优势不是对于单个连接处理得更快,而是能同时处理更多的连接。这和多线程阻塞式 I/O 有点类似。只不过后者是使用多个线程(每个文件描述符对应一个线程)来处理 I/O,每个线程都可以自由地调用阻塞式系统调用,比如 recvfrom。我们知道线程多了会带来上下文切换的开销,因此未必优于 select 方式。
在这里插入图片描述
Linux 内核将所有外部设备都当成一个个文件来操作。我们对文件的读写都通过调用内核提供的系统调用;内核给我们返回一个文件描述符(file descriptor)。而对一个 Socket 的读写也会有相应的描述符,称为 socketfd。应用进程对文件的读写通过对 fd 的读写完成。

由于NIO在内核准备数据阶段,客户端会轮询很多次无用的系统调用去询问内核是否准备好数据,并且每个客户端都会询问内核,都是单个的,为了改进去除不必要的操作,将NIO的主动询问和单个操作改为批量的,等待通知的方式,就是IO多路复用select、poll。客户端的多个连接会被注册到select或者poll中进行监听,当监听到read或write事件,则将内核注册的连接集合全部复制到用户空间,由用户空间遍历判断哪些连接需要处理。

每次select和poll都需要将注册管理的多个client连接从用户空间拷贝到内核,在管理百万连接时,拷贝操作会带来较大资源开销,影响性能,所以出现了epoll。epoll改进就是注册时将连接复制一次到内核,在系统调用时,不拷贝连接,在返回数据时,不拷贝全部连接,而是将就绪链表中的连接准确返回给用户进程。

4. Signal Driven I/O

信号驱动方式就是等数据准备好后,由内核发出 SIGIO 信号通知应用进程。示意图如下:
在这里插入图片描述
应用进程通过 sigaction 系统调用建立起 SIGIO 信号处理通道,然后此系统调用就返回,不阻塞。当数据准备好后,内核会产生一个 SIGIO 信号通知到应用进程。此时既可以使用 SIGIO 信号处理器通过 recvfrom 系统调用读取数据,然后通知应用进程数据准备好了,可以处理了;也可以直接通知应用进程读取数据。不管使用何种方式,好处都是应用进程不会阻塞,可以继续执行,只要等待信号通知数据准备好被处理了、数据准备好被读取了。

IO多路复用在等待数据准备时,客户端还是阻塞状态的,而信号驱动,则发起一次系统调用之后就返回,不会阻塞。

5. asynchronous I/O

异步 I/O 是由 POSIX 规范定义的。和信号驱动 I/O 模型的区别是前者内核告诉我们何时可以开始一个 I/O 操作,而后者内核会告诉我们一个 I/O 操作何时完成。示意图如下:
在这里插入图片描述

当用户进程发起系统调用后会立刻返回,并把所有的任务都交给内核去完成,不会被阻塞等待 I/O 完成。内核完成之后,只需返回一个信号告诉用户进程已经完成就可以了。

五种 I/O 模式可以从同步、异步,阻塞、非阻塞两个维度来划分:
在这里插入图片描述
在这里插入图片描述

select、poll、epoll的区别:

select
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取就绪的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

select的缺点

  1. 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大
  2. 同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大
  3. 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

poll
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。也就是说,poll只解决了上面的问题3,并没有解决问题1,2的性能开销问题。

epoll
epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll的边缘触发ET和水平触发LT模式的区别:
在这里插入图片描述


引用:https://zhuanlan.zhihu.com/p/543661648

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Linux IO 模型是指 Linux 操作系统中的 IO 处理机制。它的目的是解决多个程序同时使用 IO 设备时的资源竞争问题,以及提供一种高效的 IO 处理方式。 Linux IO 模型主要分为三种:阻塞 IO、非阻塞 IOIO 多路复用。 阻塞 IO 指的是当程序进行 IO 操作时,会被挂起直到 IO 操作完成,这种方式简单易用,但是对于高并发环境不太适用。 非阻塞 IO 指的是程序进行 IO 操作时,如果无法立即完成,会立即返回一个错误码,程序可以通过循环不断地进行 IO 操作来实现轮询的效果。非阻塞 IO 可以提高程序的响应速度,但是会增加程序的复杂度。 IO 多路复用指的是程序可以同时监听多个 IO 设备,一旦有 IO 事件发生,就会立即执行相应的操作。IO 多路复用可以提高程序的效率,但是需要程序员手动编写代码来实现。 Linux IO 模型还有其他的实现方式,比如信号驱动 IO 和异步 IO 等。但是这些方式的使用比较复杂,一般不常用。 ### 回答2: Linux中的IO模型是指操作系统在处理输入输出的过程中所遵循的一种方式。它主要包括阻塞IO、非阻塞IO、多路复用IO和异步IO四种模型。 阻塞IO是最简单的IO模型,当一个IO操作发生时,应用程序会被阻塞,直到IO操作完成才能继续执行。这种模型的特点是简单直接,但是当有多个IO操作时会造成线程的阻塞,影响系统的性能。 非阻塞IO是在阻塞IO的基础上发展而来的,应用程序在发起一个IO操作后可以继续执行其他任务,不必等待IO操作的完成。但是需要通过轮询来不断地检查IO操作是否完成,效率相对较低。 多路复用IO使用select、poll、epoll等系统调用来监听多个IO事件,当某个IO事件就绪时,应用程序才会进行读写操作,避免了前两种模型的效率问题。多路复用IO模型适用于连接数较多时的场景,如服务器的网络通信。 异步IO是最高效的IO模型,应用程序发起一个IO操作后,立即可以执行其他任务,不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序进行后续处理。异步IO模型常用于高吞吐量、低延迟的应用,如高性能服务器和数据库等。 总之,Linux IO模型提供了多种不同的方式来处理输入输出,每种模型都有其适用的场景和特点。选择合适的IO模型可以提高系统的性能和效率。 ### 回答3: Linux IO模型是指操作系统中用于处理输入输出操作的一种方法或机制。在Linux中,常见的IO模型有阻塞IO、非阻塞IOIO多路复用和异步IO。 阻塞IO是最基本的IO模型,当应用程序发起一个IO请求时,它将一直阻塞等待直到IO操作完成,期间无法做其他任务。虽然简单易用,但是对资源的利用不高。 非阻塞IO在发起一个IO请求后,不会阻塞等待IO操作完成,而是立即返回并继续做其他任务。应用程序需要不断地轮询IO操作状态,直到操作完成。由于需要不断轮询,对CPU的占用较高,但可以提高资源的利用率。 IO多路复用是通过一个线程同时监听多个IO事件,从而实现并发处理多个IO操作。在IO多路复用模型中,应用程序不需要进行轮询,而是通过调用select、poll或epoll等系统调用监听多个文件描述符的IO事件。这样可以在单个线程中处理多个IO操作,提高并发性能。 异步IO模型在发起一个IO请求后,应用程序不需要等待IO操作完成,而是继续做其他任务。当IO操作完成后,操作系统会通知应用程序。异步IO模型需要操作系统的支持,效率较高,但实现较为复杂。 通过选择合适的IO模型,可以根据不同的应用场景来提高IO操作的效率和性能。例如,对于需要同时处理大量连接的服务器应用,IO多路复用是一种常见的选择;而对于需要处理大量IO操作的高性能服务器,则可以考虑使用异步IO模型

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值