vmsplice()和TCP

use*_*558 20 linux kernel mmap splice zero-copy

在最初的vmsplice()实现中,有人建议如果你有一个用户区缓冲区2x可以容纳管道的最大页数,那么缓冲区后半部分成功的vmsplice()将保证内核完成使用缓冲区的前半部分.

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

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

基于此,我有以下问题:

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

Pet*_*ran 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;
    }
};
Run Code Online (Sandbox Code Playgroud)