通过零拷贝实现有效数据传输
Last updated
Last updated
考虑这样一种场景:用户发送下载一个文件的请求,服务器收到请求后,将本地磁盘的一个文件原封不动地返回给用户。整个处理过程如下:
第1步:应用程序向系统发送要读取文件的指令(read()),此时上下文由user切换到kernel。系统收到指令后,调用sys_read() 来从文件中读取data(第一次copy),存储在kernel的buffer中。
第2步:data数据从kernel的buffer中被copy到user buffer中,此时read()成功返回,上下文从kernel切换到user。至此,数据存储在user的buffer中。
第3步:应用程序向系统发送要发送数据的指令(send()), 此时上下文由user切换到kernel。系统收到指令后,将data从user的buffer中copy到kernel buffer中。当然,这次的kernel buffer和第一步的buffer是不同的buffer。
第4步:send() 方法返回,此时上下文从kernel切换到user。同时,第四次copy发生,DMA将data从kernel buffer拷贝到protocol engine中。
整个过程中,共发送了4次上下文切换,并将文件数据copy了4次。其中,第1步和第4步的copy由DMA(data memory access)完成,第2步和第3步的copy由CPU完成。
在这个场景中,应用程序实际上只是作为一种低效的中间介质,用来把disk file的data传给socket。假如将这些中间操作都省去,直接把disk的data传输给socket,效率将会大大提高。这就是所谓的零拷贝技术:减少context switch,消除和CPU有关的数据拷贝。
使用零拷贝技术,user层面的使用方法没有变,但是内部原理却发生了变化(Linux 内核 2.4 及后期版本中):
transferTo()方法使得文件内容被copy到了kernel buffer,这一动作由DMA engine完成。
没有data被copy到socket buffer。取而代之的是socket buffer被追加了一些descriptor的信息,包括data的位置和长度。然后DMA engine直接把data从kernel buffer传输到protocol engine,这样就消除了需要占用CPU的拷贝操作。
在上面的场景中,使用零拷贝技术后,消除了CPU来copy的操作(2次),只剩下了DMA的2次copy操作,上下文只需要切换2次。
Java 的零拷贝多在网络应用程序中使用。关键的API是java.nio.channel.FileChannel
的transferTo()/transferFrom()
方法。我们可以用这两个方法来把bytes直接从调用它的channel传输到另一个writable byte channel,中间不会使data经过应用程序。
拷贝文件的示例
通过网络把一个文件从client传到server示例