对于上次提到的mmap+write做一个总结
mmap用到的特性是虚拟内存。
操作:
1.DMA把磁盘内容copy到内核缓冲区。
2.通过虚拟空间映射的方式,用户缓冲区和内核缓冲区共享,减少了一次内核缓冲区到用户缓冲区的cpu拷贝。直接把内核缓冲区数据拷贝到socket缓冲区中,这个过程发生在内核态。
但是由于mmap和write是两个系统调用,还是四次上下文切换,三次拷贝。
分别是:
1.用户调用mmap函数,用户态-->内核态(第一次切换)
DMA控制器把磁盘数据拷贝到内核缓冲区(第一次拷贝)
2.mmap函数返回,内核态—>用户态(第二次切换)、
3.用户发起write函数,用户态-->内核态(第三次切换)
(省略:内核态--->用户态)
CPU把内核缓冲区的数据copy到socket缓冲区(第二次拷贝)
4.DMA控制器把socket缓冲区数据拷贝到网卡(第三次拷贝)
5.write函数调用返回,内核态-->用户态(第四次切换)
相比传统模式,仅仅是通过虚拟内存的应用省了一步把数据从内核态copy到用户态的cpu拷贝步骤。但是仍然不够,进一步优化!
sendfile:
Linux 2.1
sendfile简单来说就是对mmap+wirte进行了综合,两个系统调用变成了一个,上下文切换自然少了两次。我们直接用sendfile函数就可以代替mmap+write
sendfile
方法IO数据对用户空间完全不可见,所以只能适用于完全不需要用户空间处理的情况,比如静态文件服务器。
Linux 2.4
在sendfile的基础上进行了优化操作,DMA Scatter/Gather 分散/收集功能。
直接把读缓存中的内存地址和偏移量交给socket,DMA直接根据socket记录的数值读取到网卡中。少了一次操作。
优化成两次上下文切换和两次拷贝操作。
Linux 2.6.17
splice
splice调用在两个文件描述符之间移动数据,而不需要数据在内核空间和用户空间来回拷贝
他从fd_in
拷贝len
长度的数据到fd_out
,但是有一方必须是管道设备,这也是目前splice
的一些局限性。splice调用利用了Linux提出的管道缓冲区机制, 所以至少一个描述符要为管道。
关于MQ和零拷贝:
消息队列使用零拷贝的场景就是发送和接受消息。生产者发送消息到MQ,然后持久化到磁盘。消费者从MQ读取消息。
RocketMQ:生产和消费都用的mmap+write
Kafka:生产数据持久化到磁盘用的mmap+write,发送数据用sendfile
总结:
由于CPU和IO速度的差异问题,产生了DMA技术,通过DMA搬运来减少CPU的等待时间。
传统的IO read+write
方式会产生2次DMA拷贝+2次CPU拷贝,同时有4次上下文切换。
而通过mmap+write
方式则产生2次DMA拷贝+1次CPU拷贝,4次上下文切换,通过内存映射减少了一次CPU拷贝,可以减少内存使用,适合大文件的传输。
sendfile
方式是新增的一个系统调用函数,产生2次DMA拷贝+1次CPU拷贝,但是只有2次上下文切换。因为只有一次调用,减少了上下文的切换,但是用户空间对IO数据不可见,适用于静态文件服务器。
sendfile+DMA gather
方式产生2次DMA拷贝,没有CPU拷贝,而且也只有2次上下文切换。虽然极大地提升了性能,但是需要依赖新的硬件设备支持。
标签:02,DMA,乔亚,--,mmap,write,内核,缓冲区,拷贝 From: https://www.cnblogs.com/dwj-ngu/p/17125257.html