传统文件传输过程: 第一步拷贝:把磁盘上的数据拷贝到操作系统的内核缓冲区上来,通过DMA实现第二步拷贝:把内核缓冲区的数据拷贝到用户的缓冲区里,此时我们的应用程序就可以使用这部分数据了,这个拷贝过程由CPU完成第三次拷贝:把刚才拷贝到用户缓冲区里的数据再拷贝到内核的socket缓冲区里,这个过程依然由CPU完成第四次拷贝:把内核的socket缓冲区里的数据拷贝到网卡的缓冲区里,这个过程由DMA实现零拷贝技术 是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。通俗来讲,零拷贝就是一种避免CPU将数据从一块存储拷贝到另一块存储的技术,零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而提高数据的传输效率DMA:把磁盘的数据栲贝到内核的缓冲区里read():系统调用的过程中会把内核缓冲区的数据栲贝到用户的缓冲区里write():操作系统直接将内核缓冲区的数据栲贝到socket缓冲区中CPU拷贝:把内核缓冲区里的数据栲贝到socket缓冲区的过程可以看出在一次文件传输的过程中需要4次拷贝,并且这个期间一共发生了4次用户态与内核态的上下文切换,因为发生了两次系统调用(应用程序去访问操作系统内核的功能的时候去调用系统内核功能的过程叫做系统调用),一次是read,一次是write,每次系统调用都要先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态,上下文切换成本并不小,一次切换需要几十纳秒到几微秒,在高并发的场景下,这种时间会被放大,影响系统性能。问题 我们再看这个文件传输过程,我们只是搬运了一份数据,结果却搬运了四次,这种传统文件传输方式存在冗余的上下文切换和数据拷贝,在高并发的场景下,严重影响系统性能 所以,我们要减少用户态和内核态的上下文切换和数据拷贝的次数 解决方案:MMAPwirte mmap()系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据栲贝操作。bufmmap(diskfd,len);write(sockfd,buf,len); 应用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态,最后,socket缓冲区再把数据发到网卡去。该过程减少了一次拷贝问题 使用mmap替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。但是使用mmap是有代价的。当你使用mmap时,你可能会遇到一些隐藏的陷阱。例如,当你的程序map了一个文件,但是当这个文件被另一个进程截断(truncate)时,write系统调用会因为访问非法地址而被SIGBUS信号终止。SIGBUS信号默认会杀死你的进程并产生一个coredump,如果你的服务器这样被中止了,那会产生一笔损失。解决方案 1。为SIGBUS信号建立信号处理程序当遇到SIGBUS信号时,信号处理程序简单地返回,write系统调用在被中断之前会返回已经写入的字节数,并且errno会被设置成success,但是这是一种糟糕的处理办法,因为你并没有解决问题的实质核心。 2。使用文件租借锁通常我们使用这种方法,在文件描述符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的RTSIGNALLEASE信号,告诉我们内核正在破坏你加持在文件上的读写锁。这样在程序访问非法内存并且被SIGBUS杀死之前,你的write系统调用会被中断。write会返回已经写入的字节数,并且置errno为success。我们应该在mmap文件之前加锁,并且在操作完文件后解锁:sendfile 从Linux内核2。4版本开始起,对于支持网卡支持SGDMA技术的情况下,第一步,通过DMA将磁盘上的数据拷贝到内核缓冲区里;第二步,缓冲区拷贝文件描述符和数据长度传到socket缓冲区,这样网卡的SGDMA控制器就可以直接将内核缓存中的数据栲贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区栲贝到socket缓冲区中,这样就减少了一次数据栲贝 零栲贝技术的文件传输方式相比传统文件传输的方式,减少了2次上下文切换和数据栲贝次数,只需要2次上下文切换和数据栲贝次数,就可以完成文件的传输,而且2次的数据栲贝过程,都不需要通过CPU,2次都是由DMA来搬运。所以,总体来看,零栲贝技术可以把文件传输的性能提高至少一倍以上零拷贝的好处减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务减少内存带宽的占用通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换