当前位置: 首页 > 科技观察

框架文章:小白秒懂的Linux零拷贝原理

时间:2023-03-19 22:08:38 科技观察

转载本文请联系偷窥公众号。前言用通俗易懂的语言解释,零拷贝是指数据不从一个存储区域复制到另一个存储区域。但是没有数据复制,怎么可能实现数据传输呢?其实我们在javaNIO、netty、kafka中遇到的零拷贝并不是不拷贝数据,而是减少不必要的数据拷贝次数,从而将代码性能提升到零拷贝内核空间和用户空间缓冲区以及虚拟内存的好处传统I/Ommap+write实现零拷贝Sendfile实现零拷贝Sendfile带有DMA集合拷贝功能实现零拷贝java提供的零拷贝方式零拷贝零拷贝机制的好处是可以减少用户空间和操作系统内核的上下文切换空间。减少内存使用。内核空间和用户空间。:提供给每个程序进程的空间;用户空间无权访问内核空间资源。如果一个应用程序需要使用内核空间资源,需要通过系统调用来完成:从用户空间切换到内核空间并完成相关操作,然后再从内核空间切换回用户空间缓冲区和虚拟内存直接内存访问(DMA))直接内存访问:DMA允许外围设备和内存存储之间直接进行IO数据传输,过程不需要CPU参与,缓冲区是所有I/O的基础。I/O只不过是将数据移入或移出缓冲区。该进程发起读取请求。内核首先检查内核空间缓冲区是否有进程需要的数据。如果已经存在,则直接Copy数据到进程的内存区。如果没有,系统从磁盘请求数据,通过DMA写入内核的读缓冲区,然后将内核缓冲区的数据复制到进程的内存区。进程发起写请求,就是将进程的内存区数据拷贝到内核中。writebuffer,然后通过DMA将内核buffer数据flush回磁盘或网卡。虚拟内存:现代操作系统使用虚拟内存,它有以下两个优点:多个虚拟地址可以指向同一个物理内存地址虚拟内存的空间可以比实际可用的物理地址大。利用第一个特性,可以将内核空间地址和用户空间虚拟地址映射到同一个物理地址,这样DMA就可以同时填充(读写)内核和用户空间进程可见的缓冲区。区域;大致如下传统I/O#includessize_twrite(intfiledes,void*buf,size_tnbytes);ssize_tread(intfiledes,void*buf,size_tnbytes);比如linux系统上的java,读取一个磁盘文件,并发送给远程服务1)发出read系统调用,会引起用户空间到内核空间的上下文切换,然后从中读取文件中的数据disk通过DMA到内核空间buffer2)然后将内核空间buffer区中的数据复制到用户空间进程内存,然后read系统调用返回。系统调用的返回将导致从内核空间到用户空间的上下文切换。3)write系统调用会再次引起用户空间到内核空间的上下文切换,将用户空间进程中的内存数据复制到内核空间的socket缓冲区(也是内核缓冲区,但对于socket来说),然后write系统调用返回,再次触发上下文切换4)至于从socketbuffer到网卡的数据传输,是一个独立的异步过程,也就是说write系统调用的返回不保证数据传输到网卡“用户空间和内核空间之间有四个上下文切换。四次数据拷贝,两次CPU数据拷贝,两次DMA数据拷贝”mmap+write实现的零拷贝#includevoid*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffset)1)发出mmap系统调用,导致上下文从用户空间切换到内核空间,磁盘文件中的数据随后被DMA引擎复制到内核空间缓冲区2)mmap系统调用返回,导致上下文从内核空间切换到用户空间3)这里不需要从内核空间拷贝数据到用户空间,因为用户空间这个buffer是和内核空间共享的4)发出write系统调用,导致从用户空间的上下文切换到内核??空间,将数据从内核空间缓冲区复制到内核空间套接字缓冲区;write系统调用返回,导致从内核空间到用户空间的上下文切换5)异步,DMA引擎将数据复制到内核空间socketbuffer到网卡”通过mmap实现零拷贝I/O性能rms4个用户空间和内核空间的上下文切换,3个数据副本;3个数据副本包括2个DMA副本和1个CPU副本。”sendfile实现的零副本#includessize_tsendfile(intout_fd,intin_fd,off_t*offset,size_tcount);1)发出sendfile系统调用,结果在一个上下文中从用户空间切换到内核空间,然后通过DMA引擎Buffer将磁盘文件的内容复制到内核空间,再将数据从内核空间的缓冲区复制到socket相关的缓冲区2)sendfile系统调用返回,导致内核空间到用户空间的上下文切换,DMA将内核空间的socketbuffer中的数据异步传输到网卡”sendfile实现的零拷贝I/O使用了2次上下文切换用户空间和内核空间,以及3份数据。其中,3个数据副本包括2个DMA副本和1个CPU副本。从Linux2.4开始实现了具有DMA收集副本功能的sendfile的零副本实现,操作系统提供了scatter和gather的SG-DMA方法,直接from将内核空间缓冲区中的数据读取到网卡中,不需要将内核空间缓冲区中的数据复制到socket缓冲区中。1)发出sendfile系统调用,导致从用户空间进行上下文切换到内核??空间,通过DMA引擎将磁盘文件的内容复制到内核空间缓冲区2)这里并没有把数据复制到socketbuffer中,而是将相应的描述符信息复制到socketbuffer中,描述符包含两个信息类型:A)内核缓冲区的内存地址,B)内核缓冲区的偏移量3)sendfile系统调用的返回,导致从内核空间到用户空间的上下文切换,DMA直接将数据复制到内核缓冲区根据套接字缓冲区的描述符提供的地址和偏移量到网卡。上下文切换,以及2份数据,而这2份数据是非CPU副本。这样我们就实现了理想的零拷贝I/O传输,不需要任何CPU拷贝,至少javaNIO提供的零拷贝方式实现是基于mmap+write方式的FileChannel的map方式生成MappedByteBufferFileChannel提供了一个map()方法,可以在一个打开的文件和MappedByteBuffer之间建立一个虚拟内存映射,MappedByteBuffer继承自ByteBuffer;buffer的内存是一个文件的内存映射区。map方法底层是通过mmap实现的,所以文件内存从磁盘读取到内核缓冲区后,用户空间和内核空间共享缓冲区。用法如下"./siting.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);MappedByteBufferdata=readChannel.map(FileChannel.MapMode.READ_ONLY,0,1024*1024*40);//数据传输writeChannel.write(data);readChannel.close();writeChannel.close();}catch(Exceptione){System.out.println(e.getMessage());}}FileChannel的transferTo,transferFrom如果底层操作系统支持,transferTo,transferFrom也会使用相关的零拷贝技术实现数据传输。用法如下"./siting.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);longlen=readChannel.size();longposition=readChannel.position();//数据传输readChannel.transferTo(position,len,writeChannel);//效果和transferTo一样//writeChannel.transferFrom(readChannel,position,len,);readChannel.close();writeChannel.close();}catch(Exceptione){System.out.println(e.getMessage());}}欢迎参考文中错误参考文章浅谈Linux下的零拷贝机制[1]面试被问“零拷贝”!你真的了解吗?[2]javaNIO的通道Channel的理解[3]Channel的基本使用——FileChannel类的使用和内存映射[4]参考[1]Linux下零拷贝机制浅谈:https://www.jianshu.com/p/e76e3580e356[2]面试被问“零文案”!你真的懂吗?:https://my.oschina.net/u/3990817/blog/30453593]javaNIOChannelChannel的理解:https://blog.csdn.net/qq_27092581/article/details/78347198[4]频道基本使用——FileChannel类的使用及内存映射:https://blog.csdn.net/qq_45337431/article/details/99645809