精华内容
下载资源
问答
  • 零拷贝
    千次阅读
    2021-12-05 21:42:14

    零拷贝技术

    概述

    零拷贝技术指在计算机执行操作时,CPU不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及CPU的拷贝时间。它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除CPU的负载。

    实现零拷贝用到的主要技术是DMA数据传输技术和内存区域映射技术

    • 零拷贝机制可以减少数据在内核缓冲区和用户进程缓冲区之间反复的I/O拷贝操作
    • 零拷贝机制可以减少用户进程地址空间之间因为上下文切换而带来的CPU开销

    物理内存和虚拟内存

    由于操作系统的进程之间是共享CPU和内存资源的,因此需要一套完整的内存管理机制防止内存泄漏。

    现代操作系统提供了一种对贮存的抽象概念,即是虚拟内存。虚拟内存为每个进程提供了一个一致的,私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉

    物理内存

    物理内存是相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间

    虚拟内存

    虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存,而实际上,虚拟内存通常是被分割成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来

    虚拟内存地址和用户进程紧密相关,一般来说不同进程里的同一个虚拟地址指向的物理地址是不一样的,每个进程所能使用的虚拟地址大小和CPU位数有关。实际上物理内存可能远远小于虚拟内存的大小,每个用户进程维护了一个单独的页表,虚拟内存和物理内存就是用过这个页表实现地址空间的映射的。

    用户进程申请并访问物理内存的过程

    1. 用户进程向操作系统发出内存申请请求

    2. 系统会检查进程的虚拟地址空间是否被用完,如果有剩余,给进程分配虚拟地址

    3. 系统为这块虚拟地址创建内存映射,并将其放到该进程的页表当中

    4. 系统返回虚拟地址给用户进程,用户进程开始访问该虚拟地址

    5. CPU根据虚拟地址在此进程的页表中找到相应的内存映射,但是这个内存映射没有和物理内存关联,于是产生缺页中断

    6. os受到缺页中断后,分配真正的物理内存并将它关联到了页表相应的内存映射。中断处理完成后CPU就可以访问内存了

      缺页中断并不是每次都会发生的,只有os觉得有必要延迟分配内存的时候才用得着,很多时候会在系统分配真正物理内存并和内存映射进行关联

    虚拟内存的优点

    • 地址空间:提供更大的地址空间,并且地址空间是连续的,使得程序编写,连接更加简单
    • 进程隔离:不同进程的虚拟地址之间没有关系,所以一个进程的操作不会对其他进程造成影响
    • 数据保护:每块虚拟内存都有相应的读写属性,这样就能保护程序的代码段不被修改,数据块不能被执行
    • 内存映射:有了虚拟内存之后,可以直接映射磁盘上的文件到虚拟地址空间
    • 共享内存
    • 物理内存管理

    内核空间和用户空间

    os的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。为了避免用户进程直接操作内核,os讲虚拟内存划分为两部分,一部分是内核空间,一部分是用户空间。在Linux系统中,内核模块运行在内核空间,对应的进程处于内核态,而用户程序运行在用户空间,对应的进程处于用户态。

    内核空间

    内核空间总是驻留在内存中,它是为操作系统的内核保留的。应用程序是不允许直接在该区域进行读写或直接调用内核代码定义的函数的,按访问权限可以分为进程私有和进程共享:

    • 进程私有的虚拟内存:每个进程都有单独的内核栈,页表,task结构以及mem_map结构等
    • 进程共享的虚拟内存:属于所有进程共享的内存区域,包括物理存储器,内核数据和内核代码区域

    用户空间

    每个普通的用户进程都有一个单独的用户空间,处于用户态的进程不能访问内核空间中的数据,也不能直接调用内核函数的,因此要进行系统调用时,就要将进程切换到内核态。用户空间包括以下几个内存区域:

    • 运行时栈:每当一个函数被调用时,该函数的返回类型和一些调用的信息被存储到栈顶,调用结束后调用信息会被弹出并释放掉内存
    • 运行时堆:用于存放进程运行中被动态分配的内存段,位于BSS和栈中间的地址位。堆的效率要比栈低的多
    • 代码段:存放CPU可以执行的机器指令,该部分内存只能读不能写。通常代码区是共享的,其他执行程序可调用它
    • 未初始化的数据段:存放未初始化的全局变量
    • 已初始化的数据段:存放已初始化的全局变量,比如静态全局变量,静态局部变量和常量等
    • 内存映射区域:例如将动态库,共享内存等虚拟空间的内存映射到物理空间的内存

    Linux I/O读写方式

    Linux提供了轮询,I/O中断以及DMA传输这三种磁盘与主存之间的数据传输机制:

    1. 轮询:基于死循环堆I/O端口进行不断检测
    2. I/O中断方式:当数据到达时,磁盘主动向CPU发起中断请求,由CPU自身负责数据的传输过程
    3. DMA传输:在I/O终端的基础上引入DMA磁盘控制器,由DMA磁盘控制器负责数据的传输,降低了I/O终端操作对CPU资源的大量消耗

    I/O中断原理

    在DMA技术出现之前俺,应用程序域磁盘之间的I/O操作都是通过CPU中断完成的。每次用户进程读取磁盘数据时,都需要CPU中断,然后发起I/O请求等待数据读取和拷贝完成,每次的I/O中断都导致CPU的上下文切换

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BtUqpfIh-1638711733230)(C:\Users\Leoyu\AppData\Roaming\Typora\typora-user-images\image-20211130215135794.png)]

    1. 用户进程向CPU发起read系统抵用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回
    2. CPU在接收到指令以后对磁盘发起I/O请求,将磁盘数据先放入磁盘控制器缓冲区
    3. 数据准备完成以后,磁盘向CPU发起I/O中断
    4. CPU受到I/O中断以后将磁盘缓冲歌曲中的数据拷贝到内核缓冲区,再从内核缓冲区拷贝到用户缓冲区
    5. 用户进程切换回用户态,解除阻塞状态

    DMA传输

    DMA的全称叫做直接内存出去(Direct Memory Access),是一种允许外围设备直接访问系统主内存的机制。也就是说,基于DMA访问方式,系统主内存域硬盘或网卡之间的数据传输可以绕开CPU的全程调度。

    整个数据传输操作在一个DMA控制器的控制下进行的。CPU除了在数据传输开始和结束时做一点处理外,在传输过程中CPU可以继续进行其他的工作。这样在大部分时间里,CPU计算和I/O操作都处于并行操作,使整个计算机系统的效率大大提高

    1. 用户进程向CPU发起read系统调用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回
    2. CPU在接收到指令以后对DMA磁盘控制器发起调度指令
    3. DMA磁盘控制器对磁盘发起I/O请求,将磁盘数据先放入磁盘控制器缓冲区,CPU全程不参与此过程
    4. 数据读取完成后,DMA磁盘控制器会接收到磁盘的通知,将数据从磁盘控制器缓冲区拷贝到内核缓冲区
    5. DMA磁盘控制器向CPU发送数据都玩的信号,由CPU负责将数据从内核缓冲区拷贝到用户缓冲区
    6. 用户进程由内核态切换回用户态,解除阻塞状态

    传统I/O操作

    传统的访问方式是通过write和read两个系统调用时显得,通过read函数读取文件到缓冲区中,然后通过write函数把缓存中的数据输出到网络端口

    read(file_fd, tmp_buf, len);
    write(socket_fd, tmp_buf, len);
    

    整个过程涉及2次CPU拷贝,2次DMA拷贝,以及4次上下文切换

    • 上下文切换:当用户程序向内核发起系统调用时,CPU将用户进程从用户态切换为内核态,当系统调用返回时,CPU将用户进程从内核态切换回用户态
    • CPU拷贝:由CPU直接处理数据的传送,数据拷贝时会一直占用CPU的资源
    • DMA拷贝:由CPU向DMA磁盘下达指令,让DMA控制器处理数据传送,传送完毕时把信息反馈给CPU,从而减轻CPU资源的占用率

    零拷贝方式(前面铺垫一大堆,终于是到重点了)

    零拷贝技术主要有三个实现思路:用户态直接I/O,减少数据拷贝次数以及写时复制技术

    • 用户态直接I/O:应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输。直接I/O不存在内核空间缓冲区和用户空间缓冲区之间的数据拷贝
    • 减少数据拷贝次数:在数据传输过程中,避免数据在用户空间缓冲区和系统内核空间缓冲区之间的CPU拷贝,以及数据在系统内核空间内的CPU拷贝,这也是当前的主流零拷贝技术的实现思路
    • 写时复制技术:写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么将其拷贝到自己的进程地址空间,如果只是数据读取则不需要进行拷贝

    用户态直接I/O

    这种方式绕过内核,极大提高了性能

    用户态直接I/O只能适用于不需要内核缓冲区处理的应用程序,这些应用程序通常在进程地址空间有自己的数据缓存机制,由于CPU和磁盘I/O之间的执行时间差距,会造成大量资源的浪费,解决方案是配合异步I/O使用

    mmap+write

    使用mmap+write代替原来的read+write方式,减少了1次CPU拷贝操作。mmap是Linux提供的一种内存映射文件方法,即将一个进程的地址空间中的一段虚拟地址映射到磁盘文件地址

    tmp_buf = mmap(file_fd, len);
    write(socket_fd, tmp_buf, len);
    

    使用mmap的目的是将内核中读缓冲区的地址与用户空间的缓冲区进行映射,从而实现内核缓冲区与应用陈鼓内存的共享,省去了将数据从内核读缓冲区拷贝到用户缓冲区的过程,内核读缓冲区仍需将数据到内核写缓冲区

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qlYjcY5R-1638711733234)(C:\Users\Leoyu\AppData\Roaming\Typora\typora-user-images\image-20211201000703550.png)]

    基于mmap+write的零拷贝方式,整个拷贝过程会发生4次上下文切换,1次CPU拷贝和2次DMA拷贝

    1. 用户进程通过mmap函数向内核发起系统调用,上下文从用户态切换为内核态
    2. 将用户进程的内核空间的读缓冲区与用户空间的缓存区进行内存地址映射
    3. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区(read buffer)
    4. 上下文从内核态切换回用户态,mmap系统调用执行返回
    5. 用户进程通过write函数向内核发起系统调用,上下文从用户态切换为内核态
    6. CPU将读缓冲区中的数据拷贝到的网络缓冲区(socket buffer)
    7. CPU利用DMA控制器将数据从网络缓冲区拷贝到网卡进行数据传输
    8. 上下文奇幻会用户态,write系统调用执行返回

    mmap主要是提高I/O性能,虽然减少了1次拷贝,但是如果mmap一个文件被另一个进程所截获,那么write系统调用会因为访问非法地址被SIGBUS信号终止,SIGBUS会杀死进程,服务器可能会因此被终止

    sendfile

    sendfile系统调用在Linux内核版本2.1中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。sendfile不仅减少了CPU拷贝的次数,还减少了上下文切换的次数

    sendfile(socket_fd, file_fd, len);
    

    通过sendfile系统调用,数据可以直接在内核空间内部进行I/O传输,从而省去了数据在用户空间和内核空间之间的来回拷贝,但是对用户空间是完全不可见

    基于sendfile的零拷贝方式过程会发生2次上下文切换,1次CPU拷贝和2次DMA拷贝:

    1. 用户进程通过sendfile函数向内核发起系统调用,上下文从用户态切换为内核态
    2. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区
    3. CPU将读缓冲区的数据拷贝到网络缓冲区
    4. CPU利用DMA控制器将数据从网络缓冲区拷贝到网卡进行数据传输
    5. 上下文切换回用户态,sendfile系统调用返回

    sendfile存在的问题是用户程序不能对数据进行修改,只是单纯地完成了一次数据传输过程

    sendfile+DMA gather copy

    Linux 2.4版本的内核对sendfile系统调用进行修改,为DMA拷贝引入了gather操作。它将内核空间的读缓冲区中对应的数据描述信息记录到相应的网络缓冲区中,由DMA根据内存地址,地址偏移量将数据批量地从读缓冲区拷贝到网卡设备中,这样就省去了内核空间中仅剩的1次CPU拷贝操作

    sendfile(socket_fd, file_fd, len);
    

    在硬件的支持下,sendfile 拷贝方式不再从内核缓冲区的数据拷贝到 socket 缓冲区,取而代之的仅仅是缓冲区文件描述符和数据长度的拷贝,这样 DMA 引擎直接利用 gather 操作将页缓存中数据打包发送到网络中即可,本质就是和虚拟内存映射的思路类似。

    基于sendfile+DMA gather copy系统调用的零拷贝方式,整个过程会发生2次上下文切换,0次CPU拷贝以及2次DMA拷贝

    1. 用户进程通过sendfile函数向内核发起系统调用,上下文切换为内核态
    2. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区
    3. CPU将读缓冲区的文件描述符和数据长度拷贝到网络缓冲区
    4. 基于文件描述符和数据长度,CPU利用DMA控制器的gather/scatter操作直接批量地将数据从内核的读缓冲区拷贝到网卡进行数据传输
    5. 上下文切换回用户态,sendfile系统调用执行返回

    sendfile+DMA gather copy只适用于将数据从文件拷贝到socket套接字上

    splice

    Linux 在 2.6.17 版本引入 splice 系统调用,不仅不需要硬件支持,还实现了两个文件描述符之间的数据零拷贝。

    splice(fd_in, off_in, fd_out, off_out, len, flags);
    

    splice系统调用可以在内核空间的读缓冲区和网络缓冲区之间建立管道,从而避免了两者之间的CPU拷贝操作

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odtI1T7l-1638711733236)(C:\Users\Leoyu\AppData\Roaming\Typora\typora-user-images\image-20211201004623176.png)]

    基于splice系统调用的零拷贝方式,整个拷贝过程会发生2

    次上下文切换,0次CPU拷贝以及2次DMA拷贝:

    1. 用户进程通过splice函数向内核发起系统调用,上下文切换为内核态
    2. CPU利用DMA控制器将数据从主存或硬盘拷贝至内核的读缓冲区
    3. CPU在内核的读缓冲区和网络缓冲区之间建立起管道
    4. CPU利用DMA控制器将数据从网络缓冲区拷贝至网卡进行数据传输
    5. 上下文切换回用户态,splice系统调用执行返回

    splice 拷贝方式也同样存在用户程序不能对数据进行修改的问题。除此之外,它使用了 Linux 的管道缓冲机制,可以用于任意两个文件描述符中传输数据,但是它的两个文件描述符参数中有一个必须是管道设备。

    写时复制

    写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中。这样做并不影响其他进程对这块数据的操作,每个进程要修改的时候才会进行拷贝,所以叫写时复制

    缓冲区共享

    Solaris上实现的fbuf(fast Buffer)

    fbuf的思想是每个进程都维护着一个缓冲区池,这个缓冲区能被同时映射到用户空间和内核态,内核和用户共享这个缓冲区池

    Linux零拷贝对比

    无论是传统 I/O 拷贝方式还是引入零拷贝的方式,2 次 DMA Copy 是都少不了的,因为两次 DMA 都是依赖硬件完成的。下面从 CPU 拷贝次数、DMA 拷贝次数以及系统调用几个方面总结一下上述几种 I/O 拷贝方式的差别。

    拷贝方式CPU拷贝DMA拷贝系统调用上下文切换
    传统方式(read + write)22read / write4
    内存映射(mmap + write)12mmap / write4
    sendfile12sendfile2
    sendfile + DMA gather copy02sendfile2
    splice02splice2

    Java NIO零拷贝实现

    在Java NIO中的通道(Channel)就相当于操作系统的内核空间的缓冲区,而缓冲区对应的相当于操作系统的用户空间中的用户缓冲区

    • 通道是全双工通信的,既可能是读缓冲区,也可能是网络缓冲区
    • 缓冲区分为堆内存和堆外内存,这是通过malloc()分配出来的用户态内存

    堆外内存(DirectBuffer)在使用后需要应用程序手动回收,而堆内存(HeapBuffer)的数据在GC时可能会被自动回收。因此,在使用HeapBuffer读写数据时,为了避免缓冲区数据因为GC而丢失,NIO会先把HeapBuffer内部的数据拷贝到一个临时的DirectBuffer中的本地内存(native memory),这个拷贝涉及copyMemory()的调用,实现原理和memcpy()类似。最后将临时生成的DirectBuffer内部的数据的内存地址传给I/O调用函数,这样就避免了再去访问Java对象处理I/O读写

    MappedByteBuffer

    MappedByteBuffer是NIO基于内存映射(mmap)这种零拷贝方式的提供的一种实现,它继承自ByteBuffer。FileChannel定义了一个map()方法,它可以把一个文件从position位置开始的size大小区域映射为内存映像文件。

    public abstract MappedByteBuffer map(MapMode mode, long position, long size)
            throws IOException;
    
    • mode:限定内存映射区域对内存映像文件的访问模式,包括只可读,可读可写和写时拷贝三种模式
    • position:文件映射的起始地址,对应内存映射区域的首地址
    • size:文件映射的字节长度,从position往后的字节数,对应内存映射区域的大小

    RocketMQ和Kafka对比

    RocketMQ 选择了 mmap + write 这种零拷贝方式,适用于业务级消息这种小块文件的数据持久化和传输;而 Kafka 采用的是 sendfile 这种零拷贝方式,适用于系统日志消息这种高吞吐量的大块文件的数据持久化和传输。但是值得注意的一点是,Kafka 的索引文件使用的是 mmap + write 方式,数据文件使用的是 sendfile 方式。

    消息队列零拷贝方式优点缺点
    RocketMQmmap + write适用于小块文件传输,频繁调用时,效率很高不能很好的利用 DMA 方式,会比 sendfile 多消耗 CPU,内存安全性控制复杂,需要避免 JVM Crash 问题
    Kafkasendfile可以利用 DMA 方式,消耗 CPU 较少,大块文件传输效率高,无内存安全性问题小块文件效率低于 mmap 方式,只能是 BIO 方式传输,不能使用 NIO 方式
    更多相关内容
  • MySQL零拷贝技术详解
  • qt 多窗口yuv零拷贝gpu渲染 基于 rk356x开发板 ,buildroot系统下 qt 多窗口yuv零拷贝gpu渲染 基于 rk356x开发板 ,buildroot系统下 qt 多窗口yuv零拷贝gpu渲染 基于 rk356x开发板 ,buildroot系统下 qt 多窗口yuv...
  • 本人用的平台是xilinx, zynqMP,提供的资源是参考的源码,可以实现在一块板子上面自动播放视频。 HDMI v4l2进, HDMI DRM出 刚好在做音视频相关的工作,其他资源会在这段时间上传吧。赚点资源分去下载其他人资源。...
  • ZSocket ZSocket是一个包装Linux复制套接字syscall的库,用于在内存映射文件中创建环形缓冲区。 它还包含一些实用程序功能和类型,以帮助一些第2层,第3层和第4层类型。 它与libcap非常相似,除了它易于理解的用于...
  • 零拷贝 零拷贝可让您避免中间缓冲区之间的冗余数据拷贝,并减少用户空间和内核空间之间的上下文切换次数。 当您的硬件(磁盘驱动器、网卡、显卡、声卡)支持DMA (直接内存访问)时,理想的零拷贝(零 CPU 拷贝)是...
  • Linux平台下基于Intel千兆网卡的零拷贝技术的研究与实现.pdf
  • 主要介绍了浅析Linux中的零拷贝技术的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • Kafka-零拷贝

    2021-09-10 17:40:30
    实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 sendfile 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。 Kafka总结 总的来说...

    Kafka除了具备消息队列MQ的特性和使用场景外,它还有一个重要用途,就是做存储层。

    用kafka做存储层,为什么呢?一大堆可以做数据存储的 MySQL、MongoDB、HDFS……

    因为kafka数据是持久化磁盘的,还速度快;还可靠、支持分布式……

    啥!用了磁盘,还速度快!!!

    没错,kafka就是速度无敌,本文将探究kafka无敌性能背后的秘密。

    首先要有个概念,kafka高性能的背后,是多方面协同后、最终的结果,kafka从宏观架构、分布式partition存储、ISR数据同步、以及“无孔不入”的高效利用磁盘/操作系统特性,这些多方面的协同,是kafka成为性能之王的必然结果。

    本文将从kafka零拷贝,探究其是如何“无孔不入”的高效利用磁盘/操作系统特性的。


    先说说零拷贝

    零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。通常是说在IO读写过程中。

    实际上,零拷贝是有广义和狭义之分,目前我们通常听到的零拷贝,包括上面这个定义减少不必要的拷贝次数都是广义上的零拷贝。其实了解到这点就足够了。

    我们知道,减少不必要的拷贝次数,就是为了提高效率。那零拷贝之前,是怎样的呢?

    聊聊传统IO流程

    比如:读取文件,再用socket发送出去
    传统方式实现:
    先读取、再发送,实际经过1~4四次copy。

    buffer = File.read 
    Socket.send(buffer)
    

    1、第一次:将磁盘文件,读取到操作系统内核缓冲区;
    2、第二次:将内核缓冲区的数据,copy到application应用程序的buffer;
    3、第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
    4、第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。

    传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的。实际IO读写,需要进行IO中断,需要CPU响应中断(带来上下文切换),尽管后来引入DMA来接管CPU的中断请求,但四次copy是存在“不必要的拷贝”的。

    重新思考传统IO方式,会注意到实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。

    显然,第二次和第三次数据copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的意义。

    这种场景:是指读取磁盘文件后,不需要做其他处理,直接用网络发送出去。试想,如果读取磁盘的数据需要用程序进一步处理的话,必须要经过第二次和第三次数据copy,让应用程序在内存缓冲区处理。


    为什么Kafka这么快

    kafka作为MQ也好,作为存储层也好,无非是两个重要功能,一是Producer生产的数据存到broker,二是 Consumer从broker读取数据;我们把它简化成如下两个过程:
    1、网络数据持久化到磁盘 (Producer 到 Broker)
    2、磁盘文件通过网络发送(Broker 到 Consumer)

    下面,先给出“kafka用了磁盘,还速度快”的结论

    1、顺序读写
    磁盘顺序读或写的速度400M/s,能够发挥磁盘最大的速度。
    随机读写,磁盘速度慢的时候十几到几百K/s。这就看出了差距。
    kafka将来自Producer的数据,顺序追加在partition,partition就是一个文件,以此实现顺序写入。
    Consumer从broker读取数据时,因为自带了偏移量,接着上次读取的位置继续读,以此实现顺序读。
    顺序读写,是kafka利用磁盘特性的一个重要体现。

    2、零拷贝 sendfile(in,out)
    数据直接在内核完成输入和输出,不需要拷贝到用户空间再写出去。
    kafka数据写入磁盘前,数据先写到进程的内存空间。

    3、mmap文件映射
    虚拟映射只支持文件;
    在进程 的非堆内存开辟一块内存空间,和OS内核空间的一块内存进行映射,
    kafka数据写入、是写入这块内存空间,但实际这块内存和OS内核内存有映射,也就是相当于写在内核内存空间了,且这块内核空间、内核直接能够访问到,直接落入磁盘。
    这里,我们需要清楚的是:内核缓冲区的数据,flush就能完成落盘。


    我们来重点探究 kafka两个重要过程、以及是如何利用两个零拷贝技术sendfile和mmap的。

    网络数据持久化到磁盘 (Producer 到 Broker)

    传统方式实现:

    data = socket.read()// 读取网络数据 
    File file = new File() 
    file.write(data)// 持久化到磁盘 
    file.flush()
    

    先接收生产者发来的消息,再落入磁盘。
    实际会经过四次copy,如下图的四个箭头。

    数据落盘通常都是非实时的,kafka生产者数据持久化也是如此。Kafka的数据并不是实时的写入硬盘,它充分利用了现代操作系统分页存储来利用内存提高I/O效率。

    对于kafka来说,Producer生产的数据存到broker,这个过程读取到socket buffer的网络数据,其实可以直接在OS内核缓冲区,完成落盘。并没有必要将socket buffer的网络数据,读取到应用进程缓冲区;在这里应用进程缓冲区其实就是broker,broker收到生产者的数据,就是为了持久化。

    在此特殊场景下:接收来自socket buffer的网络数据,应用进程不需要中间处理、直接进行持久化时。——可以使用mmap内存文件映射。

    Memory Mapped Files

    简称mmap,简单描述其作用就是:将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。
    它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。

    通过mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),也不必关心内存的大小有虚拟内存为我们兜底。
    使用这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。

    mmap也有一个很明显的缺陷——不可靠,写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘。Kafka提供了一个参数——producer.type来控制是不是主动flush;如果Kafka写入到mmap之后就立即flush然后再返回Producer叫同步(sync);写入mmap之后立即返回Producer不调用flush叫异步(async)。

    Java NIO对文件映射的支持

    Java NIO,提供了一个 MappedByteBuffer 类可以用来实现内存映射。
    MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式。
    FileChannel.map()是抽象方法,具体实现是在 FileChannelImpl.c 可自行查看JDK源码,其map0()方法就是调用了Linux内核的mmap的API。

    使用 MappedByteBuffer类要注意的是:mmap的文件映射,在full gc时才会进行释放。当close时,需要手动清除内存映射文件,可以反射调用sun.misc.Cleaner方法。

    磁盘文件通过网络发送(Broker 到 Consumer)

    传统方式实现:
    先读取磁盘、再用socket发送,实际也是进过四次copy。

    buffer = File.read 
    Socket.send(buffer)

    而 Linux 2.4+ 内核通过 sendfile 系统调用,提供了零拷贝。磁盘数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer(socket buffer),无需 CPU 拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件 - 网络发送由一个 sendfile 调用完成,整个过程只有两次上下文切换,因此大大提高了性能。零拷贝过程如下图所示。

    相比于文章开始,对传统IO 4步拷贝的分析,sendfile将第二次、第三次拷贝,一步完成。

    其实这项零拷贝技术,直接从内核空间(DMA的)到内核空间(Socket的)、然后发送网卡。
    应用的场景非常多,如Tomcat、Nginx、Apache等web服务器返回静态资源等,将数据用网络发送出去,都运用了sendfile。
    简单理解 sendfile(in,out)就是,磁盘文件读取到操作系统内核缓冲区后、直接扔给网卡,发送网络数据。

    Java NIO对sendfile的支持就是FileChannel.transferTo()/transferFrom()。
    fileChannel.transferTo( position, count, socketChannel);
    把磁盘文件读取OS内核缓冲区后的fileChannel,直接转给socketChannel发送;底层就是sendfile。消费者从broker读取数据,就是由此实现。

    具体来看,Kafka 的数据传输通过 TransportLayer 来完成,其子类 PlaintextTransportLayer 通过Java NIO 的 FileChannel 的 transferTo 和 transferFrom 方法实现零拷贝。

    @Override
    public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
       return fileChannel.transferTo(position, count, socketChannel);
    }

    注: transferTo 和 transferFrom 并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 sendfile 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。


    Kafka总结

    总的来说Kafka快的原因:
    1、partition顺序读写,充分利用磁盘特性,这是基础;
    2、Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入;
    3、Customer从broker读取数据,采用sendfile,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送。

    mmap 和 sendfile总结

    1、都是Linux内核提供、实现零拷贝的API;
    2、sendfile 是将读到内核空间的数据,转到socket buffer,进行网络发送;
    3、mmap将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上。
    RocketMQ 在消费消息时,使用了 mmap。kafka 使用了 sendFile。

    零拷贝: Kafka是如何利用零拷贝提高性能的 - sw_kong - 博客园

    原文:Kafka零拷贝 - 知乎

    展开全文
  • 零拷贝是老生常谈的问题啦,大厂非常喜欢问。比如Kafka为什么快,RocketMQ为什么快等,都涉及到零拷贝知识点。最近技术讨论群几个伙伴分享了阿里、虾皮的面试真题,也都涉及到零拷贝。因此本文将跟大家一起来学习...

    前言

    零拷贝是老生常谈的问题啦,大厂非常喜欢问。比如Kafka为什么快,RocketMQ为什么快等,都涉及到零拷贝知识点。最近技术讨论群几个伙伴分享了阿里、虾皮的面试真题,也都涉及到零拷贝。因此本文将跟大家一起来学习零拷贝原理。

    1. 什么是零拷贝

    2. 传统的IO执行流程

    3. 零拷贝相关的知识点回顾

    4. 零拷贝实现的几种方式

    5. java提供的零拷贝方式

    1.什么是零拷贝

    零拷贝字面上的意思包括两个,“零”和“拷贝”:

    • “拷贝”:就是指数据从一个存储区域转移到另一个存储区域。
    • “零” :表示次数为0,它表示拷贝数据的次数为0。

    合起来,那零拷贝就是不需要将数据从一个存储区域复制到另一个存储区域咯。

    零拷贝是指计算机执行IO操作时,CPU不需要将数据从一个存储区域复制到另一个存储区域,从而可以减少上下文切换以及CPU的拷贝时间。它是一种I/O操作优化技术。

    2. 传统 IO 的执行流程

    做服务端开发的小伙伴,文件下载功能应该实现过不少了吧。如果你实现的是一个web程序,前端请求过来,服务端的任务就是:将服务端主机磁盘中的文件从已连接的socket发出去。关键实现代码如下:

    while((n = read(diskfd, buf, BUF_SIZE)) > 0)
        write(sockfd, buf , n);
    

    传统的IO流程,包括read和write的过程。

    • read:把数据从磁盘读取到内核缓冲区,再拷贝到用户缓冲区
    • write:先把数据写入到socket缓冲区,最后写入网卡设备。

    流程图如下:

    • 用户应用进程调用read函数,向操作系统发起IO调用,上下文从用户态转为内核态(切换1)
    • DMA控制器把数据从磁盘中,读取到内核缓冲区。
    • CPU把内核缓冲区数据,拷贝到用户应用缓冲区,上下文从内核态转为用户态(切换2),read函数返回
    • 用户应用进程通过write函数,发起IO调用,上下文从用户态转为内核态(切换3)
    • CPU将用户缓冲区中的数据,拷贝到socket缓冲区
    • DMA控制器把数据从socket缓冲区,拷贝到网卡设备,上下文从内核态切换回用户态(切换4),write函数返回

    从流程图可以看出,传统IO的读写流程,包括了4次上下文切换(4次用户态和内核态的切换),4次数据拷贝(两次CPU拷贝以及两次的DMA拷贝),什么是DMA拷贝呢?我们一起来回顾下,零拷贝涉及的操作系统知识点哈。

    3. 零拷贝相关的知识点回顾

    3.1 内核空间和用户空间

    我们电脑上跑着的应用程序,其实是需要经过操作系统,才能做一些特殊操作,如磁盘文件读写、内存的读写等等。因为这些都是比较危险的操作,不可以由应用程序乱来,只能交给底层操作系统来。

    因此,操作系统为每个进程都分配了内存空间,一部分是用户空间,一部分是内核空间。内核空间是操作系统内核访问的区域,是受保护的内存空间,而用户空间是用户应用程序访问的内存区域。 以32位操作系统为例,它会为每一个进程都分配了4G(2的32次方)的内存空间。

    • 内核空间:主要提供进程调度、内存分配、连接硬件资源等功能
    • 用户空间:提供给各个程序进程的空间,它不具有访问内核空间资源的权限,如果应用程序需要使用到内核空间的资源,则需要通过系统调用来完成。进程从用户空间切换到内核空间,完成相关操作后,再从内核空间切换回用户空间。

    3.2 什么是用户态、内核态

    • 如果进程运行于内核空间,被称为进程的内核态
    • 如果进程运行于用户空间,被称为进程的用户态。

    3.3 什么是上下文切换

    • 什么是CPU上下文?

    CPU 寄存器,是CPU内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此叫做CPU上下文。

    • 什么是CPU上下文切换

    它是指,先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

    一般我们说的上下文切换,就是指内核(操作系统的核心)在CPU上对进程或者线程进行切换。进程从用户态到内核态的转变,需要通过系统调用来完成。系统调用的过程,会发生CPU上下文的切换

    CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。

    3.4 虚拟内存

    现代操作系统使用虚拟内存,即虚拟地址取代物理地址,使用虚拟内存可以有2个好处:

    • 虚拟内存空间可以远远大于物理内存空间
    • 多个虚拟内存可以指向同一个物理地址

    正是多个虚拟内存可以指向同一个物理地址,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样的话,就可以减少IO的数据拷贝次数啦,示意图如下

    3.5 DMA技术

    DMA,英文全称是Direct Memory Access,即直接内存访问。DMA本质上是一块主板上独立的芯片,允许外设设备和内存存储器之间直接进行IO数据传输,其过程不需要CPU的参与

    我们一起来看下IO流程,DMA帮忙做了什么事情.

    • 用户应用进程调用read函数,向操作系统发起IO调用,进入阻塞状态,等待数据返回。
    • CPU收到指令后,对DMA控制器发起指令调度。
    • DMA收到IO请求后,将请求发送给磁盘;
    • 磁盘将数据放入磁盘控制缓冲区,并通知DMA
    • DMA将数据从磁盘控制器缓冲区拷贝到内核缓冲区。
    • DMA向CPU发出数据读完的信号,把工作交换给CPU,由CPU负责将数据从内核缓冲区拷贝到用户缓冲区。
    • 用户应用进程由内核态切换回用户态,解除阻塞状态

    可以发现,DMA做的事情很清晰啦,它主要就是帮忙CPU转发一下IO请求,以及拷贝数据。为什么需要它的?

    主要就是效率,它帮忙CPU做事情,这时候,CPU就可以闲下来去做别的事情,提高了CPU的利用效率。大白话解释就是,CPU老哥太忙太累啦,所以他找了个小弟(名叫DMA) ,替他完成一部分的拷贝工作,这样CPU老哥就能着手去做其他事情。

    4. 零拷贝实现的几种方式

    零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及CPU拷贝的次数。零拷贝实现有多种方式,分别是

    • mmap+write
    • sendfile
    • 带有DMA收集拷贝功能的sendfile

    4.1 mmap+write实现的零拷贝

    mmap 的函数原型如下:

    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    
    • addr:指定映射的虚拟内存地址
    • length:映射的长度
    • prot:映射内存的保护模式
    • flags:指定映射的类型
    • fd:进行映射的文件句柄
    • offset:文件偏移量

    前面一小节,零拷贝相关的知识点回顾,我们介绍了虚拟内存,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,从而减少数据拷贝次数!mmap就是用了虚拟内存这个特点,它将内核中的读缓冲区与用户空间的缓冲区进行映射,所有的IO都在内核中完成。

    mmap+write实现的零拷贝流程如下:

    • 用户进程通过mmap方法向操作系统内核发起IO调用,上下文从用户态切换为内核态
    • CPU利用DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
    • 上下文从内核态切换回用户态,mmap方法返回。
    • 用户进程通过write方法向操作系统内核发起IO调用,上下文从用户态切换为内核态
    • CPU将内核缓冲区的数据拷贝到的socket缓冲区。
    • CPU利用DMA控制器,把数据从socket缓冲区拷贝到网卡,上下文从内核态切换回用户态,write调用返回。

    可以发现,mmap+write实现的零拷贝,I/O发生了4次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝

    mmap是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,所以节省了一次CPU拷贝‘’并且用户进程内存是虚拟的,只是映射到内核的读缓冲区,可以节省一半的内存空间。

    4.2 sendfile实现的零拷贝

    sendfile是Linux2.1内核版本后引入的一个系统调用函数,API如下:

    ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
    
    • out_fd:为待写入内容的文件描述符,一个socket描述符。,
    • in_fd:为待读出内容的文件描述符,必须是真实的文件,不能是socket和管道。
    • offset:指定从读入文件的哪个位置开始读,如果为NULL,表示文件的默认起始位置。
    • count:指定在fdout和fdin之间传输的字节数。

    sendfile表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作,因此可以使用它来实现零拷贝。

    sendfile实现的零拷贝流程如下:

    sendfile实现的零拷贝

    1. 用户进程发起sendfile系统调用,上下文(切换1)从用户态转向内核态
    2. DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
    3. CPU将读缓冲区中数据拷贝到socket缓冲区
    4. DMA控制器,异步把数据从socket缓冲区拷贝到网卡,
    5. 上下文(切换2)从内核态切换回用户态,sendfile调用返回。

    可以发现,sendfile实现的零拷贝,I/O发生了2次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。那能不能把CPU拷贝的次数减少到0次呢?有的,即带有DMA收集拷贝功能的sendfile!

    4.3 sendfile+DMA scatter/gather实现的零拷贝

    linux 2.4版本之后,对sendfile做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点搞零拷贝,即还可以多省去一次CPU拷贝

    sendfile+DMA scatter/gather实现的零拷贝流程如下:

    1. 用户进程发起sendfile系统调用,上下文(切换1)从用户态转向内核态
    2. DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
    3. CPU把内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)发送到socket缓冲区
    4. DMA控制器根据文件描述符信息,直接把数据从内核缓冲区拷贝到网卡
    5. 上下文(切换2)从内核态切换回用户态,sendfile调用返回。

    可以发现,sendfile+DMA scatter/gather实现的零拷贝,I/O发生了2次用户空间与内核空间的上下文切换,以及2次数据拷贝。其中2次数据拷贝都是包DMA拷贝。这就是真正的 零拷贝(Zero-copy) 技术,全程都没有通过CPU来搬运数据,所有的数据都是通过DMA来进行传输的。

    5. java提供的零拷贝方式

    • Java NIO对mmap的支持
    • Java NIO对sendfile的支持

    5.1 Java NIO对mmap的支持

    Java NIO有一个MappedByteBuffer的类,可以用来实现内存映射。它的底层是调用了Linux内核的mmap的API。

    mmap的小demo如下:

    public class MmapTest {
    
        public static void main(String[] args) {
            try {
                FileChannel readChannel = FileChannel.open(Paths.get("./jay.txt"), StandardOpenOption.READ);
                MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
                FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                //数据传输
                writeChannel.write(data);
                readChannel.close();
                writeChannel.close();
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
    }
    
    

    5.2 Java NIO对sendfile的支持

    FileChannel的transferTo()/transferFrom(),底层就是sendfile() 系统调用函数。Kafka 这个开源项目就用到它,平时面试的时候,回答面试官为什么这么快,就可以提到零拷贝sendfile这个点。

    @Override
    public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
       return fileChannel.transferTo(position, count, socketChannel);
    }
    

    sendfile的小demo如下:

    public class SendFileTest {
        public static void main(String[] args) {
            try {
                FileChannel readChannel = FileChannel.open(Paths.get("./jay.txt"), StandardOpenOption.READ);
                long len = readChannel.size();
                long position = readChannel.position();
                
                FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                //数据传输
                readChannel.transferTo(position, len, writeChannel);
                readChannel.close();
                writeChannel.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
    

    展开全文
  • Java IO篇:什么是零拷贝

    万次阅读 多人点赞 2022-01-24 02:06:05
    零拷贝指在进行数据 IO 或传输时,数据在用户态下经历了零次拷贝,并非不拷贝数据。通过减少数据传输过程中 内核缓冲区和用户进程缓冲区间不必要的 CPU数据拷贝 与 用户态和内核态的上下文切换次数,降低 CPU 在这两...

            在介绍零拷贝的IO模式之前,我们先简单了解下传统的IO模式是怎么样的?

    一、传统的IO模式:

    传统的IO模式,主要包括 read 和 write 过程:

    • read:把数据从磁盘读取到内核缓冲区,再拷贝到用户缓冲区
    • write:先把数据写入到 socket缓冲区,最后写入网卡设备

     流程图如下:

    • (1)用户空间的应用程序通过read()函数,向操作系统发起IO调用,上下文从用户态到切换到内核态,然后再通过 DMA 控制器将数据从磁盘文件中读取到内核缓冲区
    • (2)接着CPU将内核空间缓冲区的数据拷贝到用户空间的数据缓冲区,然后read系统调用返回,而系统调用的返回又会导致上下文从内核态切换到用户态
    • (3)用户空间的应用程序通过write()函数向操作系统发起IO调用,上下文再次从用户态切换到内核态;接着CPU将数据从用户缓冲区复制到内核空间的 socket 缓冲区(也是内核缓冲区,只不过是给socket使用),然后write系统调用返回,再次触发上下文切换
    • (4)最后异步传输socket缓冲区的数据到网卡,也就是说write系统调用的返回并不保证数据被传输到网卡

            在传统的数据 IO 模式中,读取一个磁盘文件,并发送到远程端的服务,就共有四次用户空间与内核空间的上下文切换,四次数据复制,包括两次 CPU 数据复制,两次 DMA 数据复制。但两次 CPU 数据复制才是最消耗资源和时间的,这个过程还需要内核态和用户态之间的来回切换,而CPU资源十分宝贵,要拷贝大量的数据,还要处理大量的任务,如果能把 CPU 的这两次拷贝给去除掉,既能节省CPU资源,还可以避免内核态和用户态之间的切换。而零拷贝技术就是为了解决这个问题

    DMA(Direct Memory Access,直接内存访问):DMA 本质上是一块主板上独立的芯片,允许外设设备直接与内存存储器进行数据传输,并且不需要CPU参与的技术

    二、什么是零拷贝:

            零拷贝指在进行数据 IO 时,数据在用户态下经历了零次 CPU 拷贝,并非不拷贝数据。通过减少数据传输过程中 内核缓冲区和用户进程缓冲区 间不必要的CPU数据拷贝 与 用户态和内核态的上下文切换次数,降低 CPU 在这两方面的开销,释放 CPU 执行其他任务,更有效的利用系统资源,提高传输效率,同时还减少了内存的占用,也提升应用程序的性能。

            由于零拷贝在内核空间中完成所有的内存拷贝,可以最大化使用 socket 缓冲区的可用空间,从而提高了一次系统调用中处理的数据量,进一步降低了上下文切换次数。零拷贝技术基于 PageCache,而 PageCache 缓存了最近访问过的数据,提升了访问缓存数据的性能,同时,为了解决机械磁盘寻址慢的问题,它还协助 IO 调度算法实现了 IO 合并与预读(这也是顺序读比随机读性能好的原因),这进一步提升了零拷贝的性能。

    三、Linux 中的零拷贝方式:

    1、mmap + write 实现的零拷贝:

    #include <sys/mman.h>
    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
    • addr:指定映射的虚拟内存地址
    • length:映射的长度
    • prot:映射内存的保护模式
    • flags:指定映射的类型
    • fd:进行映射的文件句柄
    • offset:文件偏移量

            在传统 IO 模式的4次内存拷贝中,与物理设备相关的2次拷贝(把磁盘数据拷贝到内存 以及 把数据从内存拷贝到网卡)是必不可少的。但与用户缓冲区相关的2次拷贝都不是必需的,如果内核在读取文件后,直接把内核缓冲区中的内容拷贝到 Socket 缓冲区,待到网卡发送完毕后,再通知进程,这样就可以减少一次 CPU 数据拷贝了。而 内存映射mmap 就是通过前面介绍的方式实现零拷贝的,它的核心就是操作系统把内核缓冲区与应用程序共享,将一段用户空间内存映射到内核空间,当映射成功后,用户对这段内存区域的修改可以直接反映到内核空间;同样地,内核空间对这段区域的修改也直接反映用户空间。正因为有这样的映射关系, 就不需要在用户态与内核态之间拷贝数据, 提高了数据传输的效率,这就是内存直接映射技术。具体示意图如下:

    • (1)用户应用程序通过 mmap() 向操作系统发起 IO调用,上下文从用户态切换到内核态;然后通过 DMA 将数据从磁盘中复制到内核空间缓冲区
    • (2)mmap 系统调用返回,上下文从内核态切换回用户态(这里不需要将数据从内核空间复制到用户空间,因为用户空间和内核空间共享了这个缓冲区)
    • (3)用户应用程序通过 write() 向操作系统发起 IO调用,上下文再次从用户态切换到内核态。接着 CPU 将数据从内核空间缓冲区复制到内核空间 socket 缓冲区;write 系统调用返回,导致内核空间到用户空间的上下文切换
    • (4)DMA 异步将 socket 缓冲区中的数据拷贝到网卡

            mmap 的零拷贝 I/O 进行了4次用户空间与内核空间的上下文切换,以及3次数据拷贝;其中3次数据拷贝中包括了2次 DMA 拷贝和1次 CPU 拷贝。所以 mmap 通过内存地址映射的方式,节省了数据IO过程中的一次CPU数据拷贝以及一半的内存空间

    2、sendfile 实现的零拷贝:

    #include <sys/sendfile.h>
    ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
    • out_fd:为待写入内容的文件描述符,一个socket描述符。,
    • in_fd:为待读出内容的文件描述符,必须是真实的文件,不能是socket和管道。
    • offset:指定从读入文件的哪个位置开始读,如果为NULL,表示文件的默认起始位置。
    • count:指定在fdout和fdin之间传输的字节数。

           只要我们的代码执行 read 或者 write 这样的系统调用,一定会发生 2 次上下文切换:首先从用户态切换到内核态,当内核执行完任务后,再切换回用户态交由进程代码执行。因此,如果想减少上下文切换次数,就一定要减少系统调用的次数,解决方案就是把 read、write 两次系统调用合并成一次,在内核中完成磁盘与网卡的数据交换。在 Linux 2.1 版本内核开始引入的 sendfile 就是通过这种方式来实现零拷贝的,具体流程图如下:

    • (1)用户应用程序发出 sendfile 系统调用,上下文从用户态切换到内核态;然后通过 DMA 控制器将数据从磁盘中复制到内核缓冲区中
    • (2)然后CPU将数据从内核空间缓冲区复制到 socket 缓冲区
    • (3)sendfile 系统调用返回,上下文从内核态切换到用户态
    • (4)DMA 异步将内核空间 socket 缓冲区中的数据传递到网卡

            通过 sendfile 实现的零拷贝I/O使用了2次用户空间与内核空间的上下文切换,以及3次数据的拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝。那能不能将CPU拷贝的次数减少到0次呢?答案肯定是有的,那就是 带 DMA 收集拷贝功能的 sendfile

    3、带 DMA 收集拷贝功能的 sendfile 实现的零拷贝:

            Linux 2.4 版本之后,对 sendfile 做了升级优化,引入了 SG-DMA技术,其实就是对DMA拷贝加入了 scatter/gather 操作,它可以直接从内核空间缓冲区中将数据读取到网卡,无需将内核空间缓冲区的数据再复制一份到 socket 缓冲区,从而省去了一次 CPU拷贝。具体流程如下:

    •  (1)用户应用程序发出 sendfile 系统调用,上下文从用户态切换到内核态;然后通过 DMA 控制器将数据从磁盘中复制到内核缓冲区中
    • (2)接下来不需要CPU将数据复制到 socket 缓冲区,而是将相应的文件描述符信息复制到 socket 缓冲区,该描述符包含了两种的信息:①内核缓冲区的内存地址、②内核缓冲区的偏移量
    • (3)sendfile 系统调用返回,上下文从内核态切换到用户态
    • (4)DMA 根据 socket 缓冲区中描述符提供的地址和偏移量直接将内核缓冲区中的数据复制到网卡

            带有 DMA 收集拷贝功能的 sendfile 实现的 I/O 使用了2次用户空间与内核空间的上下文切换,以及2次数据的拷贝,而且这2次的数据拷贝都是非CPU拷贝,这样就实现了最理想的零拷贝I/O传输了,不需要任何一次的CPU拷贝,以及最少的上下文切换

    备注:需要注意的是,零拷贝有一个缺点,就是不允许进程对文件内容作一些加工再发送,比如数据压缩后再发送。

    四、零拷贝技术的应用场景:

    1、Java 的 NIO:

    (1)mmap + write 的零拷贝方式:

            FileChannel 的 map() 方法产生的 MappedByteBuffer:FileChannel 提供了 map() 方法,该方法可以在一个打开的文件和 MappedByteBuffer 之间建立一个虚拟内存映射,MappedByteBuffer 继承于 ByteBuffer;该缓冲器的内存是一个文件的内存映射区域。map() 方法底层是通过 mmap 实现的,因此将文件内存从磁盘读取到内核缓冲区后,用户空间和内核空间共享该缓冲区。mmap的小demo如下:

    public class MmapTest {
    
        public static void main(String[] args) {
            try {
                FileChannel readChannel = FileChannel.open(Paths.get("./jay.txt"), StandardOpenOption.READ);
                MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
                FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                //数据传输
                writeChannel.write(data);
                readChannel.close();
                writeChannel.close();
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
    }

    (2)sendfile 的零拷贝方式:

            FileChannel 的 transferTo、transferFrom 如果操作系统底层支持的话,transferTo、transferFrom也会使用 sendfile 零拷贝技术来实现数据的传输

    @Override
    public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
       return fileChannel.transferTo(position, count, socketChannel);
    }

    sendfile 的 demo 如下:

    public class SendFileTest {
        public static void main(String[] args) {
            try {
                FileChannel readChannel = FileChannel.open(Paths.get("./jay.txt"), StandardOpenOption.READ);
                long len = readChannel.size();
                long position = readChannel.position();
                
                FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                //数据传输
                readChannel.transferTo(position, len, writeChannel);
                readChannel.close();
                writeChannel.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
    

    2、Netty 框架:

    Netty 的零拷贝主要体现在下面五个方面:

    (1)在网络通信上,Netty 的接收和发送 ByteBuffer 采用直接内存,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中(为什么拷贝?因为 JVM 会发生 GC 垃圾回收,数据的内存地址会发生变化,直接将堆内的内存地址传给内核,内存地址一旦变了就内核读不到数据了),然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

    (2)在文件传输上,Netty 的通过 FileRegion 包装的 FileChannel.tranferTo 实现文件传输,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。

    (3)在缓存操作上,Netty 提供了CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝。

    (4)通过 wrap 操作,我们可以将byte[]数组、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf对象,进而避免了拷贝操作。

    (5)ByteBuf 支持 slice 操作,因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf,避免了内存的拷贝。

    3、kafka:

    Kafka 的索引文件使用的是 mmap + write 方式,数据文件使用的是 sendfile 方式


    参考文章:

    (重点推荐)https://juejin.cn/post/6887469050515947528

    https://juejin.cn/post/6854573213452599310#heading-8

    https://blog.csdn.net/u022812849/article/details/109805403

    什么是零拷贝?如何实现零拷贝?

    展开全文
  • 零拷贝详解

    2022-05-03 23:47:06
    可参考:《用户态和内核态的区别》 2、零拷贝、浅拷贝、深拷贝 (1)零拷贝 零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。针对操作系统中的设备驱动程序、文件系统以及网络协议堆栈而出现的...
  • Java 零拷贝

    2021-12-16 21:03:03
    Java 零拷贝 参考: Java中的零拷贝 零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。 ...
  • 什么是零拷贝

    千次阅读 多人点赞 2022-03-24 16:37:49
    零拷贝是老生常谈的问题,无论是Kafka还是Netty等都用到了零拷贝的知识,那究竟什么是零拷贝呢 什么是零拷贝 “零”:表示次数是0,它表示拷贝数据的次数是0
  • 零拷贝原理详解

    2021-03-24 10:56:16
    引言 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间...零拷贝( zero-copy )技术可以有效地改善数据传输的性能,在内核驱动
  • Java NIO 与 零拷贝

    2022-04-23 19:49:56
    零拷贝是网络编程的关键,很多性能优化都离不开。在 Java 程序中,常用的零拷贝有 mmap(memory map,内存映射) 和 sendFile。那么它们在 OS(操作系统) 中,到底是怎么样的一个的设计?另外我们看下NIO 中如何使用...
  • 零拷贝源代码

    2013-11-16 12:27:57
    零拷贝的开源代码,包含测试结果,可以研究一下!
  • 磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝、直接 I/O、异步 I/O 等等,这些优化的目的就是为了提高系统的吞吐量,另外操作系统内核中的磁盘...
  • 大白话讲解 零拷贝技术

    千次阅读 2022-03-28 11:09:55
    零拷贝的特点是CPU 不全程负责内存中的数据写入其他组件,CPU 仅仅起到管理的作用。但注意,零拷贝不是不进行拷贝,而是 CPU 不再全程负责数据拷贝时的搬运工作。如果数据本身不在内存中,那么必须先通过某种方式...
  • 在去年秋招的时候,面试腾讯后台开发一面中,就曾问到过零拷贝、如何实现零拷贝、大文件如何传输。在我看来,面试官喜欢针对某一点一直深挖你的知识储备,因此,需要小伙伴们需要对某一内容要有针对性的深入了解和...
  • 零拷贝技术原理分析

    2021-04-25 11:51:58
    轻松掌握Netty、Kafka、RocketMQ高效率读写的核心秘诀--零拷贝技术
  • Java中的零拷贝

    2022-05-11 12:31:57
    在Java程序中,零拷贝技术分为两种:mmap(内存映射)和sendFile,首先要了解零拷贝的概念:所谓的零拷贝不是不拷贝,而是不经过CPU拷贝,它还是需要拷贝的(比如将数据从硬盘拷贝到内核态),这个零拷贝是从操作...
  • 详解零拷贝

    2022-04-09 17:54:31
    零拷贝 文章目录零拷贝前言IO基本原理内核态与用户态系统调用缓冲区进程缓冲区和内核缓冲区DMA技术零拷贝read+writemmap+writesendfile()sendfile()+SG-DMA总结 前言 零拷贝在很多地方都有用到,比如netty、kafka、...
  • Linux零拷贝原理.pdf

    2019-07-31 12:42:24
    linux零拷贝原理
  • Kafka零拷贝

    万次阅读 多人点赞 2019-08-15 19:35:10
    实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 sendfile 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。 Kafka总结 总...
  • Java NIO 零拷贝

    2022-03-12 21:27:03
    一、什么是零拷贝 零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽。 二、零拷贝给我们带来的好处: 减少甚至完全避免不必要的...
  • 底层原理 | 零拷贝

    2020-12-17 14:45:40
    1.1 零拷贝 "零拷贝"中的"拷贝"是操作系统在I/O操作中,将数据从一个内存区域复制到另外一个内存区域。而"零"并不是指0次复制,更多的是指在用户态和内核态之间的复制是0次。 1.2 CPU COPY 通过计算机的组成原理我们...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 133,925
精华内容 53,570
关键字:

零拷贝

友情链接: Modulator.rar