当前位置:首页 > Web开发 > 正文

深入了解Netty【二】零拷贝

2024-03-31 Web开发

引言

以下翻译自:Zero Copy I: User-Mode Perspective

零拷贝是什么?

为了更好地理解问题的解决方案,我们首先需要理解问题本身。让我们来看看什么是参与网络服务器的简单过程d?mon服务数据存储在一个文件通过网络客户端。下面是一些示例代码:

read(file, tmp_buf, len); write(socket, tmp_buf, len);

看起来很简单;您会认为只有这两个系统调用不会带来太多开销。事实上,这与事实相去甚远。在这两个调用之后,数据至少复制了四次,并且几乎执行了相同数量的用户/内核上下文切换。(实际上这个过程要复杂得多,但我想让它保持简单)。为了更好地了解所涉及的流程,请看图1。顶部显示上下文切换,底部显示复制操作。

图1。复制两个示例系统调用

第一步:read系统调用导致上下文从用户模式切换到内核模式。第一个副本由DMA引擎执行,,它从磁盘读取文件内容并将其存储到内核地址空间缓冲区中。

第二步:将数据从内核缓冲区复制到用户缓冲区,read系统调用返回。调用的返回导致上下文从内核切换回用户模式。现在数据存储在用户地址空间缓冲区中,它可以再次开始向下移动。

第三步:write系统调用导致上下文从用户模式切换到内核模式。执行第三次复制,再次将数据放入内核地址空间缓冲区。不过,这一次,数据被放入一个不同的缓冲区,一个专门与套接字关联的缓冲区。

第四步:write系统调用返回,创建我们的第四个上下文切换。当DMA引擎将数据从内核缓冲区传递到协议引擎时,会独立地、异步地进行第四次复制。你可能会问自己,“独立和异步是什么意思?”在呼叫返回之前,数据没有传输吗?“呼叫返回,实际上并不保证传输;它甚至不能保证传输的开始。它只是意味着以太网驱动程序在它的队列中有空闲的描述符,并且已经接受我们的数据进行传输。可能有许多包在我们的前面排队。除非驱动程序/硬件实现优先级环或队列,否则数据是在先进先出的基础上传输的。(图1中分叉的DMA副本演示了最后一个副本可以延迟的事实)。

正如您所看到的,很多数据复制实际上并不是必要的。可以消除一些重复,以减少开销并提高性能。作为一名驱动程序开发人员,我使用的硬件具有一些非常高级的特性。一些硬件可以完全绕过主存,直接将数据传输到另一个设备。这个特性消除了系统内存中的副本,这是一个很好的特性,但是并不是所有的硬件都支持它。还有一个问题是来自磁盘的数据必须为网络重新打包,这带来了一些复杂性。为了消除开销,我们可以从消除内核和用户缓冲区之间的一些复制开始。

消除副本的一种方法是跳过调用read,而是调用mmap。例如:

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

为了更好地了解所涉及的流程,请看图2。上下文切换保持不变。

图2。调用mmap

第一步:mmap系统调用导致DMA引擎将文件内容复制到内核缓冲区。然后与用户进程共享缓冲区,而不需要在内核和用户内存空间之间执行任何复制。

第二步:write系统调用导致内核将原始内核缓冲区中的数据复制到与套接字相关的内核缓冲区中。

第三步:当DMA引擎将数据从内核套接字缓冲区传递到协议引擎时,发生第三次复制。

通过使用mmap而不是read,我们减少了内核必须复制的数据量的一半。当传输大量数据时,这将产生相当好的结果。然而,这种改善不是没有代价的;在使用mmap write方法时存在一些隐藏的陷阱。当您在内存中映射一个文件,然后调用write,而另一个进程截断相同的文件时,您将陷入其中之一。您的写系统调用将被总线错误信号SIGBUS中断,因为您执行了错误的内存访问。该信号的默认行为是终止进程并转储内核——这对于网络服务器来说不是最理想的操作。有两种方法可以解决这个问题。

第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中简单地调用return。通过这样做,write系统调用将返回它在被中断之前写入的字节数,并将errno设置为成功。让我指出,这将是一个糟糕的解决方案,只解决症状,而不是问题的根源。因为SIGBUS信号表明进程出现了严重错误,所以我不建议使用它作为解决方案。

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/42328.html