零拷贝
零拷贝是指计算机执行 IO 操作时,CPU 不需要将数据从一个存储区域复制到另一个存储区域,从而可以减少上下文切换以及 CPU的拷贝时间。它是一种I/O 操作优化技术。
-
传统IO的执行流程:传统的 IO 流程,包括 read 读 和 write写 的过程。
- read:是数据从磁盘读取到内核缓冲区,再拷贝到用户缓冲区
- write:先是数据写入到 socket 缓冲区,最后写入网卡设备。
- 用户应用进程调用 read 函数,向操作系统发起 IO 调用,上下文从用户态转为内核态(切换 1)
- DMA 控制器将数据从磁盘中,读取到内核缓冲区。
- CPU 将内核缓冲区数据,拷贝到用户应用缓冲区,上下文从内核态转为用户态(切换 2),read 函数返回
- 用户应用进程通过 write 函数,发起 IO 调用,上下文从用户态转为内核态(切换3)
- CPU 将用户缓冲区中的数据,拷贝到 socket 缓冲区
- DMA 控制器将数据从 socket 缓冲区,拷贝到网卡设备,上下文从内核态切换回用户态(切换 4),write 函数返回
传统 IO 的读写流程,包括了 4 次上下文切换(4 次用户态和内核态的切换),4次数据拷贝(两次 CPU 拷贝以及两次 DMA 拷贝)。
-
零拷贝实现方式:零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及 CPU 拷贝的次数。零拷贝一般有这三种实现方式:
-
mmap+write:mmap 就是用了虚拟内存这个特点,它将内核中的读缓冲区与用户空间的缓冲区进行映射,以减少数据拷贝次数!
- 用户进程通过 mmap 方法向操作系统内核发起 IO 调用,上下文从用户态切换为内核态。
- CPU 利用DMA 控制器,将数据从硬盘中拷贝到内核缓冲区。
- 上下文从内核态切换回用户态,mmap 方法返回。
- 用户进程通过 write 方法向操作系统内核发起 IO 调用,上下文从用户态切换为内核态。
- CPU 将内核缓冲区的数据拷贝到 socket 缓冲区。
- CPU 利用DMA 控制器,将数据从 socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,write 调用返回。
mmap+write实现的零拷贝,I/O发生了4次用户空间与内核空间的上下文切换,以及3次数据拷贝(包括了2次DMA拷贝和1次CPU拷贝)。
-
sendfile:表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作
- 用户进程发起 sendfile 系统调用,上下文(切换 1)从用户态转向内核态
- DMA 控制器,将数据从硬盘中拷贝到内核缓冲区。
- CPU 将读缓冲区中数据拷贝到 socket 缓冲区
- DMA 控制器,异步将数据从 socket 缓冲区拷贝到网卡,
- 上下文(切换 2)从内核态切换回用户态,sendfile 调用返回。
sendfile 实现的零拷贝,I/O 发生了 2 次用户空间与内核空间的上下文切换,以及 3 次数据拷贝。其中 3 次数据拷贝中,包括了 2 次 DMA 拷贝和 1 次 CPU拷贝。
- **sendfile+DMA **(需要网卡支持):linux 2.4 版本之后,对 sendfile 做了优化升级,引入 SG-DMA 技术,其实就是对DMA 拷贝加入了 scatter/gather 操作,它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点搞零拷贝,即还可以多省去一次 CPU 拷贝。
-
- 用户进程发起 sendfile 系统调用,上下文(切换 1)从用户态转向内核态
- DMA 控制器,将数据从硬盘中拷贝到内核缓冲区。
- CPU 将内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)发送到socket 缓冲区
- DMA 控制器根据文件描述符信息,直接将数据从内核缓冲区拷贝到网卡
- 上下文(切换 2)从内核态切换回用户态,sendfile 调用返回。
sendfile+DMA scatter/gather 实现的零拷贝,I/O 发生了 2次用户空间与内核空间的上下文切换,以及 2 次数据拷贝。其中 2 次数据拷贝都是DMA 拷贝。这就是真正的 零拷贝(Zero-copy) 技术,全程都没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的