vmsplice()和TCP

时间:2011-06-21 07:25:44

标签: linux kernel mmap splice zero-copy

在原始vmsplice()实现中,it was suggested如果你有一个用户区缓冲区2x可以容纳管道的最大页数,那么下半部分的成功vmsplice()缓冲区将保证内核使用缓冲区的前半部分完成。

但事实并非如此,特别是对于TCP,内核页面将被保留,直到从另一方接收到ACK。修复此问题仍然是未来的工作,因此对于TCP,内核仍然必须从管道中复制页面。

vmsplice()SPLICE_F_GIFT选项可以解决这个问题,但问题是这会暴露出另外两个问题 - 如何有效地从内核获取新页面,以及如何减少缓存捣毁。第一个问题是mmap要求内核清除页面,第二个问题是虽然mmap可能会使用内核中的花哨kscrubd功能,但这会增加进程的工作集(缓存垃圾)。 / p>

基于此,我有以下问题:

  • 通知用户有关页面安全重复使用的当前状态是什么?我特别感兴趣的是将splice()d拼接到套接字(TCP)上。在过去的5年里发生了什么事吗?
  • mmap / vmsplice / splice / munmap目前是在TCP服务器中进行零复制的最佳做法,还是今天有更好的选择?

1 个答案:

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