vmsplice() 和 TCP

20
在原始的vmsplice()实现中,建议如果你有一个用户空间缓冲区,其大小是管道中可容纳页面数量的两倍,则对缓冲区的后半部分成功执行vmsplice()将确保内核完成使用缓冲区的前半部分。但事实并非如此,特别是对于TCP,内核页面将一直保留,直到从另一侧收到ACK。修复这个问题留作未来的工作,因此对于TCP,内核仍然必须从管道中复制页面。 vmsplice()具有SPLICE_F_GIFT选项,可以解决这个问题,但问题在于这会暴露出另外两个问题——如何高效地从内核获取新页面以及如何减少缓存崩溃。第一个问题是mmap需要内核清除页面,第二个问题是虽然mmap可能使用内核中的fancy kscrubd功能,但这会增加进程的工作集(缓存崩溃)。
基于此,我有以下问题:
  • 关于页面的安全重用,目前通知用户空间的状态如何?我特别关心在套接字(TCP)上 splice() 的页面。在过去的五年里有什么变化吗?
  • mmap/vmsplice/splice/munmap 是在 TCP 服务器中进行零拷贝的最佳实践,还是我们今天有更好的选择?

1
去问 kernel-linux@vger.kernel.org - Yann Droneaud
1个回答

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;
    }
};

你的意思肯定会影响吞吐量。 - Spudd86
1
根据 https://netdevconf.org/2.1/papers/netdev.pdf "这种方法不够精确且容易出错。一个空的传输队列并不意味着数据已经离开了机器。例如,数据包可能在设备传输队列中等待,或者如果正在运行 tcpdump 实例,则克隆可以被镜像到数据包套接字中"。通过 LWN https://lwn.net/Articles/726917/ 发现,非订阅用户可在 7-14 天后查看。 - sourcejedi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接