[转]mmap

转自  https://blog.csdn.net/lvbian/article/details/16341973

    https://www.zhihu.com/question/48161206/answer/110418693

认真分析mmap https://www.cnblogs.com/huxiao-tee/p/4660352.html

linux mmap详解  http://abcdxyzk.github.io/blog/2015/09/11/kernel-mm-mmap/

深入理解内存映射mmap  https://www.cnblogs.com/linhaostudy/p/10632082.html

 1.  

MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联

 /* Allocate some space and setup a DMA mapping */  
    dma_map.vaddr = mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,  
                 MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);  //??
    dma_map.size = 1024 * 1024;  
    dma_map.iova = 0; /* 1MB starting at 0x0 from device view */  
    dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;  
 
    ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
这段code就是将0~1M的这段memory 做iommu map.

2.

mmap_addr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, 0);

#####################################

#####################################

mmap

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

          

由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。

linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:

         

vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。

 mmap四种类型

mmap分为有后备文件的映射和匿名文件的映射,这两种映射又有私有映射和共享映射之分,所以mmap可以创建4种类型的映射

  1. 后备文件的共享映射,多个进程的vm_area_struct指向同一个物理内存区域,一个进程对文件内容的修改,会被其他进程可见。对文件内容的修改会被写回到后备文件。

  2. 后备文件的私有映射,多个进程的vm_area_struct指向同一个物理内存区域,采用写时拷贝的方式,当一个进程对文件内容做修改,不会被其他进程看到。另外对文件内的修改也不会被写回到后备文件。当内存不够需要进行页回收时,私有映射的页被交换到交换区。一般用在加载共享代码库

  3. 匿名文件的共享映射,内核创建一个初始都是0的物理内存区域,然后多个进程的vm_area_struct指向这个共享的物理内存区域,对该区域内容的修改对所有进程可见。匿名文件在页回收时被交换到交换区

  4. 匿名文件的私有映射,内核创建一个初始都是0的物理内存区域,对该区域内容的修改只对创建者进程可见。匿名文件在页回收时被交换到交换区。malloc()底层是用了匿名文件的私有映射来分配大块内存。

比如下面的例子,mmap会涉及到物理内存的变化(加载后备文件到页缓存,或者分配都是0的物理内存块),创建vm_area_struct虚拟内存区域实例,更新页表


// 后备文件的共享映射
fd = open("/home/xxx/a.txt", O_RDWR)
addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARD, fd, 0)
 
// 匿名文件的私有映射
fd = open("/dev/zero", O_RDWR)
addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)

内存映射的用途很多,比如

  1. 后备文件的共享映射可以用作内存映射IO来对大文件进行操作,比普通IO减少一次复制。需要注意的是内存映射IO涉及到内核的很多操作,比如vm_area_struct的创建,页表的修改等等,比普通IO的操作更复杂。小文件的读写使用普通IO更合适

  2. 后备文件的私有映射可以用作共享库二进制文件代码段,数据段的加载

  3. 匿名文件的共享映射可以用作fork时让父子进程共享匿名映射分配的内存

  4. 匿名文件的私有映射可以用作进程的私有内存分配

系统调用mmap()用于共享内存的两种方式:

(1)使用普通文件提供的内存映射:
适用于任何进程之间。此时,需要打开或创建一个文件,然后再调用mmap()

典型调用代码如下:

1
2
fd=open(name, flag, mode); if(fd<0) ...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,可以参看UNIX网络编程第二卷。

(2)使用特殊文件提供匿名内存映射:
适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程 继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。一般来说,子进程单独维护从父进程继 承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。

关于虚拟空间

基于题主继续的问题,我们接着来解释一下为什么我建议你放弃虚拟内存而使用虚拟空间的概念。

 

内存,我们通常指向计算机中的DRAM,上面可以保存数据。为了访问内存,我们对内存进行编址,所有编址的集合,组成内存空间。内存的空间,从总线上看到的结果,我们一般称为物理地址空间,图示如下:

 

你可以看到,物理地址空间不但包括内存,也包括IO,物理空间的大小和地址总线的长度相关,可以远远大于DRAM的实际大小。

 

CPU发起访问内存的操作,需要经过MMU的地址翻译,这个翻译本质上是一个转换算法pa=f(va),图示如下:

虚拟地址的空间和指令集的地址长度有关,不一定和物理地址长度一致,比如现在的64位处理器,从VA角度看来,可以访问64位的地址,但地址总线长度只有48位,所以你可以访问一个位于2^52这个位置的地址,但通过MMU的转化,这个地址可能只会进入很低的物理地址上(当然,你也可以强行转化到更高的地址上,只是那个地址会访问失效或者被截断使用而已)。

 

所以,虚拟空间可以很大,但不表示物理内存也需要很大。每个进程有自己的虚拟空间(切换进程的时候切换MMU的翻译表即可),这些虚拟空间可以映射到物理内存的不同或者相同的位置。示意如下:

 

Linux执行一个程序,这个程序在磁盘上,为了执行这个程序,需要把程序加载到内存中,这时采用的就是mmap,mmap让虚拟空间和文件的内容组成的空间(我这里称为文件空间)对应,类似这样:

上面展示的是mmap之后的效果,但文件的内容在磁盘上是不能被CPU访问的,所以当CPU真的在这个地址上发起读写执行等操作时,OS会进入异常,异常中会调用文件系统把一页或者多页的文件内容加载到物理内存中,这会变成这样:

你可以从/proc/<pid>/maps看到每个进程的mmap状态,下面是一个init(pid=1)进程的maps文件的内容:

这些分段空间后面的那些,就是每个虚拟空间分段对应的文件。这些文件,称为这片虚拟空间的backlog文件,它的作用是当这些内存需要被使用的时候,从磁盘中把对应的文件内容加载到物理内存中。

 这个map表中,部分内存是没有backlog文件的,所有不通过mmap某个文件增加到系统中的用户内存,都是这种类型,比如brk系统调用获得的内存(在很多libc的实现中,malloc通过这个系统调用实现内存增量),这种内存对于这个进程来说,称为“匿名内存”,如果你有swap文件,则Linux会给这些内存分一段swap文件作为匿名内存的backlog文件,这样,当系统内存不足的时候,Linux可以放弃掉一部分物理内存,等后续再从backlog文件中加载,这种backlog文件,有可能是有名文件,也可能是“无名”文件(swap),广告角度说,这个无名文件会被称为虚拟内存,但和你关心的用来加载点什么东西的那个“内存”没有什么关系。

作者:in nek
链接:https://www.zhihu.com/question/48161206/answer/110418693
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/yi-mu-xi/p/12512537.html