虚拟内存管理

MMU 

现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要处理器中的MMU(Memory Management Unit,内存管理单元)提供支持,本节简要介绍MMU的作用。

首先引入两个概念,虚拟地址和物理地址。如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA),如下图所示。

图 17.5. 物理地址

物理地址

 

如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将VA映射成PA,如下图所示。

图 17.6. 虚拟地址

虚拟地址

 

如果是32位处理器,则内地址总线是32位的,与CPU执行单元相连(图中只是示意性地画了4条地址线),而经过MMU转换之后的外地址总线则不一定是32位的。也就是说,虚拟地址空间和物理地址空间是独立的,32位处理器的虚拟地址空间是4GB,而物理地址空间既可以大于也可以小于4GB。

MMU将VA映射到PA是以页(Page)为单位的,32位处理器的页尺寸通常是4KB。例如,MMU可以通过一个映射项将VA的一页0xb7001000~0xb7001fff映射到PA的一页0x2000~0x2fff,如果CPU执行单元要访问虚拟地址0xb7001008,则实际访问到的物理地址是0x2008。物理内存中的页称为物理页面或者页帧(Page Frame)。虚拟内存的哪个页面映射到物理内存的哪个页帧是通过页表(Page Table)来描述的,页表保存在物理内存中,MMU会查找页表来确定一个VA应该映射到什么PA。

操作系统和MMU是这样配合的:

  1. 操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后用指令设置MMU,告诉MMU页表在物理内存中的什么位置。

  2. 设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做。

我们在程序中使用的变量和函数都有各自的地址,程序被编译后,这些地址就成了指令中的地址,指令中的地址被CPU解释执行,就成了CPU执行单元发出的内存地址,所以在启用MMU的情况下,程序中使用的地址都是虚拟地址,都会引发MMU做查表和地址转换操作。那为什么要设计这么复杂的内存管理机制呢?多了一层VA到PA的转换到底换来了什么好处?All problems in computer science can be solved by another level of indirection.还记得这句话吗?多了一层间接必然是为了解决什么问题的,等讲完了必要的预备知识之后,将在第 5 节 “虚拟内存管理”讨论虚拟内存管理机制的作用。

MMU除了做地址转换之外,还提供内存保护机制。各种体系结构都有用户模式(User Mode)和特权模式(Privileged Mode)之分,操作系统可以在页表中设置每个内存页面的访问权限,有些页面不允许访问,有些页面只有在CPU处于特权模式时才允许访问,有些页面在用户模式和特权模式都可以访问,访问权限又分为可读、可写和可执行三种。这样设定好之后,当CPU要访问一个VA时,MMU会检查CPU当前处于用户模式还是特权模式,访问内存的目的是读数据、写数据还是取指令,如果和操作系统设定的页面权限相符,就允许访问,把它转换成PA,否则不允许访问,产生一个异常(Exception)。异常的处理过程和中断类似,不同的是中断由外部设备产生而异常由CPU内部产生,中断产生的原因和CPU当前执行的指令无关,而异常的产生就是由于CPU当前执行的指令出了问题,例如访问内存的指令被MMU检查出权限错误,除法指令的除数为0等都会产生异常。

图 17.7. 处理器模式

处理器模式

 

通常操作系统把虚拟地址空间划分为用户空间和内核空间,例如x86平台的Linux系统虚拟地址空间是0x00000000~0xffffffff,前3GB(0x00000000~0xbfffffff)是用户空间,后1GB(0xc0000000~0xffffffff)是内核空间。用户程序加载到用户空间,在用户模式下执行,不能访问内核中的数据,也不能跳转到内核代码中执行。这样可以保护内核,如果一个进程访问了非法地址,顶多这一个进程崩溃,而不会影响到内核和整个系统的稳定性。CPU在产生中断或异常时不仅会跳转到中断或异常服务程序,还会自动切换模式,从用户模式切换到特权模式,因此从中断或异常服务程序可以跳转到内核代码中执行。事实上,整个内核就是由各种中断和异常处理程序组成的。总结一下:在正常情况下处理器在用户模式执行用户程序,在中断或异常情况下处理器切换到特权模式执行内核程序,处理完中断或异常之后再返回用户模式继续执行用户程序。

段错误我们已经遇到过很多次了,它是这样产生的:

    1. 用户程序要访问的一个VA,经MMU检查无权访问。

    2. MMU产生一个异常,CPU从用户模式切换到特权模式,跳转到内核代码中执行异常服务程序。

    3. 内核把这个异常解释为段错误,把引发异常的进程终止掉。

http://learn.akae.cn/media/ch20s05.html

我们知道操作系统利用体系结构提供的VA到PA的转换机制实现虚拟内存管理。有了共享库的基础知识之后,现在我们可以进一步理解虚拟内存管理了。首先分析一个例子:

$ psPID TTY          TIME CMD
29977 pts/0    00:00:00 bash
30032 pts/0    00:00:00 ps
$ cat /proc/29977/maps 
08048000-080f4000 r-xp 00000000 08:15 688142     /bin/bash
080f4000-080f9000 rw-p 000ac000 08:15 688142     /bin/bash
080f9000-080fe000 rw-p 080f9000 00:00 0 
09283000-09497000 rw-p 09283000 00:00 0          [heap]
b7ca8000-b7cb2000 r-xp 00000000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so
b7cb2000-b7cb3000 r--p 00009000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so
b7cb3000-b7cb4000 rw-p 0000a000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so
...
b7e15000-b7f6d000 r-xp 00000000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so
b7f6d000-b7f6f000 r--p 00158000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so
b7f6f000-b7f70000 rw-p 0015a000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so
...
b7fbd000-b7fd7000 r-xp 00000000 08:15 565466     /lib/ld-2.8.90.so
b7fd7000-b7fd8000 r-xp b7fd7000 00:00 0          [vdso]
b7fd8000-b7fd9000 r--p 0001a000 08:15 565466     /lib/ld-2.8.90.so
b7fd9000-b7fda000 rw-p 0001b000 08:15 565466     /lib/ld-2.8.90.so
bfac5000-bfada000 rw-p bffeb000 00:00 0          [stack]

ps命令查看当前终端下的进程,得知bash进程的id是29977,然后用cat /proc/29977/maps命令查看它的虚拟地址空间。/proc目录中的文件并不是真正的磁盘文件,而是由内核虚拟出来的文件系统,当前系统中运行的每个进程在/proc下都有一个子目录,目录名就是进程的id,查看目录下的文件可以得到该进程的相关信息。此外,用pmap 29977命令也可以得到类似的输出结果。

图 20.4. 进程地址空间

进程地址空间

 

在第 4 节 “MMU”讲过,x86平台的虚拟地址空间是0x0000 0000~0xffff ffff,大致上前3GB(0x0000 0000~0xbfff ffff)是用户空间,后1GB(0xc000 0000~0xffff ffff)是内核空间,在这里得到了印证。0x0804 8000-0x080f 4000是从/bin/bash加载到内存的,访问权限为r-x,表示Text Segment,包含.text段、.rodata段、.plt段等。0x080f 4000-0x080f 9000也是从/bin/bash加载到内存的,访问权限为rw-,表示Data Segment,包含.data段、.bss段等。

0x0928 3000-0x0949 7000不是从磁盘文件加载到内存的,这段空间称为堆(Heap),以后会讲到用malloc函数动态分配内存是在这里分配的。从0xb7ca 8000开始是共享库的映射空间,每个共享库也分为几个Segment,每个Segment有不同的访问权限。可以看到,从堆空间的结束地址(0x0949 7000)到共享库映射空间的起始地址(0xb7ca 8000)之间有很大的地址空洞,在动态分配内存时堆空间是可以向高地址增长的。堆空间的地址上限(0x09497000)称为Break,堆空间要向高地址增长就要抬高Break,映射新的虚拟内存页面到物理内存,这是通过系统调用brk实现的,malloc函数也是调用brk向内核请求分配内存的。

/lib/ld-2.8.90.so就是动态链接器/lib/ld-linux.so.2,后者是前者的符号链接。标有[vdso]的地址范围是linux-gate.so.1的映射空间,我们讲过这个共享库是由内核虚拟出来的。0xbfac 5000-0xbfad a000是栈空间,其中高地址的部分保存着进程的环境变量和命令行参数,低地址的部分保存函数栈帧,栈空间是向低地址增长的,但显然没有堆空间那么大的可供增长的余地,因为实际的应用程序动态分配大量内存的并不少见,但是有几十层深的函数调用并且每层调用都有很多局部变量的非常少见。总之,栈空间是可能用尽的,并且比堆空间更容易用尽,在第 3 节 “递归”讲过,无穷递归会用尽栈空间最终导致段错误。

虚拟内存管理起到了什么作用呢?可以从以下几个方面来理解。

第一,虚拟内存管理可以控制物理内存的访问权限。物理内存本身是不限制访问的,任何地址都可以读写,而操作系统要求不同的页面具有不同的访问权限,这是利用CPU模式和MMU的内存保护机制实现的。例如,Text Segment被只读保护起来,防止被错误的指令意外改写,内核地址空间也被保护起来,防止在用户模式下执行错误的指令意外改写内核数据。这样,执行错误指令或恶意代码的破坏能力受到了限制,顶多使当前进程因段错误终止,而不会影响整个系统的稳定性。

第二,虚拟内存管理最主要的作用是让每个进程有独立的地址空间。所谓独立的地址空间是指,不同进程中的同一个VA被MMU映射到不同的PA,并且在某一个进程中访问任何地址都不可能访问到另外一个进程的数据,这样使得任何一个进程由于执行错误指令或恶意代码导致的非法内存访问都不会意外改写其它进程的数据,不会影响其它进程的运行,从而保证整个系统的稳定性。另一方面,每个进程都认为自己独占整个虚拟地址空间,这样链接器和加载器的实现会比较容易,不必考虑各进程的地址范围是否冲突。

继续前面的实验,再打开一个终端窗口,看一下这个新的bash进程的地址空间,可以发现和先前的bash进程地址空间的布局差不多:

$ psPID TTY          TIME CMD
30697 pts/1    00:00:00 bash
30749 pts/1    00:00:00 ps
$ cat /proc/30697/maps
08048000-080f4000 r-xp 00000000 08:15 688142     /bin/bash
080f4000-080f9000 rw-p 000ac000 08:15 688142     /bin/bash
080f9000-080fe000 rw-p 080f9000 00:00 0 
082d7000-084f9000 rw-p 082d7000 00:00 0          [heap]
b7cf1000-b7cfb000 r-xp 00000000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so
b7cfb000-b7cfc000 r--p 00009000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so
b7cfc000-b7cfd000 rw-p 0000a000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so
...
b7e5e000-b7fb6000 r-xp 00000000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so
b7fb6000-b7fb8000 r--p 00158000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so
b7fb8000-b7fb9000 rw-p 0015a000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so
...
b8006000-b8020000 r-xp 00000000 08:15 565466     /lib/ld-2.8.90.so
b8020000-b8021000 r-xp b8020000 00:00 0          [vdso]
b8021000-b8022000 r--p 0001a000 08:15 565466     /lib/ld-2.8.90.so
b8022000-b8023000 rw-p 0001b000 08:15 565466     /lib/ld-2.8.90.so
bff0e000-bff23000 rw-p bffeb000 00:00 0          [stack]

该进程也占用了0x0000 0000-0xbfff ffff的地址空间,Text Segment也是0x0804 8000-0x080f 4000,Data Segment也是0x080f 4000-0x080f 9000,和先前的进程一模一样,因为这些地址是在编译链接时写进/bin/bash这个可执行文件的,两个进程都加载它。这两个进程在同一个系统中同时运行着,它们的Data Segment占用相同的VA,但是两个进程各自干各自的事情,显然Data Segment中的数据应该是不同的,相同的VA怎么会有不同的数据呢?因为它们被映射到不同的PA。如下图所示。

图 20.5. 进程地址空间是独立的

进程地址空间是独立的

 

从图中还可以看到,两个进程都是bash进程,Text Segment是一样的,并且Text Segment是只读的,不会被改写,因此操作系统会安排两个进程的Text Segment共享相同的物理页面。由于每个进程都有自己的一套VA到PA的映射表,整个地址空间中的任何VA都在每个进程自己的映射表中查找相应的PA,因此不可能访问到其它进程的地址,也就没有可能意外改写其它进程的数据。

另外,注意到两个进程的共享库加载地址并不相同,共享库的加载地址是在运行时决定的,而不是写在/bin/bash这个可执行文件中。但即使如此,也不影响两个进程共享相同物理页面中的共享库,当然,只有只读的部分是共享的,可读可写的部分不共享。

使用共享库可以大大节省内存。比如libc,系统中几乎所有的进程都映射libc到自己的进程地址空间,而libc的只读部分在物理内存中只需要存在一份,就可以被所有进程共享,这就是“共享库”这个名称的由来了。

现在我们也可以理解为什么共享库必须是位置无关代码了。比如libc,不同的进程虽然共享libc所在的物理页面,但这些物理页面被映射到各进程的虚拟地址空间时却位于不同的地址,所以要求libc的代码不管加载到什么地址都能正确执行。

第三,VA到PA的映射会给分配和释放内存带来方便,物理地址不连续的几块内存可以映射成虚拟地址连续的一块内存。比如要用malloc分配一块很大的内存空间,虽然有足够多的空闲物理内存,却没有足够大的连续空闲内存,这时就可以分配多个不连续的物理页面而映射到连续的虚拟地址范围。如下图所示。

图 20.6. 不连续的PA可以映射为连续的VA

不连续的PA可以映射为连续的VA

 

第四,一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存,虚拟内存管理使得这种情况下各进程仍然能够正常运行。因为各进程分配的只不过是虚拟内存的页面,这些页面的数据可以映射到物理页面,也可以临时保存到磁盘上而不占用物理页面,在磁盘上临时保存虚拟内存页面的可能是一个磁盘分区,也可能是一个磁盘文件,称为交换设备(Swap Device)。当物理内存不够用时,将一些不常用的物理页面中的数据临时保存到交换设备,然后这个物理页面就认为是空闲的了,可以重新分配给进程使用,这个过程称为换出(Page out)。如果进程要用到被换出的页面,就从交换设备再加载回物理内存,这称为换入(Page in)。换出和换入操作统称为换页(Paging),因此:

系统中可分配的内存总量 = 物理内存的大小 + 交换设备的大小

如下图所示。第一张图是换出,将物理页面中的数据保存到磁盘,并解除地址映射,释放物理页面。第二张图是换入,从空闲的物理页面中分配一个,将磁盘暂存的页面加载回内存,并建立地址映射。

图 20.7. 换页

换页

转载于:https://www.cnblogs.com/cnland/archive/2013/03/21/2972571.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461259.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql重新用户设置密码_mysql用户密码如何重新设置?

mysql用户密码重新设置停掉MySQL服务:sudo service mysql stop以上命令适用于Ubuntu和Debian。CentOS、Fedora和RHEL下使用mysqld替换mysql。以安全模式启动mysql:sudo mysqld_safe --skip-grant-tables --skip-networking &这样我们就可以直接用roo…

第三章 门电路

1 半导体二极管开关特性 1 二极管的特性可以近似的用3.2.1的PN结方程和图3.2.2伏安特性曲线描述 如下图 二极管近似伏安特性和对应的等效电路 1 a电路表示vcc和r都很小时候二极管正向导通压降和正向电阻都不能忽视 2 b电路表示二极管正向导通电压不可以忽视,但是二…

mysql查询数据库日期_mysql如何查询日期与时间

前言:在项目开发中,一些业务表字段经常使用日期和时间类型,而且后续还会牵涉到这类字段的查询。关于日期及时间的查询等各类需求也很多,本篇文章简单讲讲日期及时间字段的规范化查询方法。1.日期和时间类型概览MySQL支持的日期和时…

设备驱动框架3——使用gpiolib完成LED驱动

以下内容源于朱有鹏嵌入式课程的学习整理,如有侵权请告知删除。 一、前言 在实际情况中,很多硬件都要用到GPIO,因此GPIO会复用;如果同一个GPIO被2个驱动同时控制就会出现bug;因此内核提供了gpiolib来统一管理系统中所有…

from PyQt4 import QtGui,QtCore出错-解

from PyQt4 import QtGui,QtCore出错-解今天尝试着安装PyQt写界面,官网下载后发现import出错了,情况如下图:import PyQt4就可以,from PyQt4 import QtCore却不行提示DLL load faied找了下网上有些人说是某些dll文件丢失了&#xf…

设备驱动框架4——将驱动集成到内核中

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。 驱动集成到内核的概念 驱动开发的步骤一般是: (1)以模块的形式在内核外部编写与调试 (2)将调试好的驱动代码集成到kernel中 之前我们编写的…

例子简单说说C# ref和out

首写从这字段看 ref 就是引用的意思 out当然就是输出了public void getRefStr(ref string str) {str"hello 你好,你变成了Ref了" }public void getOutStr(out string outStr){outStr "hello 你好,你是out输出的值";} protected…

VARIANT变体类型数据

2019独角兽企业重金招聘Python工程师标准>>> 特殊 Variant 是一种特殊的数据类型,除了定长String数据及用户定义类型外,可以包含任何种类的数据。Variant 也可以包含Empty、Error、Nothing及Null等特殊值。可以用VarType函数或TypeName函数来…

mysql修改校对集_MySQL 图文详细教程之校对集问题

软件安装:装机软件必备包SQL是Structured Query Language(结构化查询语言)的缩写。SQL是专为数据库而建立的操作命令集,是一种功能齐全的数据库语言。在使用它时,只需要发出“做什么”的命令,“怎么做”是不用使用者考虑的。SQL功…

C# winform 魔兽MH全图制作教程(1): 开发准备工作

C# winform 魔兽MH全图制作教程(1): 开发准备工作 一、开发条件: Visual Studio 2008win xp,win 7,win 2003.C# 语言基础会调试能够运行游戏:《魔兽争霸3冰封王座》拥有版本魔兽客户端版本切换器1.20E,1.24E,1.24D二、设计思路&am…

从常识看中国经济社会-再续之续:套利

2019独角兽企业重金招聘Python工程师标准>>> 《全球化掠夺》提及财富流转的路径,世界仍旧是个丛林,每个人、每个族群都在争夺自己的利益。在一个经济体的内部,财富是垂直流动的;在全球化的经济体中,财富是纵…

MYSQL中的BlackHole引擎

MYSQL中的BlackHole引擎 http://blog.csdn.net/ylspirit/article/details/7234021 http://blog.chinaunix.net/uid-22646981-id-3271711.html MySQL在5.x系列提供了Blackhole引擎–“黑洞”. 其作用正如其名字一样:任何写入到此引擎的数据均会被丢弃掉,…

《DIY四轴飞行器》读书笔记1

内容整理于黄和悦的《DIY四轴飞行器》。 一、四轴飞行器概述 1、四轴飞行器的现状 (1)研究内容 多级协作,自主飞行倾斜;最优控制理论,飞行器自主飞行和避障;主要是飞控部分。 (2&#xff09…

脚本输出当前 “yyyy-MM-dd WeakDay Festval”

ylbtech-JavaScript: 脚本输出当前 “yyyy-MM-dd WeakDay Festval”脚本输出当前 “yyyy-MM-dd WeakDay Festval” 1.A,源代码(Source Code)-脚本输出当前 “yyyy-MM-dd WeakDay Festval”返回顶部 <SCRIPT languagejavascript> <!--calendar new Date();day cal…

SecureCRT密钥远程登录Linux

一&#xff1a;环境SecureCRT版本&#xff1a;SecureCRT_5.1.3linux版本&#xff1a;[rootangelT ~]# cat /etc/redhat-release CentOS release 6.4 (Final)[rootangelT ~]# uname -r2.6.32-358.el6.x86_64linux系统的sshd_config配置文件是默认的&#xff0c;没有任何的修改。…

在Linux系统安装Nginx及配置https加密访问

2019独角兽企业重金招聘Python工程师标准>>> 1、安装nginx ①、为了确保能在 nginx 中使用正则表达式进行更灵活的配置&#xff0c;安装之前需要确定系统是否安装有 PCRE&#xff08;Perl Compatible Regular Expressions&#xff09;包。您可以到 ftp://ftp.csx.c…

和菜鸟一起学linux之bluez学习记录2

这里主要摘取对于hci&#xff0c;l2cap&#xff0c;sdp和rfcomm的一些应用编程。 关于hci 一、HCI层协议概述 1、HCI Command Packets 详见bluez源码&#xff1a;lib/hci.h /* Link Control */ #define OGF_LINK_CTL 0x01 #define OCF_INQUIRY 0x0001 #define OCF_…

AppDelegate.h

2019独角兽企业重金招聘Python工程师标准>>> #ifndef __APP_DELEGATE_H__ #define __APP_DELEGATE_H__#include "CCApplication.h" //CCApplication.h能根据平台打开对应的平台头文件 /** brief The cocos2d Application.The reason for implement as …

虚拟机上网以及互ping问题

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 虚拟机设置静态IP和上网问题 &#xff08;1&#xff09;在“虚拟机——设置——网络适配器”中选择桥接模式&#xff1b; &#xff08;2&#xff09;在“编辑——虚拟网络编辑器”中&#xff0c;选择桥接到有…

qt 飞扬青云_R语言学习——实例标识符

> patientID> age> diabetes> status> patientdata> #在上述创建的病例数据框中&#xff0c;病人编号(patientID)用于区分数据中的不同个体&#xff0c;在R中实例标识符(case identifier)可以通过数据框操作函数中的rowname选项指定&#xff0c;如下代码&…