零拷贝原理

什么是零拷贝

    CPU不需要再为数据在内存之间拷贝而消耗资源。通常指计算机在网络上传输数据时,不需要把数据拷贝到用户空间(User Space) 而直接在内核空间将数据传输到网络上的方式。

零拷贝的优点

    减少不必要的CPU拷贝;
    减少内存带宽的占用;
    减少用户空间和内核空间的上下文切换。

预备知识

    在理解零拷贝之前需要先了解一下两种I/O读写方式

    1.I/O中断方式

    使用这种方式应用程序与磁盘之间的I/O操作,都是通过CPU的中断来完成的,每次用户进程读取磁盘数据时,都需要CPU中断,然后发起I/O请求等待数据读取完成。每次I/O中断都会引起CPU的上下文切换。如下图所示:大致的过程如下:

    1).用户进程向CPU发起read系统调用读取数据,由用户进程地址空间切换到内核地址空间,然后当前用户线程阻塞等待数据返回;
    2).CPU接收到read指令以后对磁盘发起I/O请求,将磁盘数据先放入磁盘控制器缓冲区;
    3).数据准备完毕之后,磁盘向CPU发起I/O中断;
    4).CPU收到I/O中断以后,将磁盘缓冲区中的数据拷贝到内核缓冲区{2},然后再从内核缓冲区拷贝到用户缓冲区;
    5).用户进程从内核地址空间切换回用户地址空间,解除阻塞状态,等待CPU的下一个执行时间片。

    2.DMA方式

    I/O中断方式进行I/O读写会消耗大量的CPU资源,于是出现了DMA技术(Direct Memory Access),即直接存储器存取。DMA技术是现在计算机磁盘系统都支持的一种技术。其原理是,整那个数据的传输操作都在一个DMA控制器的控制下进行。CPU除了在数据传输的开始和结束时做中断处理外,在传输过程中CPU可以处理其他任务。具体操作流程如下:
    1).外设通过DMA控制器向CPU发出DMA请求;
    2).CPU响应DMA请求,系统转变为DMA工作方式,并把总线控制权交给DMA控制器;
    3).由DMA控制器发送存储器地址,并决定传输数据块的长度;
    4).传输数据;
    5).DMA操作结束,把总线控制权交换给CPU。

    现在的计算机磁盘系统都支持DMA技术,DMA技术也是零拷贝的底层实现原理。

    下面看一下传统I/O方式和零拷贝方式读写数据时,操作系统都进行了哪些工作。

传统I/O读写方式

    read(file,tmp_buf,len);
    write(socket,tmp_buf,len);
    以上代码很简单,操作系统的实现过程却很复杂,大概处理流程如下:
   

零拷贝原理


    1).程序调用read()系统调用。系统从用户态切换到内核态(第一次上下文切换),磁盘中的数据用DMA的方式读取到内核缓存区(kernel buffer)中。DMA过程CPU不需要参与数据的读写,而是DMA处理器直接将硬盘数据通过中线传输到内存中。
    2).系统有内核态切换为用户态(第二次上下文切换),当程序读取的数据已经完成写入内核缓存区后,会将数据从内核缓存区拷贝到用户缓存区(第一次CPU拷贝)。这个过程需要CPU参与读写。
    3).程序使用write()系统调用。系统由用户态切换到内核态(第三次上下文切换),数据从用户空间缓存区拷贝到网络缓冲区(Socket Buffer)( 第二次CPU拷贝)。这个过程需要CPU参与读写。
    4).系统由内核态切换到用户态(第四次上下文切换),网络缓冲区数据通过DMA的方式传输到网卡的驱动中。
   传统的I/O方式,要经过4次用户态与内核态的上下文切换,并且要进行2此CPU内存拷贝过程。整个过程比较消耗计算机资源。(注意,第1步和第4步,如果不使用DMA技术,而使用CPU中断方式,那么还会多出两次CPU拷贝)

零拷贝实现方式

    零拷贝技术就是为了解决以上过程中资源消耗过大的问题。零拷贝的实现方式有三种

    1.内存映射方式实现零拷贝

     tmp_buf = mmap(file,len);
     write(socket, tmp_buf, len);

    这种方式的处理流程如下:

零拷贝原理

    这种方式的I/O原理就是将用户缓存区(user buffer)的内存地址和内核缓冲区(kernel buffer)的内存地址做一个映射,也就是说系统在用户态可以直接操作内核空间的数据。
    1).mmap()系统调用首先会使用DMA的方式将磁盘数据读取到内核缓冲区,然后通过内存映射的方式,使用户缓冲区与内核缓冲区的内存地址为同一地址,也就是说不需要CPU再将数据从内核缓存区复制到用户缓冲区。
    2).当使用write()系统调用的时候,CPU将内核缓冲区的数据直接写入到网络发送缓冲区,然后通过DMA的方式将数据传入到网卡驱动程序中.

    通过内存映射的方式,可以减少CPU读写的次数,但是用户态和内核态的上下文切换仍然是4次。同时并发访问共享地址的时候,容易参数数据不一致的情况,需要使用合理的同步方式来避免对共享数据的不一致性操作。

    2.sendfile实现零拷贝

    sendfile(socket,file,len);

    通过sendfile系统调用,可以做到内核空间内部直接进行I/O传输。其流程如下:

零拷贝原理

        1).sendfile()系统调用也会引起用户态到内核态的切换,与内存映射方式不同的是,用户空间此时无法看到或修改数据内容。
        2).从磁盘读取到内存是DMA的方式,从内核读取数据到网络发送缓冲区,依旧需要使用CPU参与拷贝,从网络发送缓冲区到网卡中的缓冲区依旧是DMA方式。
        sendfile的方式有一次CPU参与拷贝,两次用户态到内核态的上下文切换。相比于内存映射的方式有了很大的进步,但这种方式程序不能对数据进行修改,只是单纯的进行一次数据传输过程。

    3.理想的零拷贝(DMA收集功能+sendfile)

    理想状态的零拷贝,需要借助于硬件上的帮助,上面的零拷贝过程是把缓冲区中的数据拷贝到socket缓存中,是加上,仅仅需要把缓冲区描述符传到socket缓冲区,再把数据长度传过去,这样DMA控制器直接将页缓存中的数据打包发送到网络中就可以了。

零拷贝原理