admin管理员组文章数量:1444631
【Linux内核】零拷贝技术
零拷贝技术
什么是零拷贝
零拷贝(Zero-copy) 技术指在计算机执行操作时,CPU不需要先将数据从一个缓冲区复制到另一个缓冲区当中,也就是CPU不参与内存拷贝的过程,从而可以减少上下文切换以及CPU的拷贝时间。
它的作用是在数据报从网卡到用户态缓冲区加载的过程中,减少拷贝的次数,减少系统调用,实现CPU的0参与,彻底消除CPU在这方面的负载。实现零拷贝用到的最主要的技术是 DMA数据传输技术 和 内存区域映射技术。
- 零拷贝可以减少数据在内核缓冲区和用户缓冲区之间反复的 I/O 拷贝操作。
- 零拷贝可以减少用户进程地址空间和内核地址空间之间因为上下文切换(用户态和内核态的切换)带来的CPU开销。
Linux中IO读写方式
Linux中提供了轮询、IO中断以及DMA传输这三种磁盘与主存之间的数据传输机制。其中:
- 轮询方式是基于死循环IO端口进行不断检测。
- IO中断方式是指当数据到达时,磁盘主动向CPU发起中断请求,由CPU自身负责数据的传输过程。
- DMA传输则在IO中断的基础上引入了DMA磁盘控制器,由DMA磁盘控制器负责数据的传输,降低了IO中断操作对CPU资源的大量消耗。
传统IO读写方式图:
从上图我们可以看到,传统的IO读写方式进行了 两次CPU拷贝+两次DMA拷贝,并且经过了四次的上下文切换。
IO中断方式原理
在DMA出现之前,应用程序与磁盘之间的IO操作都是通过CPU中断来完成的。每次用户进程读取磁盘数据时,都需要CPU中断,然后发起IO请求等待数据读取和拷贝完成,而每次的IO终端都导致CPU的上下文切换。
DMA传输原理
DMA的全称叫做 直接内存访问(Direct Memory Access),是一种允许 外设 直接访问系统主存的机制。也就是说,基于DMA的访问方式,系统主存在 硬盘 或 网卡 之间的数据传输可以全程绕开CPU的调度。目前大多数的硬件设备都支持DMA技术。
整个数据传输操作都是在一个DMA控制器的控制下进行的。CPU除了在数据传输开始和结束时做一点处理之外(开始和结束时做中断处理),在传输过程中CPU可以继续进行其他的工作。这样在大部分的时间里,CPU计算和IO操作都处于并行操作,使得整个计算机系统的效率大大提高。
DMA的方式就类似于我们的异步操作,CPU向DMA发起指令,让DMA进行IO操作,最后CPU只需要获取结果即可。
DMA数据读取流程如下:
1、用户进程向CPU发起read系统调用读取数据,由于用户态切换为内核态,read一直阻塞等待数据的返回。 2、CPU在接收到指令以后对DMA磁盘控制器发起调度指令。 3、DMA磁盘控制器对磁盘发起IO请求,将磁盘数据先放入磁盘控制器缓冲区,CPU全程不参与此过程。 4、数据读取完毕后,DMA磁盘控制器会接收到磁盘的通知,将数据从磁盘控制器缓冲区拷贝到内核缓冲区。 5、DMA磁盘控制器向CPU发出数据读完的信号,由CPU负责将数据从内核缓冲区拷贝到用户缓冲区。 6、用户进程由内核态切换回用户态,解除阻塞状态,然后等待CPU的下一个执行时间片。
零拷贝原理
在Linux中零拷贝技术主要有3个实现思路:
- 用户态直接IO:应用程序可以直接访问硬件存储,OS kernel 只是辅助数据传输。这种方式依旧存在于用户空间和内核空间的切换,硬件上的数据直接拷贝至用户空间,不经过内核空间。因此,直接IO不存在内核缓冲区和用户缓冲区之间的数据拷贝。
- 减少数据拷贝次数:在数据传输过程中,避免数据在用户缓冲区和内核缓冲区之间的CPU拷贝,以及数据在系统内核空间的CPU拷贝,这也是当前主流零拷贝技术实现思路。
- 写时拷贝技术:写时拷贝指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么将其拷贝到自己的进程地址空间中,如果只是数据读取操作则不需要进行拷贝操作。
用户态直接IO
用户态直接IO 使得应用进程或运行在用户态下的库函数直接访问硬件设备,数据直接 跨过内核进行传输, 内核在数据传输过程中除了进行必要的虚拟存储配置工作外,不参与任何其他工作,这种方式能够直接绕过内核,极大提高了性能。
用户态直接IO 只能适用于不需要内核缓冲区处理的应用程序,这些应用程序通常在进程地址空间有自己的数据缓存机制,称为自缓存应用程序,数据库管理系统就是一个代表。其次,这种零拷贝机制会直接操作磁盘IO,由于CPU和磁盘IO之间的执行时间差距,会造成大量资源的浪费,解决方案是配合异步IO使用。
mmap + write
一种零拷贝方式是使用 mmap + write 代替原来的 read + write 方式,减少了一次 CPU拷贝操作。mmap是Linux提供的一种内存映射文件方法,即 将一个进程地址空间中的一段虚拟地址映射到磁盘文件地址,mmap + write 伪代码如下:
代码语言:javascript代码运行次数:0运行复制tmp_buf = mmap(file_fd, len);
write(socket_fd, tmp_buf, len);
使用mmap的目的是将内核中读缓冲区(read buffer)的地址与用户空间的缓冲区(user buffer)进行映射,从而实现内核缓冲区与应用程序内存的共享,省去了将数据从内核读缓冲区(read buffer)拷贝到用户缓冲区(user buffer)的过程,然而内核读缓冲区(read buffer)仍然需要将数据拷贝到写缓冲区(socket buffer):
看图就很简单,就是用户缓冲区与内核读缓冲区共用了同一块空间(使用map系统调用),这样就不需要用户或者内核进行read了,也就减少了一次CPU的拷贝过程。
基于mmap + write 系统调用的零拷贝方式,整个拷贝过程会发生4次上下文切换,1次CPU拷贝和2次DMA拷贝,用户读写数据流程如下:
1、用户进程通过 mmap() 函数向内核发起系统调用,从用户态切换为内核态。 2、将内核缓冲区(read buffer)与用户态缓冲区进行内存地址映射, 3、CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区。 4、内核态切换为用户态,mmap系统调用执行返回。 5、用户进程通过write()函数向内核发起系统调用,从用户态切换为内核态。
mmap 主要用处是提高IO性能,特别是针对大文件。对于小文件,内核映射文件反而会导致碎片空间的浪费,因为内存映射总是要求对齐边界,最小单位是4kb,一个5kb的文件将会映射占用8kb内存,就会浪费3kb内存。
mmap的拷贝虽然减少了1次拷贝,提升了效率,但也存在一些隐藏问题。当mmap一个文件时,如果这个文件被另一个进程所截获,那么write系统调用会因为访问非法地址被 SIGBUS 信号终止,SIGBUS 默认会杀死进程并产生一个coredump,服务器可能因此被终止。
sendfile
sendfile系统调用在Linux内核版本2.1中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。sendfile系统调用的引入,不仅减少了CPU拷贝的次数,还减少了用户与内核态转换的次数。
代码语言:javascript代码运行次数:0运行复制sendfile(socket_fd, file_fd, len);
通过sendfile系统调用,数据可以直接在内核空间进行IO传输,从而省去了数据在用户空间和内核空间来回拷贝。与mmap内存映射不同的是,sendfile调用中IO数据对用户空间是完全不可见的。也就是说,这是一次完全意义上的数据传输过程。
sendfile + DMA gather copy
Linux2.4版本的内核对sendfile系统调用进行修改,为DMA拷贝引入了 gather 操作。它将内核空间读缓冲区对应的数据描述信息(内存地址、地址偏移量)记录到相应的网络缓冲区中,由DMA根据内存地址、地址偏移量将数据批量地从读缓冲区拷贝到网卡中,这样就省去了内核空间中仅剩的1次CPU拷贝操作。
代码语言:javascript代码运行次数:0运行复制sendfile(socket_fd, file_fd, len);
在硬件的支持下,sendfile拷贝方式不再从内核缓冲区的数据拷贝到socket缓冲区,取而代之的仅仅是缓冲区文件描述符和数据长度的拷贝,这样DMA引擎直接利用gather操作将页缓存中数据打包发送到网络中即可,本质就是虚拟内存映射的思路类似。
sendfile + DMA gather copy 拷贝方式同样存在用户程序不能对数据进行修改的问题,而且本身需要硬件的支持,它只适用于将数据从文件拷贝到socket套接字上的传输过程。
splice
sendfile只适用于将数据从文件拷贝到socket上,同时需要硬件支持,这也限定了它的使用范围。Linux在2.6.17版本引入 splice 系统调用,不仅不需要硬件支持,还实现了两个文件描述符之间的数据零拷贝。
代码语言:javascript代码运行次数:0运行复制splice(fd_in, off_in, fd_out, off_out, len, flags);
splice系统调用可以在内核空间的读缓冲区和网络缓冲区间建立管道,从而避免了两者之间的 CPU 拷贝操作。
splice拷贝方式也同样存在用户程序不能对数据进行修改的问题。除此之外,它使用了Linux的管道缓存机制,可以用于任意两个文件描述符中传输数据,但是它的两个文件描述符参数中有一个必须是管道设备。
写时拷贝
缓冲区共享
缓冲区共享的难度在于管理共享缓冲区池需要应用程序、网络软件以及设备驱动之间紧密配合,而且如何改写API目前还处于试验阶段并不成熟。
Linux零拷贝对比
无论是传统IO拷贝方式还是引入零拷贝方式,2次DMA copy是少不了的,因为两次DMA都是依赖硬件完成的。下面从CPU拷贝次数、DMA拷贝次数以及系统调用几个方面总结一下上述几种IO拷贝的差别:
应用案例
RocketMQ 和 KafKa零拷贝:
RocketMQ选择了mmap + write 这种零拷贝方式,适用于业务级消息这种小块文件的数据持久化和传输。而KafKa采用的是sendfile这种零拷贝方式,适用于系统日志消息这种高吞吐量的大块文件的数据持久化和传输。但是值得注意的一点是,KafKa的索引文件使用的是mmap + write 方式,数据文件使用的是sendfile方式。
本文标签: Linux内核零拷贝技术
版权声明:本文标题:【Linux内核】零拷贝技术 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1748200857a2825160.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论