虚拟地址空间
1. 虚拟地址空间
1.1 定义
虚拟存储器是一个抽象概念,它为每个进程提供了一个假象,好像每个进程都在独占地使用主存。每个进程看到的存储器都是一致的,称之为虛拟地址空间。
地址空间就是每个进程所能访问的内存地址范围,是一段连续的虚拟内存空间。
1.2 背景(为什么需要虚拟地址空间?)
因为早期的计算机运行方式是直接将程序运行在物理内存上。这就存在三个问题:
问题1. 地址空间不隔离
所有进程都直接访问物理地址,程序使用的物理空间不是隔离的
问题2. 运行效率低
监控程序直接将整个程序装入内存进行执行
问题3. 运行地址不确定
因为每次执行装入的物理地址是不确定的
为了解决以上三个问题,增加了一个中间层,用来解决上面的问题。
那就是给出一个虚拟地址,然后通过一种映射关系,将其与物理地址进行映射,将虚拟地址转换成物理地址。
这样就能解决存在的问题1和问题3,问题2需要页映射来解决。
这个可以看之前的博客介绍。
https://blog.csdn.net/lqy971966/article/details/99533610
1.3 大小/平坦地址空间
每个进程都有一个32位或者64位的平坦(flat)地址空间,具体大小取决于体系结构。
平坦指地址空间范围是一个独立的连续空间,如0~4294967295(2^32)。
因为每个内存都有唯一的平坦地址空间,所以一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,实际上也彼此互不相干。
1.4 内存区域
1.4.1 内存区域定义
尽管一个进程可以寻址4GB的虚拟内存,但这并不代表它有权访问所有的虚拟地址。
可被访问的合法地址空间称为内存区域(memory areas)
通过内核,进程可以给自己的地址空间动态地添加或减少内存区域。进程只能访问有效内存区域内的内存地址,每个内存区域也具有相关权限属性(可读、可写、可执行)。
1.4.2 内存区域组成(进程地址空间)
linux下,进程虚拟空间分为:操作系统占1GB,进程占3GB。
linux下一个进程的地址空间从上到下依次是:
内核空间:内核是操作系统总是驻留在存储器中的部分
应用程序不允许读写这个区域的内容或者直接调用内核代码定义的函数
栈:临时变量,返回值等,向低地址扩展
用户栈在程序执行期间可以动态地扩展和收缩
调用函数时,栈就会增长每次我们从函数返回时,栈就会收缩
共享库:存放像C标准库和数学库这样共享库的代码和数据的区域
堆:动态分配的内存,向高地址扩展
调用像malc和free这样的C标准库函数的结果,堆可以在运态地扩展和收缩
bss段数据:即未初始化全局变量的内存映射
数据段(只读数据段):即可执行文件的已初始化全局变量的内存映射
代码段:即可执行文件二进制代码的内存映射(可执行文件映像)
保留区域
1.5 页表映射物理内存
地址空间最终会通过页表映射到物理内存上,因为内核操作的是物理内存。
1.5.1 局部性原理(动态装入的基本原理)
程序运行时是有局部性原理的,所以可以将最常用的部分驻留内存中,其他放在硬盘里,这就是动态装入的基本原理。
页:
OS将内存和磁盘中的数据和指令按照页/page为单位划分成若干个页。
以后所有的装载和操作的单位就是页。
1.5.2 两个映射
创建虚拟地址空间的本质是:创建映射函数需要的数据结构。
映射1:
建立虚拟空间和可执行文件的映射关系
其实这个关系就是:发生页错误时,OS应该知道程序当前需要的页在可执行文件中的哪一个位置。
映像文件(Image):
因为可执行文件在被装载时是被映射到虚拟地址空间的,所以很多时候可执行文件又被叫做映像文件。
这种映射关系也是保存在OS中的一个数据结构中的。
虚拟内存区域(VMA: Virtual Memory Area):
linux将进程虚拟空间中的一个段叫做虚拟内存区域。windows叫做虚拟段。
映射2:
建立虚拟空间的各个页映射到相应的物理地址的映射
自己画的,参考之前的博客:https://blog.csdn.net/lqy971966/article/details/106910442
1.5.3 页错误
定义:
当cpu执行进程的某个页面时,发现他要访问的页(虚拟地址的页)没有在物理内存中,而导致的中断(页错误)。
(一个可执行文件可能很大,放在磁盘上,由局部性原理一次只将其中一部分读进内存)
解决:
通过页交换将虚拟页映射至物理内存中。
详细请参考之前的博客:页错误 Page Fault 详解
https://blog.csdn.net/lqy971966/article/details/106910442