内存映射mmap是Linux内核的一个重要机制,它和虚拟内存管理以及文件IO都有直接的关系,这篇细说一下mmap的一些要点。

修改(2015-11-12):Linux的虚拟内存管理是基于mmap来实现的。vm_area_struct是在mmap的时候创建的,vm_area_strcut代表了一段连续的虚拟地址,这些虚拟地址相应地映射到一个后备文件或者一个匿名文件的虚拟页。一个vm_area_struct映射到一组连续的页表项。页表项又指向物理内存page,这样就把一个文件和物理内存页相映射。

来理解一下虚拟地址映射的过程:拿到一个虚拟地址,根据已有的vm_area_struct看这个虚拟地址是否属于某个vm_area_struct

  • 如果没有匹配到,就报段错误,访问了一个没有分配的虚拟地址。
  • 如果匹配到了vm_area_struct,根据虚拟地址和页表的映射关系,找到对应的页表项PTE,如果PTE没有分配,就报一个缺页异常,去加载相应的文件数据到物理内存,如果PTE分配,就去相应的物理页的偏移位置读取数据

所以虚拟页的三种状态的实际含义如下:

  • 未分配虚拟页,指的是没有使用mmap建立vm_area_struct,所以也就没有对应到具体的页表项
  • 已分配虚拟页,未映射到物理页,指的是已经使用了mmap建立的vm_area_struct,可以映射到对应的页表项,但是页表项没有指向具体的物理页
  • 已分配虚拟页,已映射到物理页,指的是已经使用了mmap建立的vm_area_struct,可以映射到对应的页表项,并且页表项指向具体的物理页

mmap要么映射到一个后备文件,要么映射到一个匿名文件。操作系统分配物理内存时实际用到了匿名文件的mmap。

mmap和虚拟内存管理

先来看看Linux内核的用户进程虚拟内存管理。内核定义了mm_struct结构来表示一个用户进程的虚拟内存地址空间。

  1. start_code, end_code指定了进程的代码段的边界,start_data, end_data指定了进程数据段的边界。在ELF二进制文件映射到虚拟内存地址空间后,这几个长度就不会再改变

  2. start_brk, brk指定了堆的边界。start_brk表示堆的起始地址,在进程整个生命周期不会改变,brk表示堆的结束位置,会随着堆的长度改变而改变

  3. stack_top指定了栈的起始位置,一般是

  4. task_size指定了用户进程地址空间的长度,也就是用户进程地址空间的顶部边界

  5. mmap_base指定了用户进程虚拟地址空间中用作内存映射部分的地址的基地址,这个位置不是随机的,通常是TASK_SIZE / 3位置处

这里各个区域的地址都是用户进程的虚拟地址,用户进程使用虚拟地址和页表结构来访问内存

  1. 首先根据所在区域的虚拟地址转换成对应的页表数组的数组项索引,找到页表索引最后定为到PTE中保存的物理内存页的页号,加上虚拟地址低12位的offset来确定一个唯一的物理内存地址

  2. 如果物理内存地址所在的页存在,就返回该物理地址存放的内容。如果不存在就触发缺页异常。虚拟内存管理采用按需分配 + 缺页异常机制来管理页表项和分配对应的物理内存页。当一个虚拟地址对应的页表项不存在时,先创建页表结构,再分配物理内存页,再修改页表

进程的mm_struct除了包含虚拟内存地址空间布局的信息,还包含了虚拟内存区域vm_area_struct的信息。虚拟内存区域vm_area_struct是内核管理用户进程虚拟地址空间的方式,实际上数据段,文本段,共享库这些都是通过vm_area_struct来管理的

vm_area_struct由两种组织形式,一种是单链表,包含了所有创建的vm_area_struct实例,一种是红黑树结构,加速区域的查找。这两个数据结构都是面向同一份vm_area_struct实例,只是组织形式不同。

再考虑一下vm_area_struct和页表的关系,vm_area_struct本质上是一段用户进程的虚拟地址,而我们知道虚拟地址和页表数组的索引是对应的,页表数组的最后一级PTE数组的数组项存放着物理内存页的页号,这样就建立了虚拟内存地址到物理内存地址的对应关系。

  1. 有一种情况是先有虚拟地址,再由访问虚拟地址引起缺页异常去加载物理内存,再更新页表建立虚拟地址,页表,物理内存三者的联系

  2. 有一种情况(mmap)是先从设备加载文件,建立address_space,页缓存(物理内存),再创建vm_area_struct结构,更新页表,返回虚拟地址。

vm_area_struct的结构体如下

  1. vm_start, vm_end表示区域的开始位置和结束位置,确定了区域的边界。两个vm_area_struct不会出现交叉的情况

  2. vm_page_prot 表示这个区域的页的访问权限

  3. shared结构处理有后备文件的内存映射,和后备文件的address_space地址空间关联起来

  4. anon_vma_node, anon_vma处理匿名文件共享内存映射的情况,映射到同一物理内存页的映射都保存在一个链表中

  5. vm_pgoff, vm_file都是处理有后备文件内存映射的情况,获得该映射在文件的页偏移量,以及打开文件file实例的信息

对于有后备文件的映射,内核还提供了一个优先查找树结构,来加速确定一个文件和所有映射到这个文件的虚拟内存区域vm_area_struct实例的关系,从而可以得到所有映射到这个文件的进程信息。这张图表示了一个后备文件被mmap映射后内核建立的一些数据结构,涉及到了内存管理的数据和文件系统的数据

内核提供了一系列的函数对虚拟内存区域vm_area_struct进行操作,比如创建,删除,合并,查找等等。而mmap是C标准库提供给用户程序的一个函数来使用内存映射,建立起文件地址空间和虚拟内存区域的映射关系。

mmap的4种类型

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. 匿名文件的私有映射可以用作进程的私有内存分配

内核对堆空间的管理

实际上从内核的管理用户进程虚拟地址空间的角度来说,内存映射是管理用户进程虚拟地址空间的主要手段,通过建立vm_area_struct来分配虚拟内存区域。内核对堆空间的分配主要是brk系统调用,brk系统调用本质上也是利用了匿名文件私有映射,分配初始化0的物理内存页,建立vm_area_struct,然后更新页表结构。brk系统调用分配的内存最小单位是页,需要按页对齐,从start_brk位置向上扩展,也就是说从内核的角度来说,每次对堆空间的分配最小就是一页,更细粒度的字节内存空间分配由C语言标准库实现的。

深入理解内存映射mmap的更多相关文章

  1. 计算机底层知识拾遗(九)深入理解内存映射mmap

    内存映射mmap是Linux内核的一个重要机制,它和虚拟内存管理以及文件IO都有直接的关系,这篇细说一下mmap的一些要点. 修改(2015-11-12):Linux的虚拟内存管理是基于mmap来实现 ...

  2. 内存映射mmap的几个api及其使用

    内存映射 mmap 内存映射mmap函数的作用是建立一段可以被两个或者多个程度读写的内存段,一个程序对他进行任何修改,对其它程序可见.同样,这个功能可以用在对文件的处理上,mmap函数创建一个指向一个 ...

  3. [转载]linux内存映射mmap原理分析【转】

    转自:http://www.cnblogs.com/wanpengcoder/articles/5306688.html 转自:http://blog.csdn.net/yusiguyuan/arti ...

  4. 内存映射MMAP和DMA【转】

    转自:http://blog.csdn.net/zhoudengqing/article/details/41654293 版权声明:本文为博主原创文章,未经博主允许不得转载. 这一章介绍Linux内 ...

  5. 内存映射mmap

    Table of Contents 1. 什么是mmap 2. 使用方法 2.1. mmap构造器的格式 2.2. 例子1 2.3. 例子2 3. 其它 4. 参考资料 什么是mmap 通常在Unix ...

  6. Linux内存映射(mmap)系列(1)

    看到同事的代码中出现了mmap.所以自己私下学习学习,研究研究..... http://www.cnblogs.com/lknlfy/archive/2012/04/27/2473804.html ( ...

  7. Linux内存映射--mmap函数

    Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, 先来看一下mmap的函数声明: 头文件: < ...

  8. linux mmap 内存映射【转】

    转自:http://blog.csdn.net/xyyangkun/article/details/7830313 [-] mmap vs readwritelseek mmap vs malloc ...

  9. mmap内存映射

    http://blog.csdn.net/kongdefei5000/article/details/70183119 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...

随机推荐

  1. win10环境下如何运行debug

    在学习汇编的时候,会需要用到debug调试程序,但是现在win10默认已经移除了这个插件,我们需要手动安装,下面就告诉大家如何在win10环境下安装debug. 1:准备工具 1.1 DOSBox 1 ...

  2. 20180726 - Windows 10 Pro 下远程桌面连接提示“出现身份验证错误”

    问题:Windows 10 Pro 下远程桌面连接提示“出现身份验证错误” [Window Title]远程桌面连接 [Content]出现身份验证错误.要求的函数不受支持 远程计算机: 192.16 ...

  3. kubernetes 客户端KubeClient使用及常用api

    KubeClient是kubernetes 的C#语言客户端简单易用,KubeClient是.NET Core(目标netstandard1.4)的可扩展Kubernetes API客户端, gith ...

  4. Intellij Idea 无法启动项目的配置坑

    1. run/debug configuration里面,tomcat的deployment点击添加不能自动创建war-explorded包: 方案:删除project libraries,重新mav ...

  5. Linux下ps -ef和ps aux的区别

    Linux下显示系统进程的命令ps,最常用的有ps -ef 和ps aux.这两个到底有什么区别呢?两者没太大差别,讨论这个问题,要追溯到Unix系统中的两种风格,System V风格和BSD 风格, ...

  6. SUSE12SP3-Mycat(2)Schema.xml配置详解

    简介 Schema.xml 作为 MyCat 中重要的配置文件之一,管理着 MyCat 的逻辑库.表.分片规则.DataNode 以及 DataSource.弄懂这些配置,是正确使用 MyCat 的前 ...

  7. Vue(day4)

    这里说的Vue中的路由是指前端路由,与后端路由有所区别.我们可以使用url来获取服务器的资源,而这种url与资源的映射关系就是我们所说的路由.对于单页面程序来说,我们使用url时常常通过hash的方法 ...

  8. 2. CMake 系列 - 编译多文件项目

    目录 1. 编译不使用第三方库的项目 1.1 项目目录结构 1.2 相关代码 1.3 编译 2. 编译使用第三方库的项目 2.1 项目目录结构 2.2 相关代码 2.3 编译 1. 编译不使用第三方库 ...

  9. GitHub的Repository权限将public转为private

    2019年1月7日,GitHub CEO Nat Friedman 于官方博客公开发文,称“New year, new GitHub”,宣布从此将免费无限地为普通用户提供私有仓库服务. 因此,我们可以 ...

  10. SpringBoot进阶教程(二十三)Linux部署Quartz

    在之前的一篇文章中<SpringBoot(九)定时任务Schedule>,已经详细介绍了关于schedule框架的配置和使用,有收到一些朋友关于部署的私信,所以抽时间整理一个linux部署 ...