在原来的vmsplice()
实现中, 有人build议 ,如果你的用户空间缓冲区是可以放在一个pipe道中的最大页面数的2倍,那么在缓冲区的后半部分成功的vmsplice()可以保证内核是使用缓冲区的前半部分完成的。
但毕竟这并不是真的,特别是对于TCP,内核页面将被保留直到从对方收到ACK。 解决这个问题留作未来工作,因此对于TCP,内核仍然需要从pipe道复制页面。
vmsplice()
有SPLICE_F_GIFT
选项,可以解决这个问题,但问题是这暴露了另外两个问题 – 如何高效地从内核获取新的页面,以及如何减lesscaching垃圾。 第一个问题是mmap要求内核清除页面,第二个问题是尽pipemmap可能会在内核中使用花哨的kscrubd特性,这会增加进程的工作集(caching垃圾)。
基于此,我有这些问题:
mmap
/ vmsplice
/ splice
/ munmap
当前在TCP服务器中进行零拷贝的最佳实践,还是今天我们有更好的select? 是的,由于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; } };