20

在最初的vmsplice()实现中,建议如果您的用户级缓冲区是管道中最大页数的 2 倍,则缓冲区后半部分的成功 vmsplice() 将保证内核已使用缓冲区的前半部分。

但这毕竟不是真的,特别是对于 TCP,内核页面会一直保留到从另一端收到 ACK 为止。解决这个问题留作未来的工作,因此对于 TCP,内核仍然必须从管道复制页面。

vmsplice()SPLICE_F_GIFT处理这个问题的选项,但问题是这暴露了另外两个问题——如何有效地从内核获取新页面,以及如何减少缓存垃圾。第一个问题是 mmap 需要内核清除页面,第二个问题是尽管 mmap 可能会使用内核中花哨的kscrubd功能,但这会增加进程的工作集(缓存垃圾)。

基于此,我有以下问题:

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

1 回答 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;
    }
};
于 2011-07-22T19:23:02.243 回答