1. 典型IO调用
一个典型的IO调用过程如下:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
首先调用read将文件从磁盘读取到tmp_buf,然后调用write将tmp_buf写入到socket,在这过程中会出现四次数据 copy,过程如下图:
- 当调用 read 系统调用时,通过
DMA(Direct Memory Access)将数据copy到内核模式- 然后由
CPU控制将内核模式数据 copy 到用户模式下的 buffer 中- read 调用完成后,write 调用首先将用户模式下 buffer 中的数据 copy 到内核模式下的
socket buffer中- 最后通过
DMA copy将内核模式下的socket buffer中的数据 copy 到网卡设备中传送
从上面的过程可以看出,数据从内核模式到用户模式走了一 圈,浪费了两次copy,而这两次copy都是CPU copy,即占用CPU资源。
2. sendfile zero-copy
Linux 2.1 版本内核引入了 sendfile 函数,用于将文件通过socket传送。
sendfile(socket, file, len);
该函数通过一次系统调用完成了文件的传送,减少了原来 read/write 方式的模式切换。此外更是减少了数据的copy,sendfile的详细过程如下图
- 首先通过
DMA copy将数据从磁盘读取到kernel buffer中- 然后通过
CPU copy将数据从kernel buffercopy到sokcet buffer中- 最终通过
DMA copy将socket buffer中数据 copy 到网卡 buffer 中发送
sendfile 与 read/write 方式相比,少了 一次模式切换和一次 CPU copy。但是从上述过程中也可以发现从 kernel buffer 中将数据 copy 到 socket buffer 是没必要的。
Linux 2.4 内核对 sendfile 做了改进,如下图所示
改进后的处理过程如下:
DMA copy将磁盘数据 copy 到kernel buffer中- 向
socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量DMA gather copy根据socket buffer中的位置和偏移量直接将kernel buffer中的数据 copy 到网卡上。
经过上述过程,数据只经过了 2 次 copy 就从磁盘传送出去了。
3. Java NIO transferTo()
Java NIO中
FileChannel.transferTo(long position, long count, WriteableByteChannel target)
方法将当前通道中的数据传送到目标通道target中,在支持 Zero-Copy 的linux系统中,transferTo() 的实现依赖于 sendfile()调用。

本文介绍了典型IO调用过程中的数据拷贝问题,并对比分析了sendfile和Java NIO transferTo()方法如何减少数据拷贝次数,提升系统性能。
220

被折叠的 条评论
为什么被折叠?



