浅谈零拷贝
一、零拷贝
1.适用场景
应用程序从硬件设备读取和写入数据,都需要通过内核来进行交互。实际应用中会需要对来请求持久化到硬盘,或者从硬盘读取数据然后发送。整个操作过程中涉及到多次的数据复制和上下文切换。
2.零拷贝概念
零拷贝不是指没有进行数据拷贝,而是指减少需要CPU进行的数据拷贝,主要是为了释放CPU去执行其他任务。
3.零拷贝优点
- 避免CPU数据拷贝,释放CPU去执行其他任务
- 减少用户空间和内核空间上下文切换
- 减少内存占用
二、传统IO-read/write
传统IO主要是应用程序用read系统调用进行数据读取,用write系统调用进行数据发送。相关函数如下:
#include <unistd>
ssize_t write(int filedes, void *buf, size_t nbytes);
ssize_t read(int filedes, void *buf, size_t nbytes);
1.数据读取过程
- 应用进行read系统调用,导致从用户空间切换到内核空间
- 内核线程等待DMA将数据从磁盘拷贝到内核缓冲区
- 内核线程将数据从内核缓冲区拷贝到用户空间缓冲区
- 内核线程返回read系统调用,导致内核空间切换到用户空间
2.数据发送过程
- 应用进行write系统调用,导致从用户空间切换到内核空间
- 内核线程将数据从用户空间缓冲区拷贝到socket缓冲区
- 内核线程触发DMA将数据从socket缓冲区拷贝到网卡
- 内核线程返回write系统调用,导致内核空间切换到用户空间
总结
整个操作过程涉及操作如下:
- 四次用户空间与内核空间上下文切换
- 两次CPU数据拷贝
- 两次DMA数据拷贝
三、零拷贝-mmap/write
用mmap替换read函数,将用户缓冲区与内核缓冲区进行映射,减少CPU拷贝。相关函数如下:
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
1.数据读取过程
- 应用进行mmap系统调用,导致从用户空间切换到内核空间
- 内核线程等待DMA将数据从磁盘拷贝到内核缓冲区
- 内核线程将用户空间映射到内核缓冲区
- 内核线程返回read系统调用,导致内核空间切换到用户空间
2.数据发送过程
- 应用进行write系统调用,导致从用户空间切换到内核空间
- 内核线程将数据从内核缓冲区拷贝到socket缓冲区
- 内核线程触发DMA将数据从socket缓冲区拷贝到网卡
- 内核线程返回write系统调用,导致内核空间切换到用户空间
总结
整个操作过程涉及操作如下:
- 四次用户空间与内核空间上下文切换
- 一次CPU数据拷贝,将用户空间映射到内核缓冲区减少一次CPU数据拷贝
- 两次DMA数据拷贝
四、零拷贝-sendfile
这里是使用sendfile替换mmap/read+write函数调用,减少两次上下文切换。
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
1.执行过程
- 应用进行senfile系统调用,导致从用户空间切换到内核空间
- 内核线程等待DMA将数据从磁盘拷贝到内核缓冲区
- 内核线程将数据从内核缓冲区拷贝到socket缓冲区
- 内核线程触发DMA将数据从socket缓冲区拷贝到网卡
- 内核线程返回senfile系统调用,导致内核空间切换到用户空间
总结
整个操作过程涉及操作如下:
- 两次用户空间与内核空间上下文切换
- 一次CPU数据拷贝
- 两次DMA数据拷贝
五、零拷贝-sendfile(带DMA收集拷贝功能)
1.执行过程
- 应用进行senfile系统调用,导致从用户空间切换到内核空间
- 内核线程等待DMA将数据从磁盘拷贝到内核缓冲区
- 内核线程将内核缓冲区描述符复制到socket缓冲区
- 内核线程触发SG-DMA将根据描述符将数据从内核缓冲区拷贝到网卡
- 内核线程返回senfile系统调用,导致内核空间切换到用户空间
总结
整个操作过程涉及操作如下:
- 两次用户空间与内核空间上下文切换
- 不需要CPU数据拷贝
- 两次DMA数据拷贝
总结
零拷贝主要是通过Linux的不同函数,在数据读取和发送过程中,减少CPU参与的数据拷贝次数和上下文切换。