Linux虚拟内存系统详解
本文章以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虚拟内存系统详解的更多相关文章
- Linux /dev目录详解和Linux系统各个目录的作用
Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...
- syslog之一:Linux syslog日志系统详解
目录: <syslog之一:Linux syslog日志系统详解> <syslog之二:syslog协议及rsyslog服务全解析> <syslog之三:建立Window ...
- Linux 内存机制详解宝典
Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...
- Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)
启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...
- Linux启动过程详解
Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...
- Linux netstat命令详解
Linux netstat命令详解 一 简介 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多 ...
- java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET
java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了! 社区福利快来领取免费参加MDCC大会机会哦 Tag功能介绍—我们 ...
- Linux crontab 命令详解
在 Linux 中,任务可以被配置在指定的时间段.指定的日期.或系统平均载量低于指定的数量时自动运行.红帽企业 Linux 预配置了对重要系统任务的运行,以便使系统能够时时被更新.譬如,被 locat ...
- linux curl用法详解
linux curl用法详解 curl的应用方式,一是可以直接通过命令行工具,另一种是利用libcurl库做上层的开发.本篇主要总结一下命令行工具的http相关的应用, 尤其是http下载方面 ...
随机推荐
- Android学习笔记 Toast屏幕提示组件的使用方法
activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&qu ...
- Mybatis中的多表查询 多对多
示例:用户和角色 一个用户可以有多个角色 一个角色可以赋予多个用户 步骤: 1.建立两张表:用户表,角色表 让用户表和角色表具有多对多的关系. 需要使用中间表,中间表中包含各自的主键,在中间表中是外键 ...
- VMware虚拟机下安装CentOS系统超详细教程
链接:https://jingyan.baidu.com/article/fdffd1f8736173f3e98ca1e3.html 1.步骤一.工具准备 1.物理计算机一台 配置要求: 操作系统:w ...
- 关于DataGridView的ClearSelection方法
最近做winform项目,遇到一个问题. 纠结了几天,最终解决了,记录一下! 新建一个Form窗体,拽一个DataGridView上去,然后写一个方法 private void BindData() ...
- c语言数据结构学习心得——数据结构基本概念
1.数据>数据元素>数据项 数据的基本单位是数据元素,数据元素的基本单位是数据项 2.运算的定义->针对逻辑结构 集合:同属于一个集合,无其他关系.(数学上的集合) 线性结 ...
- css3中的display:-webkit-box的用法
一. css weui-media-box__desc { color: #999999; font-size: 13px; line-height: 1.2; overflow: hidden; t ...
- C++中define与const的区别
C++中不但可以用define定义常量还可以用const定义常量,它们的区别如下: 用#define MAX 255定义的常量是没有类型的,所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常 ...
- 在makefile通过宏定义来控制源程序的编译
在Makefile中我们可以通过宏定义来控制源程序的编译.只要在Makefile中的CFLAGS中通过选项-D来指定你于定义的宏即可. 如:CFLAGS += -D _XXX在编译的时候加上此选项就可 ...
- Windows Server 2008 R2中IIS7.5配置完网站权限不足问题的解决办法:
Windows Server 2008 R2中IIS7.5配置完网站权限不足问题的解决办法:常见问题:HTTP 错误 500.0 - Internal Server Error无法显示页面,因为发生内 ...
- python获取函数参数默认值的两种方法
1.使用函数的__defaults__魔术方法 demo: # coding=utf-8 def f(a,b,c=1): pass f.__defaults__ 输出结果: (1,) 2.使用insp ...