图解|零拷贝Zero-Copy技术大揭秘( 二 )

  • CPU完成拷贝之后 , read()函数返回实现用户态切换用户态 , 这是第2次状态切换;
  • 写数据过程:
    • 应用程序要向网卡写数据 , 调用write()函数实现用户态切换内核态 , 这是第1次切换;
    • CPU将用户缓冲区数据拷贝到内核缓冲区 , 这是第1次CPU拷贝;
    • DMA控制器将数据从内核缓冲区复制到socket缓冲区 , 这是第1次DMA拷贝;
    • 完成拷贝之后 , write()函数返回实现内核态切换用户态 , 这是第2次切换;
    综上所述:
    • 读过程涉及2次空间切换、1次DMA拷贝、1次CPU拷贝;
    • 写过程涉及2次空间切换、1次DMA拷贝、1次CPU拷贝;
    可见传统模式下 , 涉及多次空间切换和数据冗余拷贝 , 效率并不高 , 接下来就该零拷贝技术出场了 。
    4. 零拷贝技术4.1 出现原因我们可以看到 , 如果应用程序不对数据做修改 , 从内核缓冲区到用户缓冲区 , 再从用户缓冲区到内核缓冲区 。 两次数据拷贝都需要CPU的参与 , 并且涉及用户态与内核态的多次切换 , 加重了CPU负担 。
    我们需要降低冗余数据拷贝、解放CPU , 这也就是零拷贝Zero-Copy技术 。
    4.2 解决思路目前来看 , 零拷贝技术的几个实现手段包括:mmap+write、sendfile、sendfile+DMA收集、splice等 。
    图解|零拷贝Zero-Copy技术大揭秘文章插图
    4.2.1 mmap方式mmap是Linux提供的一种内存映射文件的机制 , 它实现了将内核中读缓冲区地址与用户空间缓冲区地址进行映射 , 从而实现内核缓冲区与用户缓冲区的共享 。
    这样就减少了一次用户态和内核态的CPU拷贝 , 但是在内核空间内仍然有一次CPU拷贝 。
    图解|零拷贝Zero-Copy技术大揭秘文章插图
    mmap对大文件传输有一定优势 , 但是小文件可能出现碎片 , 并且在多个进程同时操作文件时可能产生引发coredump的signal 。
    4.2.2 sendfile方式mmap+write方式有一定改进 , 但是由系统调用引起的状态切换并没有减少 。
    sendfile系统调用是在 Linux 内核2.1版本中被引入 , 它建立了两个文件之间的传输通道 。
    sendfile方式只使用一个函数就可以完成之前的read+write 和 mmap+write的功能 , 这样就少了2次状态切换 , 由于数据不经过用户缓冲区 , 因此该数据无法被修改 。
    图解|零拷贝Zero-Copy技术大揭秘文章插图
    图解|零拷贝Zero-Copy技术大揭秘文章插图
    从图中可以看到 , 应用程序只需要调用sendfile函数即可完成 , 只有2次状态切换、1次CPU拷贝、2次DMA拷贝 。
    但是sendfile在内核缓冲区和socket缓冲区仍然存在一次CPU拷贝 , 或许这个还可以优化 。
    4.2.3 sendfile+DMA收集Linux 2.4 内核对 sendfile 系统调用进行优化 , 但是需要硬件DMA控制器的配合 。
    升级后的sendfile将内核空间缓冲区中对应的数据描述信息(文件描述符、地址偏移量等信息)记录到socket缓冲区中 。
    DMA控制器根据socket缓冲区中的地址和偏移量将数据从内核缓冲区拷贝到网卡中 , 从而省去了内核空间中仅剩1次CPU拷贝 。
    图解|零拷贝Zero-Copy技术大揭秘文章插图
    这种方式有2次状态切换、0次CPU拷贝、2次DMA拷贝 , 但是仍然无法对数据进行修改 , 并且需要硬件层面DMA的支持 , 并且sendfile只能将文件数据拷贝到socket描述符上 , 有一定的局限性 。
    4.2.4 splice方式splice系统调用是Linux 在 2.6 版本引入的 , 其不需要硬件支持 , 并且不再限定于socket上 , 实现两个普通文件之间的数据零拷贝 。


    推荐阅读