文章字数:548,阅读全文大约需要2分钟
归纳自原文。
从文件读取信息并发送到其它服务器的过程中普通文件读取和零拷贝的区别
传统方式
File.read(file, buf, len)
读取文件内容到缓冲区buf
里Socket.send(socket, buf, len)
发送数据
看上去数据只经过两次复制: 文件->buf->socket的缓冲区
其实底层步骤是
- 调用
read()
方法,DMA(direct memory access 直接内存存取)
会现将数据存储到内核空间的读取缓冲区。 read()
方法调用返回时,因为应用程序需要操作此数据(即赋值给buf
),因此触发了一次上下文切换(内核态->用户态)。数据被拷贝到了用户地址空间。(cpu
需要参与操作)- 调用
send()
方法,此时又触发了一次上下文切换(用户态->内核态)。buf
里的数据被拷贝到与目标套接字相关的内核空间缓冲区,此操作需要cpu
参与 send()
方法调用返回前会进行最后一次拷贝,由DMA(direct memory access 直接内存存取)
将数据从缓冲区传到协议引擎进行发送。
也就是总共进行了四次拷贝操作
dma->cpu->cpu->dma
零拷贝方式
传统方式中cpu
的两次操作其实是多余的。我们只是发送数据,不需要对于数据进行操作。所以无需进入用户态,直接在内核态进行数据转移即可。
FileChannel
类的transferTo()
可以实现将在两个内核缓冲区中搭建一个传递通道,可以将传统方式的两次cpu
操作转换成一次cpu
操作,即transferTo()
。
从操作系统角度来看,数据只在内核空间内传递就已经算是零拷贝。
内核需要复制的原因是因为通用硬件DMA
访问需要连续的内存空间(因此需要缓冲区)。 但是,如果硬件支持scatter-and-gather
,这是可以避免的。
即内部会自动将内核的数据之间发送给套接字引擎。即内核区域只存在一份数据。