• 零拷贝Zero copy-linux and java


    什么是零拷贝

    传统

    零拷贝

    背景-几种拷贝方式

    方式1:Copying in Two Sample System Calls

    read(file, tmp_buf, len);
    write(socket, tmp_buf, len);

    1. 首先,调用read时,文件A copy到了kernel模式;
    2. 之后,CPU控制将kernel模式数据copy到user模式下;
    3. 调用write时,先将user模式下的内容copy到kernel模式下的socket的buffer中;
    4. 最后将kernel模式下的socket buffer的数据copy到网卡设备中传送;

    方式2:Calling mmap

    tmp_buf = mmap(file, len);
    write(socket, tmp_buf, len);

    1. 首先(通过DMA)将数据从磁盘读取到kernel buffer中;
    2. 然后将kernel buffer拷贝到socket buffer中;
    3. 最后将socket buffer中的数据copy到网卡设备(protocol engine)中发送;
      其中mmap可以得到kernal buffer的引用,并直接进行使用

    方式3:Replacing Read and Write with Sendfile

    sendfile(socket, file, len);

    1. 首先(通过DMA)将数据从磁盘读取到kernel buffer中;
    2. 然后将kernel buffer拷贝到socket buffer中;
    3. 最后将socket buffer中的数据copy到网卡设备(protocol engine)中发送;

    方式4:Hardware that supports gather can assemble data from multiple memory locations, eliminating another copy.

    sendfile(socket, file, len);
    调用无变化

    1. 将文件拷贝到kernel buffer中;
    2. 向socket buffer中追加当前要发生的数据在kernel buffer中的位置和偏移量;
    3. 根据socket buffer中的位置和偏移量直接将kernel buffer的数据copy到网卡设备(protocol engine)中;
      经过上述过程,数据只经过了2次copy就从磁盘传送出去了。

    linux零拷贝

    Linux系统零拷贝调用

    The Linux kernel supports zero-copy through various system calls, such as
    sys/socket.h's sendfile, sendfile64
    splice, tee, vmsplice
    process_vm_readv, process_vm_writev
    copy_file_range
    raw sockets with packet mmap[4] or AF_XDP

    sendfile()

    splice()

    splice() 系统调用和 sendfile() 非常类似,

    用户应用程序必须拥有两个已经打开的文件描述符,一个用于表示输入设备,一个用于表示输出设备。与 sendfile() 不同的是,splice() 允许任意两个文件之间互相连接,而并不只是文件到 socket 进行数据传输。对于从一个文件描述符发送数据到 socket 这种特例来说,一直都是使用 sendfile() 这个系统调用,而 splice 一直以来就只是一种机制,它并不仅限于 sendfile() 的功能。

    也就是说,sendfile() 只是 splice() 的一个子集,在 Linux 2.6.23 中,sendfile() 这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过 API 以及相应的功能是利用了 splice() 这种机制来实现的。
    总体来讲splice()是Linux 2.6.23 内核版本中替换sendfile()系统调用的一个方法,它不仅支持文件到Socket的直接传输,也支持文件到文件的直接传输I/O,但是其底层的传输过程和sendfile()并无区别。

    零拷贝实现

    1. 最早的实现为IBM OS/360,其中一个程序可以指示通道子系统从一个文件或设备复制数据块到另一处,无需先转移数据。
    2. 实现零复制的软件通常依靠基于直接存储器访问(DMA)的复制,以及通过内存管理单元(MMU)的内存映射。这些功能需要特定硬件的支持,并通常涉及到特定存储器的对齐。
    3. 一种较新的方式为使用异构系统架构(HSA),便于CPU和GPU以及其他处理器传递指针。这需要CPU和GPU使用统一地址空间。

    java 零拷贝

    directbytebuffer and heapbytebuffer

    directbytebuffer减少了堆大小,自己进行内存管理,可以避免gc引发的在不同内存区之间拷贝的问题

    https://stackoverflow.com/questions/49174987/where-is-the-memory-of-direct-buffer-allocated-by-netty-kernel-space-or-user-sp

    transferTo

    java实现:
    https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/sun/nio/ch/FileChannelImpl.java

      private long transferToDirectly(long position, int icount,
                                        WritableByteChannel target)
            throws IOException
        {
            if (!transferSupported)
                return IOStatus.UNSUPPORTED;
    
            FileDescriptor targetFD = null;
            if (target instanceof FileChannelImpl) {
                if (!fileSupported)
                    return IOStatus.UNSUPPORTED_CASE;
                targetFD = ((FileChannelImpl)target).fd;
            } else if (target instanceof SelChImpl) {
                // Direct transfer to pipe causes EINVAL on some configurations
                if ((target instanceof SinkChannelImpl) && !pipeSupported)
                    return IOStatus.UNSUPPORTED_CASE;
                targetFD = ((SelChImpl)target).getFD();
            }
            if (targetFD == null)
                return IOStatus.UNSUPPORTED;
            int thisFDVal = IOUtil.fdVal(fd);
            int targetFDVal = IOUtil.fdVal(targetFD);
            if (thisFDVal == targetFDVal) // Not supported on some configurations
                return IOStatus.UNSUPPORTED;
    
            long n = -1;
            int ti = -1;
            try {
                begin();
                ti = threads.add();
                if (!isOpen())
                    return -1;
                do {
                    n = transferTo0(thisFDVal, position, icount, targetFDVal);
                } while ((n == IOStatus.INTERRUPTED) && isOpen());
                if (n == IOStatus.UNSUPPORTED_CASE) {
                    if (target instanceof SinkChannelImpl)
                        pipeSupported = false;
                    if (target instanceof FileChannelImpl)
                        fileSupported = false;
                    return IOStatus.UNSUPPORTED_CASE;
                }
                if (n == IOStatus.UNSUPPORTED) {
                    // Don't bother trying again
                    transferSupported = false;
                    return IOStatus.UNSUPPORTED;
                }
                return IOStatus.normalize(n);
            } finally {
                threads.remove(ti);
                end (n > -1);
            }
        }
    

    依赖于native方法
    c实现:
    https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/solaris/native/sun/nio/ch/FileChannelImpl.c

    JNIEXPORT jlong JNICALL
    Java_sun_nio_ch_FileChannelImpl_transferTo0(JNIEnv *env, jobject this,
                                                jint srcFD,
                                                jlong position, jlong count,
                                                jint dstFD)
    {
    #if defined(__linux__)
        off64_t offset = (off64_t)position;
        jlong n = sendfile64(dstFD, srcFD, &offset, (size_t)count);
        if (n < 0) {
            if (errno == EAGAIN)
                return IOS_UNAVAILABLE;
            if ((errno == EINVAL) && ((ssize_t)count >= 0))
                return IOS_UNSUPPORTED_CASE;
            if (errno == EINTR) {
                return IOS_INTERRUPTED;
            }
            JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");
            return IOS_THROWN;
        }
        return n;
    #elif defined (__solaris__)
        sendfilevec64_t sfv;
        size_t numBytes = 0;
        jlong result;
    
        sfv.sfv_fd = srcFD;
        sfv.sfv_flag = 0;
        sfv.sfv_off = (off64_t)position;
        sfv.sfv_len = count;
    
        result = sendfilev64(dstFD, &sfv, 1, &numBytes);
    
        /* Solaris sendfilev() will return -1 even if some bytes have been
         * transferred, so we check numBytes first.
         */
        if (numBytes > 0)
            return numBytes;
        if (result < 0) {
            if (errno == EAGAIN)
                return IOS_UNAVAILABLE;
            if (errno == EOPNOTSUPP)
                return IOS_UNSUPPORTED_CASE;
            if ((errno == EINVAL) && ((ssize_t)count >= 0))
                return IOS_UNSUPPORTED_CASE;
            if (errno == EINTR)
                return IOS_INTERRUPTED;
            JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");
            return IOS_THROWN;
        }
        return result;
    #elif defined(__APPLE__)
        off_t numBytes;
        int result;
    
        numBytes = count;
    
    #ifdef __APPLE__
        result = sendfile(srcFD, dstFD, position, &numBytes, NULL, 0);
    #endif
    
        if (numBytes > 0)
            return numBytes;
    
        if (result == -1) {
            if (errno == EAGAIN)
                return IOS_UNAVAILABLE;
            if (errno == EOPNOTSUPP || errno == ENOTSOCK || errno == ENOTCONN)
                return IOS_UNSUPPORTED_CASE;
            if ((errno == EINVAL) && ((ssize_t)count >= 0))
                return IOS_UNSUPPORTED_CASE;
            if (errno == EINTR)
                return IOS_INTERRUPTED;
            JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");
            return IOS_THROWN;
        }
    
        return result;
    #else
        return IOS_UNSUPPORTED_CASE;
    #endif
    }
    

    mmap

    会有陆续的文章进行介绍

    参考

    https://www.linuxjournal.com/article/6345?page=0,0
    https://blog.csdn.net/u013256816/article/details/52589524
    https://en.wikipedia.org/wiki/Zero-copy

  • 相关阅读:
    【08月20日】A股滚动市净率PB历史新低排名
    沪深300指数的跟踪基金最近1年收益排名
    基金前15大重仓股持仓股排名
    中证500指数的跟踪基金最近1年收益排名
    【08月14日】A股ROE最高排名
    【08月13日】预分红股息率最高排名
    【08月09日】北上资金持股比例排名
    最近一月研报推荐次数最多的最热股票
    JDK源码阅读-------自学笔记(二十一)(java.util.ArrayList详细版集合类)
    MyBatis-Plus 3.0.3 Sql注入器添加,即全局配置Sql注入器,sqlInjector改写
  • 原文地址:https://www.cnblogs.com/victor2302/p/11381597.html
Copyright © 2020-2023  润新知