网络I/O与零拷贝
普通的网络传输步骤如下
- 操作系统将数据从磁盘复制到操作系统内核的页缓存中
- 应用将数据从内核缓存复制到应用的缓存中
- 应用将数据写回内核的Socket缓存中
- 操作系统将数据从Socket缓存区复制到网卡缓存,然后将其通过网络发出
- 当调用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资源
sendfile/零拷贝(kafka用到此特性)
通过sendfile传送文件只需要一次系统调用,当调用 sendfile时:
- 首先通过DMA copy将数据从磁盘读取到kernel buffer中
- 然后通过CPU copy将数据从kernel buffer copy到sokcet buffer中
- 最终通过DMA copy将socket buffer中数据copy到网卡buffer中发送
sendfile与read/write方式相比,少了 一次模式切换一次CPU copy。但是从上述过程中也可以发现从kernel buffer中将数据copy到socket buffer是没必要的
改进版零拷贝
改进后的处理过程如下:
- DMA copy将磁盘数据copy到kernel buffer中
- 向socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量
- DMA gather copy根据socket buffer中的位置和偏移量直接将kernel buffer中的数据copy到网卡上
经过上述过程,数据只经过了2次copy就从磁盘传送出去了。(事实上这个Zero copy是针对内核来讲的,数据在内核模式下是Zero-copy的)。当前许多高性能http server都引入了sendfile机制,如nginx,lighttpd等