暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

带你走入零拷贝的思考

Lord Lean Notes 2021-06-24
369
在学习优秀的中间件时,几乎都会看到用到了零拷贝的技术。于是查阅各种资料,再加上与优秀的人员对线交流,在这里记录下对零拷贝的理解。
Zero Copy
Zero Copy(零拷贝),这个是Linux系统下为了提升传输效率,而在用户态和内核态层面做的优化。
我们先来看下普通情况下调用读取和写入函数的代码,以及数据在用户态和内核态的流程。
    read(file, tmp_buf, len);
    write(socket, tmp_buf, len);

    图1

    在图1中数据被复制了四次,并且发生了四次的上下文切换。下面就看下流程:
    1. read函数的调用导致线程从用户态切换到内核态,接下来由DMA引擎从磁盘中读取文件内容,并将其保存到内核态的缓冲区中。

    2. 数据从内核缓冲区复制用户缓冲区,read系统调用返回。这时又发生了一次上下文的切换和数据复制

    3. write函数的调用导致再次从用户态切换到内核态,并将数据从用户缓冲区复制到专门与套接字关联的缓冲区

    4. write函数的系统调用返回时,发生了上下文的第四次切换。这时DMA引擎将数据同步或者异步的的方式将数据复制到协议引擎

    在普通的调用流程中发生了较多无效的动作,于是通过消除一些无效的复制操作来提升性能,这是因为一些高级的硬件可以直接将数据从一个设备传输到另一个设备。所以就出现了采用mmap函数替换read函数的方式,代码和流程如下:
      tmp_buf = mmap(file, len);
      write(socket, tmp_buf, len);

                                                      图2
      在图2中上下文切换的次数仍然是四次,但是数据的复制却是三次。
      1. mmap函数的调用使文件从DMA引擎复制到内核缓冲区,而且该缓冲区与用户缓冲区共享,不会在内核态和用户态之间发生复制。

      2. write函数的调用使内核将数据从内核缓冲区复制到与套接字关联的缓冲区

      3. DMA引擎将数据复制到协议引擎时,发生第三次复制

      通过使用mmap替换read函数的方式,将内核复制的数据量减少了大半。这样在传输大量数据时产生了较好的效果,但是也带来相应的问题。当调用write函数传输数据时,另一个进程对此文件做出了修改操作,write操作会因为错误的内存访问被中断。
      当然也是可以通过锁定此内核文件的一段使用期限,如果在时间期限内发生了修改,那么内核就会向操作进程发送一个中断信号,当调用到无效的地址并被错误信号杀死时,写入调用会被中断,并返回中断前写入的字节数。
      后面在内核2.1的版本中,引入了sendFile系统调用用以简化网络上两个本地文件的传输,sendFile这种方式不仅减少了数据复制,也减少了上下文切换的次数。下面是sendFile的代码和调用流程:
        sendfile(socket, file, len);

                                                        图3
        1. sendFile系统调用使文件内容被DMA引擎复制到内核缓冲区,然后内核将数据复制到与套接字相关的缓冲区

        2. DMA引擎将数据从套接字缓冲区传递到协议引擎,发生第三次复制

        当sendFile传递数据时发生了中断,则将简单的返回它在中断之前传输的字节数。
        在内核2.4的版本中,在硬件的帮助下,通过增加了一个支持收集操作的网络接口,消除了数据在内核中的重复复制动作。下面是其代码和流程:
          sendfile(socket, file, len);

                                                      图4

          在图4中通过支持收集的硬件从多个内存位置搜集数据,消除了另一个副本。

          1. sendFile系统调用使文件内容被DMA引擎复制到内核缓冲区
          2. 没有数据被复制到套接字缓冲区。只有具有有关数据的位置和长度的信息描述符才能附加到套接字缓冲区。DMA引擎将数据直接从内核缓冲区传递到协议引擎。

          零拷贝中是将数据从磁盘复制到内存以及从内存复制到线路,但是从操作系统来看这确实是零拷贝,因为数据在缓冲区中并没有发生复制。零拷贝不仅避免了复制,同时减少了上下文切换、CPU的数据校验和计算以及CPU缓存数据的污染。

          思考

          前段时间一起在讨论为什么Kafka的生产者发送消息时采用的是mmap的方式,而消费者消费消息时却采用的是sendFile的方式?

          结合上面的资料,mmap天生适用于小数据传输,这是因为mmap在传输时如果传输的内容发生了修改,则会中断操作。虽说有方法可以解决,但是在生产者传输消息的这种场景下,文件是会经常被修改的,如果传输大的文件,则会经常发生中断的情况。而传输小的文件则尽量可以避免这种情况的发生。

          在生产者写入消息的场景中,mmap特有的用户态和内核态共享的方式,可以使中间件做一些更多的事情。

          消费者采用sendFile的方式,首先消息可以直接在网络中传输,而且这时消息已经固定,几乎不会发生变化,可以减去对数据的校验和计算的步骤,来提升数据传输性能。

          文章转载自Lord Lean Notes,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论