本文章以Linux为例,讲解一下虚拟内存系统的工作原理,windows系统的原理也是大同小异,有兴趣的读者可以自行查阅相关资料。

linux内核以及它管理用户内存的机制,下面我们以应用程序gonzo的内存示意图为例,进行详细说明。

Linux进程在内核中是以一个task_struct实例来实现的,称为进程描述符。task_struct的mm字段指向了内存描述符,即mm_struct,它是一份可执行程序的内存结构概要。如上图所示,它存储了内存各个内存端的起始位置和结束位置,进程使用的物理内存页的数量,进程使用的虚拟地址空间等信息。在内存描述符内部,还有两个内存管理的重要结构:virtual memory areas和page tables。下图就是Gonzo的内存区域示意图:

每一个virtual memory area(VMA)都是一段连续的虚拟内存地址,这些内存区域绝不会重合。一个vm_area_struct描述一个内存区域,包括了它的起始地址和结束地址,内存访问权限标志位,以及一个vm_file字段(如果有该字段的话,用来指定哪个文件映射到了该内存区域)。VMA不会映射匿名文件。进程内存布局中除了内存映射段外的每一个内存段都对应一个VMA。这种方式尽管在X86机器上很常见,但这并不是硬性要求。VMA们并不关心它们对应的是哪个段。

一个程序的VMA们都是作为一个链表存在于内存描述符的mmap字段中的,并且按照虚拟地址进行了排序,并且是一个以mm_rb为根节点的红黑树。采用红黑树的数据结构是为了方便内核给定虚拟地址后快速查找对应的内存区域。当你读/proc/pid_of_process/maps这个文件时,内核就是简单的遍历进程的VMA链表并挨个打印。

Windows中的EPROCESS块就是task_struct和mm_struct的混合,它对应于VMA的是一个称为Virtual Address Descriptor(VAD)的数据结构,VAD们存储在一个AVL树中。有意思的是,Windows和linux的区别真的很小。

4GB的虚拟内存地址空间被分成很多页。X86处理器在32位模式下支持4KB、2MB以及4MB大小的页。Linux和Windows都是用的4KB的页来分割用户虚拟地址空间的。0-4096字节落在page 0,4096-8192字节落在page 1,以此类推。VMA的大小一定是page大小的倍数。下图就是用4KB的页分割的3GB用户虚拟地址空间示意:

处理器利用page tables来将虚拟内存地址转换为物理内存地址。每个进程都有自己的page tables,无论进程切换何时发生,用户态的page table也会跟着切换。Linux在内存描述符中的pgd字段存储了一个指向该进程page tables的指针。每一个虚拟内存页对应一个page table entry,一个X86的页的结构如下:

Linux有函数来读取和设置PTE中的每一个标志位。位P告诉处理器该虚拟页是否要在物理内存中呈现。如果设为0,访问该页时会触发一个页错误。R/W标志位代表了读写权限,如果为0则该页为只读。U/S标志位代表了普通用户和超级用户,如果设置为0,则该页只能被内核访问。这些标志位都是用来实现前面看到的只读内存和内核态地址空间的。

D和A标志位代表了dirty和accessed,一个脏页表示该页已经被写过,一个被访问过的页表示该页被读过或者写过。最后,PTE存储了其对应的物理内存地址的起始地址,4KB对齐。

内存保护是以页为单位进行的,因为每个页都共用U/S和R/W标志位。但是同一个物理内存页可以对应多个虚拟内存页,这些不同的虚拟内存也可能有不同的保护标志位,所以要记住:在VMA设置的权限标志位不一定真正的用到了物理内存的保护上。

虚拟内存不存储任何东西,它只是简单的将程序的地址空间映射到底层的物理内存空间,物理地址空间才是处理器真正操作的内存空间。物理地址空间也被分成了以页为单位的大小。每个页是物理内存管理的最小单位。32位Linux和Windows都是以4KB为大小划分页的。下图所示为一个2GB大小的RAM

我们把virtual memory areas,page table entry以及page frame放在一起,理解一下它们是如何协作的,下图是一个用户堆的示例:

蓝色部分代表了VMA对应的地址范围,每一项都是一个page table entry,每个箭头代表从PTE到物理page frame的映射,某些PTE没有箭头,代表这些PTE的P标志位被清零了。这可能是因为这些页从来没有被访问过或者页已经被换出了。无论哪种情况,访问这些页都会导致缺页错误。

一个VMA就像一份你的程序和内核之间合约,你要求完成一些事情,比如内存分配、文件映射等,内核说没问题,然后它创建或者更新合适的VMA。但是为了效率,内核不会立马相应你的请求,直到第一次访问页产生缺页错误时才会去做,这也是虚拟内存的设计原则。

让我们看下所有的这些数据结构联合起来是如何工作的,下图是一个内存分配的示例:

当程序通过brk()系统调用请求更多的内存时,内核简单的更新堆的VMA,这时并没有page frame分配。

文章参考翻译自:https://manybutfinite.com/post/how-the-kernel-manages-your-memory/

Linux虚拟内存系统详解的更多相关文章

  1. Linux /dev目录详解和Linux系统各个目录的作用

    Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...

  2. syslog之一:Linux syslog日志系统详解

    目录: <syslog之一:Linux syslog日志系统详解> <syslog之二:syslog协议及rsyslog服务全解析> <syslog之三:建立Window ...

  3. Linux 内存机制详解宝典

    Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...

  4. Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)

    启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...

  5. Linux启动过程详解

    Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...

  6. Linux netstat命令详解

    Linux netstat命令详解 一  简介 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多 ...

  7. java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET

    java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了!      社区福利快来领取免费参加MDCC大会机会哦    Tag功能介绍—我们 ...

  8. Linux crontab 命令详解

    在 Linux 中,任务可以被配置在指定的时间段.指定的日期.或系统平均载量低于指定的数量时自动运行.红帽企业 Linux 预配置了对重要系统任务的运行,以便使系统能够时时被更新.譬如,被 locat ...

  9. linux curl用法详解

    linux ‍‍curl用法详解 ‍‍curl的应用方式,一是可以直接通过命令行工具,另一种是利用libcurl库做上层的开发.本篇主要总结一下命令行工具的http相关的应用, 尤其是http下载方面 ...

随机推荐

  1. 【C#】CLR内存那点事(string)

    string是比特殊的类,说引用类型,但不存在堆里面,而且String str=new String("HelloWorld")这样的重装也说没有的. 我们先来看一个方法 clas ...

  2. C# Path类常用方法

    Path 类 对包含文件或目录路径信息的 String 实例执行操作. 1.Path.GetExtension 方法 —— 返回指定的路径字符串的扩展名. public static string G ...

  3. UIView 动画

    1.UIView 动画 核心动画 和 UIView 动画 的区别: 核心动画一切都是假象,并不会真实的改变图层的属性值,如果以后做动画的时候,不需要与用户交互,通常用核心动画(转场). UIView ...

  4. day8学python 各种简单模板

    各种简单模板 内容: 1.shelve模板 存储数据 2.shutil 模板 用作拷贝/删除/压缩文件(使用便捷) 3.hashlib 模板 加密文件 4.re模板 ================= ...

  5. node.js安装以及git 的使用说明

     第一步:安装node.js: Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境.   Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高 ...

  6. 微信小程序HTTPS - cenos apache 下安装SSL证书

    1.yum install mod_ssl 2.接下来,我们需要创建一个新目录,我们将存储服务器密钥和证书 mkdir /root/ssl 3.vi /etc/httpd/conf.d/ssl.con ...

  7. get与post中文乱码问题

    Jsp默认的字符编码格式是iso-8859-1 因为post方法与get方法传递参数的方式不一样,所以有不同的解决方法. 一.post乱码解决方法: 1.设置请求和响应的编码方式 //设置请求的编码格 ...

  8. 冒泡排序 思想 JAVA实现

    已知一个数组78.75.91.36.72.94.43.64.93.46,使用冒泡排序将此数组有序. 冒泡排序是一个运行时间为O(N²)的排序算法. 算法思想:(已从小到大为例) 78.75.91.36 ...

  9. _.each _.map _.filter javascript 语法

    1.forEach():没有返回值,只是针对每个元素调用func     简单来说,就是将数组中的每个值,依次进行遍历,执行功能函数     如果只需对数组元素进行操作,不需返回任何值时适用 var ...

  10. 首字母变大写(stringstream的应用)

    Problem Description 输入一个英文句子,将每个单词的第一个字母改成大写字母.   Input 输入数据包含多个测试实例,每个测试实例是一个长度不超过100的英文句子,占一行.   O ...