零拷贝技术是指在计算机操作中,cpu不需要将数据从一个存储区域复制到另一个存储区域,从而减少上下文切换和cpu的拷贝时间。其主要作用是在数据传输过程中减少拷贝次数和系统调用,实现cpu的零参与,彻底消除cpu在这方面的负载。零拷贝技术主要依赖于dma数据传输技术和内存区域映射技术。
零拷贝技术可以减少数据在内核缓冲区和用户缓冲区之间的反复I/O拷贝操作,以及用户进程地址空间和内核地址空间之间因上下文切换带来的CPU开销。Linux中提供了轮询、IO中断和DMA传输三种磁盘与主存之间的数据传输机制。
传统的IO读写方式包括两次CPU拷贝和两次DMA拷贝,经过了四次上下文切换。
在DMA出现之前,IO操作通过CPU中断完成,每次读取磁盘数据时都需要CPU中断并等待数据读取和拷贝完成,导致CPU的上下文切换。
DMA(直接内存访问)是一种允许外设直接访问系统主存的机制,数据传输可以绕开CPU调度,大多数硬件设备都支持DMA技术。
整个DMA数据传输操作在DMA控制器的控制下进行,CPU仅在传输开始和结束时做中断处理,传输过程中CPU可以继续其他工作,提高系统效率。
零拷贝在Linux中的实现主要有三种思路:
- 用户态直接IO:应用程序直接访问硬件存储,数据直接从硬件传输到用户空间,减少数据拷贝次数。
- mmap + write:使用mmap将内核读缓冲区与用户缓冲区映射,减少了一次CPU拷贝操作。
tmp_buf = mmap(file_fd, len); write(socket_fd, tmp_buf, len);
- sendfile:sendfile系统调用在Linux内核2.1中引入,简化了网络数据传输过程,减少了CPU拷贝和用户与内核态转换次数。
sendfile(socket_fd, file_fd, len);
sendfile在Linux2.4版本中引入DMA gather操作,进一步减少了CPU拷贝操作。
sendfile(socket_fd, file_fd, len);
- splice:在Linux2.6.17版本引入的splice系统调用,可以在两个文件描述符之间实现零拷贝,不需要硬件支持。
splice(fd_in, off_in, fd_out, off_out, len, flags);
- 写时拷贝:当多个进程共享数据时,只有在需要修改数据时才进行拷贝操作。
无论是传统IO还是零拷贝方式,两次DMA拷贝是必需的。以下是几种IO拷贝方式的对比:
在应用案例中,rocketmq使用mmap + write方式,适用于小块文件的数据持久化和传输;kafka使用sendfile方式,适用于大块文件的高吞吐量数据传输,但其索引文件使用mmap + write方式。