文件映射(mmap)和sendfile和零copy之间的联系和区别

        我们知道,操作系统把内存分为物理内存和虚拟内存,每个进程都有相同的虚拟内存空间,而每个进程中都会维护一个映射表,表中记录这虚拟内存和物理内存的映射关系,也就是说,每一个进程对物理内存都可触达。(虚拟内存有广义和侠义之分,狭义的虚拟内存特指window的虚拟内存和linux的swap,而广义的虚拟内存指对物理内存的一种抽象,虚拟内存的大小和物理内存的大小没有任何关系,侠义的虚拟内存大小可通过命令设置,而广义虚拟内存的大小和计算机的字长有关,字长=可寻址大小或者说CPU可寻址能力大小

 为了进程间内存的隔离,以及屏蔽一些操作系统的核心库,防止程序误操作。操作系统又把虚拟内存进一步划分,分为内核空间用户空间,内核空间是给操作系统内核访问的,而我们在操作系统上运行的应用程序是不能访问的。也就是说,我们的应用程序对磁盘网卡接口等是无感知的。

当我们的程序发出指定,获取磁盘或者网络上资源时,就必然需要经过系统调用,获取外部资源,然后再把资源给应用程序,而这些资源在不同空间之间的转移和复制就是我们说的IO.

 进入正题,假设我们需要从磁盘中获取数据并加工处理后发送到一个外部网络中,我们应该经过如下步骤:

1. 应用程序发起获取文件指令,当该指令被加载到指令寄存器中,CPU从寄存器中读取该指令进行执行,发现是一个系统调用

2. 操作系统进行进行上下文切换,把用户进程当前的上下文信息保存在操作系统进程表中,然后切换到内核空间,开始执行系统调用,当前用户进程挂起等待 从用户空间切换到内核空间

3. 当CPU发现需要从磁盘中获取数据,然后CPU开始进程磁盘文件搬运,把资源从磁盘中加载到内核中。第一次copy

4 加载完毕后发送一个信号中断异常,操作系统开始进行上下文切换,从内核空间切换到用户空间 ,CPU开始把资源从内核空间copy到用户空间 第二次copy

5. 程序对内容进行加工,加工后发送指令,把内容发布到i网上,由于用户空间无法直接进行数据发送,需要进行上下文切换,从用户空间切换到内核空间 ,然后CPU把数据从用户空间copy到内核空间,第三次copy

6 当内核数据准备完毕,CPU又把数据从内核空间copy到网卡缓存中,第四次copy

7 当发送完毕后再进行上下文切换,从内核空间切换到用户空间

从上面步骤可以看出,要把一个磁盘上的文件发布到网络上,需要经过 4次copy和4次上下文切换,这样的效率对计算机来说是很低的。

那么有没有办法减少上下文切换和copy呢?

答案是有的,

不难看出,上次的4此copy,CPU是全程参与的,而在copy期间,当前CPU是无法做任何事情的,对分秒必争的CPU来说,这显然是无法接受的,所以一项DMA(直接储存器访问)技术应运而生。(DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的一台计算机可以有多个DMA通道

引用了DMA,  那么数据从磁盘copy到内核 以及数据从内核copy到网卡,可以直接用DMA来执行。

但是,即使引用了DMA,虽然CPU得到了释放,但是操作系统还是进行了copy。为了减少copy

操作系统有了mmap(文件映射)

操作系统有两种储存器映射方式,以linux为例,分为文件映射(mmap匿名映射

所谓储存器映射是指:通过将一个虚拟存储器区域和一个磁盘上的对象关联起来,以初始化这个虚拟存储器这个区域的内容,这个过程就是储存器映射

文件映射是指:把一个进程的虚拟储存器区域和磁盘上一个文件映射起来,而这些虚拟页面的内容并没有被加载到内存中,当第一次发生缺页异常时,操作系统会把该虚拟页映射的磁盘文件加载到内存中,而由于该虚拟内存也是进程的虚拟内存缺页加载的,本身也是进程内容的一部分,所以被copy到内核空间的文件也在进程内存空间范围内,所以当需要把该区域的数据发送到网卡或者磁盘其他位置时,就无需进行内核到用户空间的copy

匿名映射主要发生在堆栈内存分配时,内核创建匿名文件,包含的内存全部是二进制0 ,然后把以一个虚拟内存区域关联到该匿名文件,其实就是给堆初始化内存空间

通过上面可以得出,mmap需要经过4次上下文切换,1次copy

那么我们能不能把这一次copy也取消掉呢?答案也是有的

sendFile函数

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
		//目的端文件描述符、源文件描述符、源端偏移量、复制数据的长度
		//返回值是实际复制的长度

通过该函数的参数定义可知,sendFile函数传输的是fd(文件描述符)通过文件描述符直接进行数据的copy到目的文件描述符所在文件中,而且这个调用是在内核中进行的,且copy也是DMA执行的。通过这个函数,我们完全可以进行两次用户切换,无需CPU进行数据COPY,就可以把磁盘文件的内容发送到网络中。

需要注意的是,该方法没有经过用户空间,是直接把数据从内核发送到网卡缓存中,也就是说,无法对数据进行加密,且只能从内核把数据发送到网络。

零copy并不是真的不进行数据copy,其实还是需要进行两次数据copy的,只不过这两次都是通过DMA进行copy的,无需CPU参与,所以叫做零copy

mmap和sendFile都是零copy的一种实现方案,只不过不同的场景用不同的方案。


版权声明:本文为qq_29206465原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>