一、传统拷贝方式
(一)操作系统经过4次拷贝
CPU 负责将数据从磁盘搬运到内核空间的 Page Cache 中;
CPU 负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区;
CPU 负责将数据从用户空间的缓冲区搬运到内核空间的 Socket 缓冲区中;
CPU 负责将数据从内核空间的 Socket 缓冲区搬运到的网络中;
(二)4 次上下文切换
read 系统调用时:用户态切换到内核态;
read 系统调用完毕:内核态切换回用户态;
write 系统调用时:用户态切换到内核态;
write 系统调用完毕:内核态切换回用户态;
二、DMA参与下的数据拷贝
DMA参与后只是在”拷贝1“的位置,暂时交给DMA来控制,可以暂时释放CPU去做别的事情。
三、mmap+write方式实现的零拷贝
(一)上下文状态切换:
1.用户进程通过mmap方法,向操作系统内核发起IO调用,用户态切换内核态;
2.内核态切换回用户态,mmap方法返回;
3.用户进程通过write方法向操作系统内核发起IO调用,上下文从用户态切换为内核态;
4.数据从socket缓冲区拷贝到网卡结束后,上下文从内核态切换回用户态,write调用返回。
(二)数据拷贝
1. CPU利用DMA控制器,把数据从硬盘中拷贝到内核缓冲区;
2.CPU将内核缓冲区的数据拷贝到socket缓冲区
3.CPU利用DMA控制器,把数据从socket缓冲区拷贝到网卡
mmap+write方式的实现的零拷贝,IO发生了4次用户空间和内核空间的上下文切换,这个并没有节省,但是数据拷贝减少了一次。
其中2次DMA拷贝和1次CPU拷贝。
(mmap是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,所以节省了一次CPU拷贝,并且用户京城内存是虚拟的在,只是映射到内核读缓冲区,可以节省一半的内存空间。)
四、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表示两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作,因此可使用他来实现零拷贝。
(一)上下文状态切换+数据拷贝
1.用户进程发起sendfile系统调用,上下文从用户态转向内核态
2.DMA控制器,把数据从硬盘中拷贝到内核缓冲区;
3.CPU将读缓冲区中的数据拷贝到socket缓冲区;
4.DMA控制器,异步将数据从socket缓冲区拷贝到网卡;
5.上下文从内核态切换回用户态,sendfile返回。
sendfile实现的零拷贝,IO发生了两次用户空间与内核空间的上下文切换,以及3次数据拷贝,其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。那能不能把CPU的拷贝次数减少到0次呢?有的,即带有DMA手机拷贝功能的sendfile
五、sendfile+DMA scatter/gather实现的零拷贝
Linux2.4之后,对sendfile做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点做零拷贝,还可以少一次CPU拷贝。
(一)上下文状态切换+数据拷贝
1.用户进程发起sendfile系统调用,上下文从用户态切换为内核态;
2.DMA控制器,把数据从硬盘中拷贝到内核缓冲区;
3.CPU把内核换乘功能区重点 文件描述信息(内核缓冲区的内存地址和偏移量)发送到socket缓冲区
4.DMA控制器根据文件描述信息,直接把数据从内核缓冲区拷贝到网卡
5.上下文从内核态奇幻回用户态,sendfile调用返回。
可以发现,sendfile+DMA scatter/gather实现的零拷贝,IO发生了2次用户空间与内核空间的上下文切换,以及2次数据拷贝。其中2次数据拷贝都是DMA拷贝,这就是真正的零拷贝技术,全程都没有通过CPU来搬运数据,所有的数据都是通过DMA来传输的。
参考:https://heapdump.cn/article/3290793