目录
实现 零拷贝用到的最主要技术是 DMA 数据传输技术 和 内存区域映射技术(虚拟内存)。
什么是虚拟内存
待补充。。。
什么是DMA
待补充。。。
由于CPU和I/O速度差异问题产生了DMA技术,通过DMA搬运减少了CPU的等待时间。
传统的 I/O 方式
传统的 I/O 方式是通过read()和write()函数实现,通过read()将数据从硬盘读取到内核缓冲区,然后再复制到用户缓冲区,然后再通过write写入到socket缓冲区,最后写入到网卡设备,整个过程发生了4次用户态和内核态的上下文切换和四次拷贝。
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
流程如下:
第一步,用户进程通过read方法向操作系统发起调用,上下文从用户态切换为内核态;
第二步,DMA控制器把数据从硬盘拷贝到内核读缓冲区;
第三布,CPU把数据从内核缓冲区拷贝到用户缓冲区,上下文从内核态切换为用户态,read返回;
第四步,用户进程通过write方法向操作系统发起系统调用,上下文从用户态切换为内核态;
第五步,CPU将用户缓冲区中的数据拷贝到socket缓冲区;
第六步,DMA控制器将socket缓冲区中的数据拷贝到网卡,上下文从内核态切换为用户态,write返回。
传统I/O方式缺点
一次简单的I/O发生了四次上线文切换和4次数据拷贝,这在高并发场景下无疑会对性能产生极大的影响。
零拷贝
零拷贝是指计算机在执行操作的时候,CPU不需要将数据从某处内存复制到特定的区域,这种技术通常用于网络传输文件时节省CPU周期和内存带宽。对于零拷贝而言,并非是真的没有数据拷贝的过程,只不过是减少了用户态和内核态的切换次数以及CPU的拷贝次数。
关于零拷贝主要技术有 mmap+write和
sendfile
等几种方式。
mmap+write
mmap+write简单来说就是使用mmap替代原有的read+write中的read操作,减少了一次CPU拷贝,mmap的实现方式是将内核读缓冲区中的地址和用户缓冲区中的地址进行映射,内核缓冲区和用户缓冲区共享从而减少一次从读缓冲区到用户缓冲区的一次CPU拷贝,整个过程发生了四次用户态和内核态的上下文切换和三次数据拷贝。
流程如下:
第一步,用户进程通过mmap方法向操作系统发起系统调用,上下文从用户态切换为内核态;
第二步,DMA控制器将数据从硬盘拷贝到内核读缓冲区;
第三步,上下文从内核态切换为用户态,mmap返回;
第四步,用户进程通过write方法向操作系统发起系统调用,上下文从用户态切换为内核态;
第五步,CPU将读缓冲区中的数据拷贝到socket缓冲区;
第六步,DMA控制器将socket缓冲区中的数据拷贝到网卡,上下文从内核态切换为用户态,write返回。
从这里我们可以看到mmap的方式节省了一次CPU的数据拷贝,同时用于用户进程中的内存是虚拟的,只是映射到内核的读缓冲区,所以可以节省一半的内存空间,比较适合大文件的传输。
sendfile
从 Linux 2.1 版本开始,Linux 引入了 sendfile
来简化操作。sendfile
方式可以替换上面的mmap+write
方式来进一步优化。
通过sedfile()数据可以直接在内核空间进行传输,因此避免了用户空间和内核空间的数据拷贝,同时用于sendfile()替换了read()+write(),从而节省了一次系统调用,也就是两次用户态和内核态的上下文切换。整个过程发生了两次用户态和内核态的上下文切换和三次数据拷贝。
流程如下:
第一步,用户进程通过sendfile方法向操作系统发起系统调用,上下文从用户态切换为内核态;
第二步,DMA控制器将数据从硬盘拷贝到内核读缓冲区;
第三步,CPU将读缓冲区中的数据拷贝到socket缓冲区;
第四步,DMA控制器将socket缓冲区中的数据拷贝到网卡,上下文从内核态切换为用户态,sendfile返回。
sendfile方式I/O数据对用户空间完全不可见,所以只能处理数据完全不需要用户空间处理的情况,比如静态文件服务器。