在原始vmsplice()
实现中,it was suggested如果你有一个用户区缓冲区2x可以容纳管道的最大页数,那么下半部分的成功vmsplice()缓冲区将保证内核使用缓冲区的前半部分完成。
但事实并非如此,特别是对于TCP,内核页面将被保留,直到从另一方接收到ACK。修复此问题仍然是未来的工作,因此对于TCP,内核仍然必须从管道中复制页面。
vmsplice()
有SPLICE_F_GIFT
选项可以解决这个问题,但问题是这会暴露出另外两个问题 - 如何有效地从内核获取新页面,以及如何减少缓存捣毁。第一个问题是mmap要求内核清除页面,第二个问题是虽然mmap可能会使用内核中的花哨kscrubd功能,但这会增加进程的工作集(缓存垃圾)。 / p>
基于此,我有以下问题:
mmap
/ vmsplice
/ splice
/ munmap
目前是在TCP服务器中进行零复制的最佳做法,还是今天有更好的选择?答案 0 :(得分:5)
是的,由于TCP套接字在不确定的时间内保留页面,因此无法使用示例代码中提到的双缓冲方案。此外,在我的用例中,页面来自循环缓冲区,因此我无法将页面提供给内核并分配新页面。我可以验证我在收到的数据中看到数据损坏。
我使用轮询TCP套接字的发送队列的级别,直到它耗尽为0.这样可以修复数据损坏但是次优,因为将发送队列排到0会影响吞吐量。
n = ::vmsplice(mVmsplicePipe.fd.w, &iov, 1, 0);
while (n) {
// splice pipe to socket
m = ::splice(mVmsplicePipe.fd.r, NULL, mFd, NULL, n, 0);
n -= m;
}
while(1) {
int outsize=0;
int result;
usleep(20000);
result = ::ioctl(mFd, SIOCOUTQ, &outsize);
if (result == 0) {
LOG_NOISE("outsize %d", outsize);
} else {
LOG_ERR_PERROR("SIOCOUTQ");
break;
}
//if (outsize <= (bufLen >> 1)) {
if (outsize == 0) {
LOG("outsize %d <= %u", outsize, bufLen>>1);
break;
}
};