NIO之零拷贝

    技术2025-11-16  20

    NIO的零拷贝的0指的是,0次需要cpu的拷贝,DMA拷贝不算在里面。零拷贝可以大大提高我们数据传输的效率。

    传统IO

    磁盘 -> 内核空间的缓存(DMA)内核空间的缓存 -> 用户态中的程序缓存对数据做一系列操作用户态中的程序缓存 -> 内核空间的的socket缓存内核空间的socket缓存->网卡缓存(DMA)网络发送(或拷贝到磁盘)!

    在一次传统的IO中,操作系统一共进行了2次拷贝,4次操作系统状态转换 2次拷贝:注意,磁盘到内核态的读写是通过DMA拷贝,外部设备(磁盘,U盘)不通过CPU直接与系统内存交换数据。所以是2次拷贝 4次系统状态切换:详情如下 tips: 为什么数据不直接从磁盘拷贝到用户程序空间呢?通过局部性原理我们知道,当我们从磁盘上取一个数据的时候,有很大可能下一次读取的数据就是本次读取数据周围的数据。因此操作系统为了提高性能,在读取数据的时候,会先把该目标数据周围的数据(NTFS下是4KB为单位)也一并读到操作系统的read buffer中,下次读取时,就有很大可能命中read buffer,减少了磁盘IO

    NIO 零拷贝

    不需要对数据进行操作
    windows Linux2.4以下

    sendFile() 该版本下的sendFile,只需要2次状态切换,即 开始: 用户态->内核态(read buffer 到 socket buffer)->用户态,但是还需要把Read buffer中的数据通过cpu完整的拷贝到Socket Buffer,所以不能算是真正的0拷贝。 该种方法是2次切换,1次拷贝
    Linux 2.4以后

    sendFile() 该版本下,sendFile()直接将内核态的Read buffer发送到NIC的 buffer,只有部分描述信息,如缓存位移,描述符经过socket buffer,通过这些信息,DMA直接把数据拷贝到外围设备缓存中。由于数据描述信息量很少,基本可以视作0,所以这也是0拷贝
    需要对数据进行操作

    当我们需要在用户空间对数据进行处理时,上述方法都不能解决问题

    mmap,把内核态的缓存空间和用户态的缓存空间映射到一块物理地址,实现共享,减少了内核态缓存到用户态缓存的一次拷贝。(因为使用mmap后,内存共享了,进程直接对内核空间映射到进程的虚拟地址进行读写就行,无需拷贝,但系统状态还是需要切换的)

    该种方法是4次上下文切换,3次拷贝(2次DMA拷贝)。

    3次上下文切换:
    发出mmap系统调用,用户态->内核态,通过DMA将磁盘文件拷贝到read buffer中mmap系统调用返回,内核态->用户态,此时用户空间和内核空间中的read buffer共享一块缓冲区内存空间用户修改数据,并发出write调用,用户态->内核态,read buffer中的数据通过cpu拷贝到socket bufferwrite调用返回,socker buffer通过DMA拷贝到外围设备。内核态->用户态

    零拷贝的再次理解

    我们说零拷贝,是从cpu的角度来说的,减少对cpu占用可以大大提升效率,而DMA拷贝无法避免。零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算。

    mmap和sendFile的区别

    mmap适合小数据读写, sendFile适合大文件传输mmap需要4次上下文切换,1次数据拷贝(还有);sendFile需要3次上下文切换,最小号2次数据拷贝sendFile可以利用DMA,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

    参考 浅谈NIO与零拷贝 尚硅谷韩顺平Netty视频教程(2019发布)

    Processed: 0.012, SQL: 9