1️⃣ I/O基础前言
在引入IO模型前,先对io等待时某一段数据的"流程"做一番解释。如图:
- 当某个程序或已存在的进程/线程(后文将不加区分的只认为是进程)需要某段数据时,它只能在用户空间中属于它自己的内存中访问、修改,这段内存暂且称之为
app buffer
。 - 假设需要的数据在磁盘上,那么进程首先得发起相关系统调用,通知内核去加载磁盘上的文件。但正常情况下,数据只能加载到内核的缓冲区,暂且称之为
kernel buffer
。 - 数据加载到
kernel buffer
之后,还需将数据复制到app buffer
。到了这里,进程就可以对数据进行访问、修改了。
📶 数据流程四问
🅿 1.为什么不能直接将数据加载到app buffer呢?
实际上是可以的,有些程序或者硬件为了提高效率和性能,可以实现内核旁路的功能,避过内核的参与,直接在存储设备和app buffer之间进行数据传输,例如RDMA技术就需要实现这样的内核旁路功能。
但是,最普通也是绝大多数的情况下,为了安全和稳定性
- 数据必须先拷入内核空间的kernel buffer
- 再复制到app buffer,以防止进程串进内核空间进行破坏
🅿 2.上面提到的数据几次拷贝过程,拷贝方式是一样的吗?
不一样。现在的存储设备(包括网卡)基本上都支持DMA
操作。
- DMA(direct memory access,),又叫直接内存访问,简单地说,就是内存和设备之间的数据交互可以直接传输,
- 不再需要计算机的CPU参与,而是通过硬件上的芯片(可以简单地认为是一个小cpu)进行控制。
假设,存储设备不支持DMA,那么数据在内存和存储设备之间的传输,必须通过计算机的CPU计算从哪个地址中获取数据、拷入到对方的哪些地址、拷入多少数据(多少个数据块、数据块在哪里)等等,仅仅完成一次数据传输,CPU都要做很多事情。而DMA就释放了计算机的CPU,让它可以去处理其他任务。
再说kernel buffer
和app buffer
之间的复制方式,这是两段内存空间的数据传输,只能由CPU来控制。
- DMA拷贝方式是加载硬盘数据到kernel buffer的过程
- CPU参与的拷贝方式是从kernel buffer到app buffer的过程
🅿 3.如果数据要通过TCP连接传输出去要怎么办?
例如,web服务对客户端的响应数据,需要通过TCP连接传输给客户端。
TCP/IP协议栈维护着两个缓冲区:send buffer
和recv buffer
,它们合称为socket buffer
。需要通过TCP连接传输出去的数据,需要先复制到send buffer
,再复制给网卡通过网络传输出去。如果通过TCP连接接收到数据,数据首先通过网卡进入recv buffer
,再被复制到用户空间的app buffer
。
同上述🅿 2
- 在数据复制到
send buffer
或从recv buffer
复制到app buffer
时,是CPU参与的拷贝。 - 从send buffer复制到网卡或从网卡复制到recv buffer时,是DMA操作方式的拷贝。
如下图所示,是通过TCP连接传输数据时的过程。
🅿 4.网络数据一定要从kernel buffer复制到app buffer再复制到send buffer吗?
不是。如果进程不需要修改数据,就直接发送给TCP连接的另一端,可以不用从kernel buffer
复制到app buffer
,而是直接复制到send buffer
。这就是零复制技术。
例如httpd不需要访问和修改任何信息时,将数据原原本本地复制到app buffer
再原原本本地复制到send buffer
然后传输出去,但实际上复制到app buffer
的过程是可以省略的。使用零复制技术,就可以减少一次拷贝过程,提升效率。
以下是以httpd进程处理文件类请求时比较完整的数据操作流程
流程解释如下:
- 客户端发起对某个文件的请求,通过TCP连接,请求数据进入TCP 的recv buffer,
- 再通过recv()函数将数据读入到app buffer,此时httpd工作进程对数据进行一番解析,知道请求的是某个文件,于是发起某个系统调用(例如要读取这个文件,发起read())
- 于是内核加载该文件,数据从磁盘复制到kernel buffer再复制到app buffer,
- 此时httpd就要开始构建响应数据了,可能会对数据进行一番修改,例如在响应首部中加一个字段,
- 最后将修改或未修改的数据复制(例如send()函数)到send buffer中,再通过TCP连接传输给客户端。
2️⃣ I/O机制简介
🅿 1.I/O概念简述
I/O 是 Input/Ouput 的缩写,即输入输出端口,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据
- IO模型,描述的是出现I/O等待时进程的状态以及处理数据的方式。
- 围绕着进程的状态、数据准备到kernel buffer再到app buffer的两个阶段展开。
- 其中数据复制到kernel buffer的过程称为数据准备阶段,
- 数据从kernel buffer复制到app buffer的过程称为数据复制阶段
🅿 2. IO系统的分层
▶ 1.三层结构解析四问
上图层次比较多,但总的就是三部分。磁盘 (存储)、 VM (卷管理)和文件系统 。专有名词不好理解,打个比方说:磁盘就相当于一块待用的空地; LVM 相当于空地上的围墙(把空地划分成多个部分);文件系统则相当于每块空地上建的楼房(决定了有多少房间、房屋编号如何,能容纳多少人住);而房子里面住的人,则相当于系统里面存的数据。
-
文件系统—数据如何存放?
- 对应了上图的
File System
和Buffer Cache
。 - File System (文件系统):解决了空间管理的问题 ,即:数据如何存放、读取。
- Buffer Cache :解决数据缓冲的问题。对读,进行 cache ,即:缓存经常要用到的数据;对写,进行buffer ,缓冲一定数据以后,一次性进行写入。
- 对应了上图的
-
VM —磁盘空间不足了怎么办?
- 对应上图的
Vol Mgmt
。 - VM 其实跟 IO 没有必然联系。他是处于文件系统和磁盘(存储)中间的一层。 VM 屏蔽了底层磁盘对上层文件系统的影响 。当没有 VM 的时候,文件系统直接使用存储上的地址空间,因此文件系统直接受限于物理硬盘,这时如果发生磁盘空间不足的情况,对应用而言将是一场噩梦,不得不新增硬盘,然后重新进行数据复制。而 VM 则可以实现动态扩展,而对文件系统没有影响。另外, VM 也可以把多个磁盘合并成一个磁盘,对文件系统呈现统一的地址空间,这个特性的杀伤力不言而喻。
- 对应上图的
-
存储—数据放在哪儿?如何访问?如何提高IO 速度?
- 对应上图的
Device Driver
、IO Channel
和Disk Device
- 数据最终会放在这里,因此,效率、数据安全、容灾是这里需要考虑的问题。而提高存储的性能,则可以直接提高物理 IO 的性能
- 对应上图的
▶ 2.Logical IO vs Physical IO
- 逻辑 IO 是操作系统发起的 IO ,这个数据可能会放在磁盘上,也可能会放在内存(文件系统的 Cache )里。
- 物理 IO 是设备驱动发起的 IO ,这个数据最终会落在磁盘上。
- 逻辑 IO 和物理 IO 不是一一对应的。
3️⃣ 系统I/O模型
📶 同步/异步:关注的是事件处理的消息通信机制,即在等待⼀件事情的处理结果时,被调⽤者是否提供完成通知。
- 同步:synchronous,调⽤者等待被调⽤者返回消息后才能继续执⾏,如果被调⽤者不提供消息返回则为同步,同
步需要调⽤者主动询问事情是否处理完成。 - 异步:asynchronous,被调⽤者通过状态、通知或回调机制主动通知调⽤者被调⽤者的运⾏状态
- 同步:进程发出请求调⽤后,等内核返回响应以后才继续下⼀个请求,即如果内核⼀直不返回数据,那么进程就⼀直等。
- 异步:进程发出请求调⽤后,不等内核返回响应,接着处理下⼀个请求,Nginx是异步的。
📶 阻塞/⾮阻塞:关注调⽤者在等待结果返回之前所处的状态
- 阻塞:
blocking
,指IO操作需要彻底完成后才返回到⽤⼾空间,调⽤结果返回之前,调⽤者被挂起,⼲不了别的事情。 - ⾮阻塞:
nonblocking
,指IO操作被调⽤后⽴即返回给⽤⼾⼀个状态值,⽆需等到IO操作彻底完成,最终的调⽤结果返回之前,调⽤者不会被挂起,可以去做别的事情。
4️⃣ 网络I/O模型
分类:阻塞型、非阻塞型、复用型、信号驱动型、异步型
🅿 1.同步阻塞型IO模型(blocking IO)
从应用程序开始系统调用->数据就绪进行拷贝->拷贝结束,这之间应用程序都处于等待状态,不能做其它事情,直到将数据拷贝到用户空间或出错才返回,我们称之为阻塞 I/O 模式。
📶 blocking IO的特点就是在IO执行的下两个阶段的时候都被block了。
- 等待数据准备就绪 (Waiting for the data to be ready)
「阻塞」
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
「阻塞」
重点解释下上图,下面例子都会讲到。 - 首先application调用 recvfrom()转入kernel
- 注意kernel有2个过程,wait for data 和 copy data from kernel to user。
- 直到最后copy complete后,recvfrom()才返回。此过程一直是阻塞的
🅿 2.同步非阻塞型I/O模型(nonblocking IO)
- 用户线程需要不断地发起I/O请求,直到数据到达后,才真正读取到数据,继续执行。即**“轮询”机制**
📶 non blocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有:
- 等待数据准备就绪 (Waiting for the data to be ready)
「非阻塞」
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
「阻塞」
比较浪费CPU的方式,一般很少直接使用这种模型,而是在其他 I/O 模型中使用非阻塞 I/O 这一特性。这种方式对单个 I/O 请求意义不大,但给 I/O 多路复用铺平了道路.
🅿 3.IO多路复⽤型(IO multiplexing)
IO multiplexing
就是我们说的select
,poll
,epoll
,有些地⽅也称这种IO⽅式为event driven IO。- select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。
- 它的基本原理就是select,poll,epoll这些个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
- I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。
这个图和blocking IO的图其实并没有太大的不同,事实上因为IO多路复用多了添加监视 socket,以及调用 select 函数的额外操作,效率更差。还更差一些。因为这里需要使用两个system call (select 和 recvfrom)
,而blocking IO只调用了一个system call (recvfrom
)。但是,但是,使用 select
以后最大的优势是用户可以在一个线程内同时处理多个 socket 的 I/O 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 I/O 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
- 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
📶 IO多路复用模型特点
- 等待数据准备就绪 (Waiting for the data to be ready)
「阻塞」
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
「阻塞」
🅿 4.信号驱动式IO(signal-driven IO)
- 允许 socket 进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。
- 当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
- 该模型并不常用
🅿 5.异步(非阻塞) IO(asynchronous IO)
- 相对于同步IO,异步IO不是顺序执⾏。
- ⽤⼾进程进⾏
aio_read
系统调⽤之后,⽆论内核数据是否准备好,都会直接返回给⽤⼾进程,然后⽤⼾态进程可以去做别的事情。 - 另一方面,从kernel的角度,当它发现一个
asynchronous read
之后,首先它会立刻返回,所以不会对用户进程产生任何block
- 然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个
signal
,告诉它read
操作完成了 - 实现较⼤的同时并实现较⾼的IO复⽤,因此异步⾮阻塞使⽤最多的⼀种通信⽅式,nginx是异步⾮阻塞。
- 异步 I/O 模型使用了
Proactor
设计模式实现了这一机制。
📶 因此对异步IO模型特点
- 等待数据准备就绪 (Waiting for the data to be ready)
「非阻塞」
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
「非阻塞」
5️⃣ IO对比
blocking和non-blocking的区别
- 调用blocking IO会一直block住对应的进程直到操作完成,会block2个阶段
- 而non-blocking IO在kernel还准备数据的情况下会立刻返回,只会block第二个阶段
synchronous IO和asynchronous IO的区别
在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:
-
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
-
An asynchronous I/O operation does not cause the requesting process to be blocked;
-
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO
有人会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。
而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。
📶 因此我们会得出下面的分类:
-
同步IO (synchronous IO)
- blocking IO model
- non-blocking IO model
- IO multiplexing model
-
异步IO (asynchronous IO)
- asynchronous IO model
- asynchronous IO model
-
可以看出,越往后,阻塞越少,理论上效率也是最优。
6️⃣ 剧场版风格讲解 I/O 模型的演进
故事标题:小明与妹子的邂逅
故事情节:小明在校园一次文艺晚会上邂逅了一位妹子,在只得知妹子名字、手机号的情况下,经过几天的苦苦追寻,历经千山万水,终得美人归!
演员介绍:男一号@小明、女一号@妹子、串场@门卫大爷
-
第一幕:同步阻塞 I/O 模式
小明电话相约妹子在校门口,然后小明很专一、不见到妹子不回家,期间没有做任何事情,一直在等待! -
第二幕:同步非阻塞 I/O 模式
小明电话相约妹子在校门口,妹子还没准备好(出门前化妆几小时。。。),这时候的小明很执着,每隔一会儿给妹子发个信息直到妹子准备好了。 -
第三幕:I/O 多路复用模式
- select 小明电话相约妹子在校门口,委托门卫select大爷帮忙,select大爷很敬业每出去一个人都会进行询问,但是select大爷有个限制最多只能询问1024个。
- poll poll类似于select功能,不同的是poll大爷没有1024限制,可以一直坚持,但是当poll大爷超过1024,询问的越来越多之后就显得越来越精疲力尽了。
- epoll 小明电话相约妹子在校门口,委托门卫epoll大爷帮忙,epoll大爷不在是每个询问,规定每个人出入校门必须带上学生证,这样opoll大爷就是知道哪个是小明的女神了,epoll大爷找到女神之后在电话通知小明。
-
第四幕:信号驱动 I/O 模式
小明电话相约妹子在校门口,此时妹子回复说我还没准备好(出门前化妆几小时。。。),这个时候小明也没去,而是先去干其它事情了,等妹子准备好之后电话通知小明,我已经准备好了,小明这个时候才去校门口等着和妹子的约会。 -
第五幕:异步 I/O 模式
小明告诉妹子我们在校园门口相约,之后小明没有在那干等了,而是先回宿舍休息会或者和朋友在打会球等等,妹子到校门口之后电话通知小明,我已经来啦。