传统文件传输过程
Linux传统文件传输流程
第一次拷贝:将磁盘上的数据拷贝到内核缓冲区
第二次拷贝(read方法):将内核缓冲区的数据拷贝到用户缓冲区,这个时候应用程序就可以访问/操作这部分数据了
第三次拷贝(write方法):将用户缓冲区的数据拷贝到内核缓冲区
第四次拷贝:将内核缓冲区的数据拷贝到网卡
上面这个过程我们可以发现CPU参与了每一次拷贝的过程。然而CPU是非常宝贵的资源,而且CPU访问内存的速度远远高于磁盘准备数据的速度,上面这个流程是非常低效的。
CPU拷贝数据的方式1:IO轮询
CPU通过轮询的方式来检测IO设备的忙闲,CPU大量的时间都浪费在等待和循环检测上。
- 优点:实现简单
- 缺点:效率低下
CPU拷贝数据的方式2:IO中断
CPU在等待的时间内先去执行其他任务,外设在完成工作后通过中断机制通知CPU将来进行数据拷贝。
- 优点:一定程度上释放了CPU资源
- 缺点:大数据量传输的情况下,CPU会反复中断
CPU拷贝数据的方式3:DMA传输
DMA芯片是设计用来单独处理IO数据传输的,只有当DMA完成了整体的数据块之后,才会中断通知CPU,从CPU的角度看是异步非阻塞的
- 优点:彻底减少了一次CPU拷贝
- 缺点:依赖设备硬件支持
零拷贝的实现方式
零拷贝的意义
我们发现虽然引进了DMA,解放了CPU从IO设备缓冲区拷贝到内核缓冲区的操作,但从内核缓冲区到用户缓冲区的拷贝仍然需要CPU的介入,带来的CPU上下文的开销也是无法避免的。是否存在一种新的方式,减少甚至去掉用户态到内核态之间的拷贝操作呢?
零拷贝的定义
零拷贝并不是0次拷贝数据,而是:
- 减少用户空间和内核空间之间的CPU 拷贝次数
- 减少上下文切换次数
零拷贝方案1--mmap+write
mmap本质上是一种进程虚拟内存映射的方法,将一个文件/一段物理内存映射到进程的虚拟内存地址空间,之后进程就可以用指针的方式读取这一段物理内存。
- 首先用户进程调用mmap方法,向os发起系统调用,此时发生cpu上下文切换到内核态,mmap在用户缓冲区和内核缓冲区之间建立起内存地址映射。
- CPU利用DMA控制器发起数据IO,把数据从硬盘拷贝到内核缓冲区。mmap系统调用结束,cpu上下文切换到用户态
- 用户进程对数据进行加工/修改后,调用write系统调用cpu上下文切换到内核态
- cpu直接把数据从内核缓冲区拷贝到socket缓冲区。
- 最后,CPU利用DMA控制器把数据从socket缓冲区拷贝到网卡。
适用场景:适合大文件传输,小文件会产生内存空间的浪费
开销:4次上下文切换+4次cpu拷贝
零拷贝方案2--sendfile
如果只是想把磁盘上的文件原封不动地转发地话,就没有必要将文件从内核空间和用户空间来回进行数据拷贝。sendfile系统调用会直接将内核缓冲区的数据拷贝到socket缓冲区中。
开销:2次上下文切换+1次cpu拷贝
零拷贝方案2升级--sendfile+gather
sendfile+gather这种方式是方案2的一个升级,DMA会直接把数据从内核缓冲区拷贝到网卡上,从而消除了最后一次cpu拷贝。
开销:仅仅2次上下文切换。但是需要硬件支持
零拷贝方案3--splice
splice系统调用会在内核缓冲区和socket缓冲区之间建立一个管道,同时将写端绑定到内核缓冲区中,读端绑定到socket缓冲区中,通过管道来进行文件拷贝工作。
和senfile不同的是splice允许任何两个文件之间互相连接,而不仅仅是文件描述符之间进行数据传输。Linux2.6.13之后,sendfile功能没有了,底层是splice机制
开销:仅仅2次上下文切换
总结
引用外部资料
字节青训营课程--从零拷贝视角看性能优化 juejin.cn/course/byte… bytedance.feishu.cn/file/AgBobX…