在最初的vmsplice()实现中,有人建议如果你有一个用户区缓冲区2x可以容纳管道的最大页数,那么缓冲区后半部分成功的vmsplice()将保证内核完成使用缓冲区的前半部分.
但事实并非如此,特别是对于TCP,内核页面将被保留,直到从另一方接收到ACK.修复这个问题是未来的工作,因此对于TCP,内核仍然需要从管道中复制页面.
vmsplice()有一个SPLICE_F_GIFT选项可以处理这个,但问题是这暴露了另外两个问题 - 如何有效地从内核获取新页面,以及如何减少缓存垃圾.第一个问题是mmap需要内核清除页面,第二个问题是虽然mmap可能会使用内核中的花式kscrubd功能,但这会增加进程的工作集(缓存垃圾).
基于此,我有以下问题:
mmap/ vmsplice/ splice/ munmap目前在TCP服务器中进行零复制的最佳实践还是今天我们有更好的选择?目前,我试图了解splice/vmsplice的价值.关于IPC的用例,我在stackoverflow上偶然发现了以下答案:https://stackoverflow.com/a/1350550/1305501
问题:如何使用vmsplice将内存页面从一个进程转移到另一个进程而不复制数据(即零拷贝)?
上面提到的答案声称它是可能的.但是,它不包含任何源代码.如果我理解vmsplice正确的文档,如果内存被正确分配和对齐,以下函数将把内存页面转移到管道(内核缓冲区)而不复制.为了便于演示而省略了错误处理.
// data is aligned to page boundaries,
// and length is a multiple of the page size
void transfer_to_pipe(int pipe_out, char* data, size_t length)
{
size_t offset = 0;
while (offset < length) {
struct iovec iov { data + offset, length - offset };
offset += vmsplice(pipe_out, &iov, 1, SPLICE_F_GIFT);
}
}
Run Code Online (Sandbox Code Playgroud)
但是如何在不复制的情况下从用户空间访问内存页面?显然,以下方法不起作用:
vmsplice:此功能也可用于反向.但根据内核源代码中的注释,数据将被复制.read:我可以想象,如果内存正确对齐,这个函数会产生一些魔力,但我对此表示怀疑.mmap:管道不可能.但是有没有可以使用的某种虚拟文件,即splice虚拟文件的内存页面mmap呢?根本不可能vmsplice吗?
使用内核AIO O_DIRECT|O_SYNC,并没有复制到内核缓冲区中,当数据实际刷新到磁盘时,可以获得细粒度的通知.但是,它需要将数据保存在用户空间缓冲区中io_prep_pwrite().
使用splice(),可以将数据从内核空间缓冲区(管道)直接移动到磁盘,而无需复制它.但是,splice()在数据排队后立即返回,并且不等待对磁盘的实际写入.
目标是将数据从套接字移动到磁盘而不复制它,同时确认它已被刷新.如何结合以前的方法?
通过结合splice()使用O_SYNC,我希望splice()阻止和一个具有使用多线程来掩盖延迟.或者,可以使用异步io_prep_fsync()/ io_prep_fdsync(),但是这会等待所有数据被刷新,而不是用于特定的写入.两者都不完美.
所需要的是splice()与内核AIO 的组合,允许零拷贝和写入的异步确认,这样单个事件驱动的线程可以将数据从套接字移动到磁盘并在需要时获得确认,但这似乎不是支持的.有一个很好的解决方法/替代方法吗?
我正在Cyclone V SoC上运行Linux 5.1,这是一个FPGA,在一个芯片中具有两个ARMv7内核。我的目标是从外部接口收集大量数据,并通过TCP套接字流出(部分)这些数据。这里的挑战是数据速率非常高,并且可能接近饱和GbE接口。我有一个write()可行的实现,该实现只使用对套接字的调用,但其最高速度为55MB / s;大约是理论GbE限制的一半。我现在正在尝试使零拷贝TCP传输能够提高吞吐量,但是我遇到了麻烦。
为了将数据从FPGA传送到Linux用户空间,我编写了一个内核驱动程序。该驱动程序使用FPGA中的DMA模块将大量数据从外部接口复制到连接到ARMv7内核的DDR3存储器中。当使用dma_alloc_coherent()进行探测时GFP_USER,驱动程序将此内存分配为一堆连续的1MB缓冲区,并通过mmap()在文件中实现并将这些/dev/地址返回给应用程序使用dma_mmap_coherent()预分配的缓冲区,将这些缓冲区公开给用户空间应用程序。
到目前为止,一切都很好; 用户空间应用程序正在查看有效数据,并且吞吐量超过360MB / s足够多,并有剩余空间(外部接口的速度不足以真正看到上限)。
为了实现零拷贝TCP网络,我的第一种方法是SO_ZEROCOPY在套接字上使用:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
Run Code Online (Sandbox Code Playgroud)
但是,这导致send: Bad address。
谷歌搜索了一段时间之后,我的第二种方法是使用管道,splice()然后执行以下操作vmsplice():
ssize_t sent_bytes;
int pipes[2];
struct iovec iov = {
.iov_base = buf,
.iov_len = len
};
pipe(pipes);
sent_bytes = vmsplice(pipes[1], &iov, 1, 0);
if (sent_bytes < 0) {
perror("vmsplice"); …Run Code Online (Sandbox Code Playgroud) 嗨,我使用两种方法删除数组中的对象: - 拼接和过滤.
拼接代码在这里: -
(this.myArray).splice((this.myArray).indexOf(myobject), 1);
Run Code Online (Sandbox Code Playgroud)
过滤代码: -
(this.myArray).filter(obj => obj !== myobject);
Run Code Online (Sandbox Code Playgroud)
请告诉我们两者之间的差异,哪一个是最好的方法?
在 C++20 (N4849) 中,关联容器extract()和insert(node_handle)/insert(hint, node_handle)方法没有异常安全措辞。
但是对于merge(),虽然有这样的措辞:
抛出:除非比较对象抛出,否则什么都没有。
位置:
22.2.6 关联容器 [associative.reqmts]
表 78:关联容器要求(除了容器)[tab:container.assoc.req]
第 799 页
显然,最初的提案 ( P0083R3 ) 旨在使其无投掷(第 5 页):
异常安全
如果容器的 Compare 函数是 no-throw(这是很常见的),那么移除、修改和插入节点都是 no-throw,除非修改值会抛出。如果修改值确实抛出,它会在所涉及的容器之外进行。
但为什么在该提案后面的拟议措辞中没有发言权?
这是关于splice()的另一个问题.我希望用它来复制文件,我试图使用两个拼接调用,通过像splice维基百科页面上的例子一样的管道连接.我写了一个简单的测试用例,它只试图从一个文件读取前32K字节并将它们写入另一个文件:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv) {
int pipefd[2];
int result;
FILE *in_file;
FILE *out_file;
result = pipe(pipefd);
in_file = fopen(argv[1], "rb");
out_file = fopen(argv[2], "wb");
result = splice(fileno(in_file), 0, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
printf("%d\n", result);
result = splice(pipefd[0], NULL, fileno(out_file), 0, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
printf("%d\n", result);
if (result == -1)
printf("%d - %s\n", errno, strerror(errno));
close(pipefd[0]);
close(pipefd[1]);
fclose(in_file);
fclose(out_file);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当我运行它时,输入文件似乎正确读取,但第二次拼接调用失败了EINVAL.谁知道我在这里做错了什么?
谢谢!
更新:Nemo先生的回答有助于解决问题!下面的代码包含修复程序!看到nb False和nb True下面调用.
还有一个新的Haskell包被称为splice(具有特定于操作系统的便携式实现,这是一种最着名的socket到socket数据传输循环).
我有以下(Haskell)代码:
#ifdef LINUX_SPLICE
#include <fcntl.h>
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
#endif
module Network.Socket.Splice (
Length
, zeroCopy
, splice
#ifdef LINUX_SPLICE
, c_splice
#endif
) where
import Data.Word
import Foreign.Ptr
import Network.Socket
import Control.Monad
import Control.Exception
import System.Posix.Types
import System.Posix.IO
#ifdef LINUX_SPLICE
import Data.Int
import Data.Bits
import Unsafe.Coerce
import Foreign.C.Types
import Foreign.C.Error
import System.Posix.Internals
#else
import System.IO
import Foreign.Marshal.Alloc
#endif
zeroCopy :: Bool
zeroCopy =
#ifdef …Run Code Online (Sandbox Code Playgroud)