【IO】零拷贝

133 阅读5分钟

“零”:指的是次数是0

“拷贝”:指的是将数据从一块存储区域拷贝到另一个存储区域

作为IO领域的一项技术,“零拷贝”并不是一次拷贝都不需要,而是尽可能的减少cpu介入的数据拷贝工作和减少用户态与内核态的切换。

一、一次普通的io过程

image.png 一次普通的io过程经过如下步骤

  1. 首先由用户程序发起读命令,读取硬件中的数据
  2. cpu由用户态切换至内核态
  3. cpu将任务交给dma,然后由dma将数据从硬件拷贝至内核缓冲区
  4. cpu将内核缓冲区内的数据拷贝至用户缓冲区
  5. cpu从内核态切换至用户态
  6. 用户程序发起写命令,将数据写入网卡或其他设备
  7. cpu从用户态切换至内核态
  8. cpu将数据从用户缓冲区拷贝至socket缓冲区
  9. cpu将任务交给dma,然后由dma将数据从socket缓冲区拷贝至网卡
  10. cpu从内核态切换至用户态io结束

上述过程中一共经历了4次cpu上下文切换,以及4次拷贝(2次cpu拷贝,2次dma拷贝)

DMA控制器

DMA控制器属于一种外设,使用专用的总线进行数据传输,传输过程中不需要cpu介入,减轻了cpu的工作,上述过程中将数据从硬件拷贝至缓冲区,以及从缓冲区拷贝至网卡。在零拷贝概念中的零指的是cpu拷贝不包括dma拷贝。

二、虚拟内存与物理内存

这里简单说一下这虚拟内存和物理内存,详细知识单独开章节

物理内存就是内存硬件内真是的物理存储区域,实际数据会在物理内存上进行存储拷贝。

虚拟内存则是一种内存管理的技术,如果应用程序直接使用物理内存,那么多个应用程序之间无法保证不会相互影响,并且空间利用率低。使用虚拟内存,相当于在物理内存之上建立了一份虚拟地址与物理地址的映射,并且通过分段、分页、段页等方式划分内存区域提高空间利用,而且程序使用虚拟内存时可以根据情况进行分配,可以共享内核空间、共享用户空间也可以使用专用的内存空间,即假设两个程序都申请了4GB内存,可能有1GB内存是共享的,那么7GB的物理内存就实现了8GB空间的效果。

三、如何实现零拷贝

在零拷贝的概念中需要尽可能减少数据拷贝的次数,这里利用到了虚拟内存物理内存映射关系的原理,一块物理内存区域可以被多个虚拟内存地址映射,换句话说就是假设用户缓冲区内核缓冲区虚拟地址映射到同一个物理地址上,那实际上物理上的存储区域是同一个,是不是就减少了内存间的拷贝次数,以及内核缓冲区socket缓冲区等地址映射也是同样的道理,这就是实现零拷贝的基础。

image.png

MMAP

函数原型

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:指定映射的虚拟内存地址\
length:映射的长度\
prot:映射内存的保护模式\
flags:指定映射的类型\
fd:进行映射的文件句柄\
offset:文件偏移量

image.png MMAP这个API实际上就是利用了虚拟内存和物理内存之间映射,将用户缓冲区与内核缓冲区做了地址映射,在上文的普通IO过程中经过了从内核缓冲区到用户缓冲区,再由用户缓冲区到socket缓冲区这两次cpu拷贝,当用户缓冲区与内核缓冲区映射到了同一块物理内存时,只需要直接从内核缓冲区往socket缓冲区就可以了,减少了一次CPU拷贝。

sendfile

函数原型

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
out_fd:为待写入内容的文件描述符\
in_fd:为待读出内容的文件描述符\
offset:文件偏移量\
count:指定在fdout和fdin之间传输的字节数

image.png MMAP这个API是对用户缓冲区与内核缓冲区做了映射,在物理上减少了一次拷贝的过程,其余的流程与普通的IO过程一致,需要经历4次内核态用户态之间的切换,那么在是不是可以把整个过程包起来直接告诉内核将数据从内核缓冲区拷贝至socket缓冲区,在内核中待读写的数据以文件描述符的形式存在,内核缓冲区相当于待读取的文件,而socket缓冲区相当于待写入的文件,sendfile这个API就是直接将两个文件描述符以及偏移量与待传输的数据量,告诉内核,让内核直接从内核缓冲区socket缓冲区拷贝数据,而不需要经过所谓的用户缓冲区,因此相对于MMAP,sendFile减少了两次CPU状态的切换。

sendfile + SG-DMA

image.png linux2.4之后对sendfile进行了优化,引入了SG-DMA技术,上文提到过DMA是一种帮助CPU分摊数据拷贝任务的外设,参与了从硬件到内核缓冲区拷贝数据,以及socket缓冲区到网卡拷贝数据的任务。sendfile并不是真正的零拷贝因为至少还是经历了一次从内核缓冲区到socket缓冲区的CPU拷贝,那谈论到可优化的地方,自然而然就会想到这一次CPU拷贝,既然DMA从socket缓冲区把数据拷贝到网卡,为什么CPU不直接告诉DMA从内核缓冲区把数据拷贝到网卡呢,而SG-DMA就是基于这个想法,直接从内核缓冲区把数据拷贝到网卡的技术,这样CPU就一次都不需要进行拷贝,因此实现了真正的零拷贝