1.基础
在引入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去完成数据拷贝(比如网卡不支持DMA时,内核负责将数据从网卡拷贝到kernel buffer)。而DMA就释放了计算机的CPU,让它可以去处理其他任务,DMA也释放了从用户进程切换到内核的过程,从而避免了用户进程在这个拷贝阶段被阻塞。
再说kernel buffer和app buffer之间的复制方式,这是两段内存空间的数据传输,只能由内核占用CPU来完成拷贝。
所以,在加载硬盘数据到kernel buffer的过程是DMA拷贝方式,而从kernel buffer到app buffer的过程是CPU参与的拷贝方式。
(3).如果数据要通过TCP连接传输出去要怎么办?
例如,web服务对客户端的响应数据,需要通过TCP连接传输给客户端。
TCP/IP协议栈维护着两个缓冲区:send buffer和recv buffer,它们合称为socket buffer。需要通过TCP连接传输出去的数据,需要先复制到send buffer,再复制给网卡通过网络传输出去。如果通过TCP连接接收到数据,数据首先通过网卡进入recv buffer,再被复制到用户空间的app buffer。
同样,在数据复制到send buffer或从recv buffer复制到app buffer时,是内核占用CPU来完成的数据拷贝。从send buffer复制到网卡或从网卡复制到recv buffer时,是DMA方式的拷贝,这个阶段不需要切换到内核,也不需要计算机自身的CPU。
如下图所示,是通过TCP连接传输数据时的过程。
(4).网络数据一定要从kernel buffer复制到app buffer再复制到send buffer吗?
不是。如果进程不需要修改数据,就直接发送给TCP连接的另一端,可以不用从kernel buffer复制到app buffer,而是直接复制到send buffer。这就是零复制技术。
例如,如果httpd进程不需要访问和修改任何数据,那么将数据原原本本地复制到app buffer再原原本本地复制到send buffer然后传输出去的过程中,从kernel buffer复制到app buffer的过程是可以省略的。使用零复制技术,就可以减少一次拷贝过程,提升效率。
当然,实现零复制技术的方法有多种。
以下是以httpd进程处理文件类请求时比较完整的数据操作流程。
大致解释下:客户端发起对某个文件的请求,通过TCP连接,请求数据进入TCP的recv buffer,再通过recv()函数将数据读入到app buffer,此时httpd工作进程对数据进行一番解析,知道请求的是某个文件,于是发起read系统调用,于是内核加载该文件,数据从磁盘复制到kernel buffer再复制到app buffer,此时httpd就要开始构建响应数据了,可能会对数据进行一番修改,例如在响应首部中加一个字段,最后将修改或未修改的数据复制(例如send()函数)到send buffer中,再通过TCP连接传输给客户端。
需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK