内存地址空间

物理内存空间的有限(16G内存条就是有16G). 但如果要运行70G的GTA5, 怎么办?

操作系统非常重要的一个功能就是虚拟化物理内存. 除了内存条等场景下的提法, 在软件地址, 指针地址等指向的地址都是虚拟化后的内存空间地址.

主存中的每个字节都由一个整数物理地址所指定,物理地址的集合叫做物理地址空间。它的范围通常为0到`N-1`,其中`N`是主存的大小。

许多操作系统提供虚拟内存,也就是说程序永远不需要处理物理地址,也不需要知道有多少物理内存是有效的。作为代替,程序处理虚拟地址,它被编码为从0到M-1,其中M是有效虚拟地址的大小。虚拟地址空间的大小取决于所处的操作系统和硬件。

Q:为什么32位操作系统给进程分配的是4G的虚拟地址空间?A:32位操作系统一共有32根地址线,每个地址线能模拟的数字位0/1。

多道程序设计进行有效的内存管理,不仅方便用户使用存储器,提高内存利用率,通过虚拟技术从逻辑上扩充存储器。操作系统的内存管理模块负责逻辑地址和物理地址的转换,因此该部分需要紧密结合

虚拟内存空间

实现

  • 传统:一次性和驻留性
  • 虚拟内存:多次性,对换性,虚拟性
块表、页高速缓存及虚拟内存技术使用高速缓存技术,高速缓存技术依赖局部性原理
  1. 空间局部性、时间局部性

  2. 虚拟内存技术建立了内存-外存两极存储结构,利用局部性原理实现高速缓存

  3. 一定容量的内存和外存

  4. 页表机制,离散分配

  5. 中断机构:缺页内部异常,缺页进程阻塞,调页完成唤醒。

  6. 地址变换机构(需要硬件支持)

容量问题

容量:虚拟存储器的容量(地址空间)由计算机地址总线的数量来决定。虚拟存储器的页表是虚地址到内存地址或者外存地址的映射。通过页表项的status位来决定。缺页中断将外存中的块置入内存。

虚存实际容量由CPU地址长度和外存容量决定。一般情况下CPU的地址长度能表示的大小都大于外存容量。

  1. 当CPU的地址长度能表示的大小远远大于外存容量时,虚存的实际容量为内存和外存容量之和
  2. 当外存容量远大于CPU字长能表示的大小时,虚存的实际容量由CPU字长决定

进程的虚拟内存空间组成

虚拟内存使每个进程都以为自己独占了主存。每个进程看到的内存都是一致的,即虚拟地址空间。

在linux中,每个进程看到的虚拟地址空间由以下几个部分内存段组成.

段名 存储内容 分配方式 生长方向 读写特点 运行态
代码段 程序指令、字符串常量、虚函数表 静态分配 由低到高 只读 用户态
数据段 初始化的全局变量和静态变量 静态分配 由低到高 可读可写 用户态
BSS段 未初始化的全局变量和静态变量 静态分配 由低到高 可读可写 用户态
堆(运行时) 动态申请的数据 动态分配 由低到高 可读可写 用户态
映射段 动态链接库、共享文件、匿名映射对象 动态分配 由低到高 可读可写 用户态
栈(用户栈) 局部变量、函数参数与返回值、函数返回地址、调用者环境信息 静态+动态分配 由高到低 可读可写 用户态
内核空间 操作系统、驱动程序 静态+动态分配 由低到高+由高到低 不能直接访问 内核态

地址从低到高,最高层的内核虚拟内存保存的是操作系统中的代码和数据,这部分每个进程都一样。

程序代码和数据

对所有进程来说,代码都是从同一个固定地址开始,紧接着是与全局变量对应的数据区。代码和数据区都是按照可执行文件的内容初始化的。代码和数据区在进程开始运行时就被指定了大小。

  1. 静态存储区内的变量在程序编译阶段已经分配好内存空间并初始化。这块内存在程序的整个运行期间都存在,它主要存放静态变量、全局变量和常量。
  2. 这里不区分data区和BSS区,静态存储区内的变量若不显示初始化,则编译器会自动以默认的方式进行初始化,即静态存储区内不存在未初始化的变量。(C++)
  3. 静态存储区内的常量分为常变量和字符串常量,一经初始化,不可修改。静态存储内的常变量是全局变量,与局部常变量不同,区别在于局部常变量存放于栈,实际可间接通过指针或者引用进行修改,而全局常变量存放于静态常量区则不可以间接修改。
  4. 字符串常量存储在静态存储区的常量区,字符串常量的名称即为它本身,属于常变量。

而运行时堆是根据 mallocfree 函数的调用在运行时动态地扩展和收缩的。

手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间。使用 malloc 或者 new 进行堆的申请,堆的总大小为机器的虚拟内存的大小。 说明:new操作符本质上是使用了malloc进行内存的申请,new和malloc的区别如下:

  1. malloc是C语言中的函数,而new是C++中的操作符。
  2. malloc申请之后返回的类型是void *,而new返回的指针带有类型。
  3. malloc只负责内存的分配而不会调用类的构造函数,而new不仅会分配内存,而且会自动调用类的构造函数。

共享库

地址空间的中间部分用来存放共享库的代码和数据。如 C 标准库、数学库等都属于共享库

用户栈和堆一样,在程序执行期间可以动态的扩展和收缩,编译器用它来实现函数调用。当调用函数时,栈增长,从函数返回时,栈收缩

由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。整个程序的栈区的大小可以在编译器中由用户自行设定,VS中默认的栈区大小为1M,可通过VS手动更改栈的大小。64b的Linux默认栈大小为10MB,可通过ulimit -s临时修改。

地址翻译

地址变换机构是内存管理单元MMU中将逻辑地址映射为内存中物理地址的硬件系统。大多数处理器提供了内存管理单元MMU,位于CPU和主存之间。MMUVAPA之间执行快速的翻译。

逻辑地址logical address是程序产生的与段相关的偏移地址部分。源代码经过complie编译:生成可执行代码,产生若干代码块Object Module后,目标程序所用的地址就是逻辑地址。逻辑地址的范围叫逻辑地址空间。物理地址physical address在CPU外部地址总线上的寻址物理内存的地址信号。物理地址用户透明,转换过程硬件自动完成,转换过程叫做地址重定位。

当cache作用时:

逻辑地址是由程序员给出的。经过查询快表页表得到物理地址。但此时的物理地址并不一定是最终地址,因为如果cache命中则需要再转换成cache地址。转换后的开始中的地址才是最终地址。但如果开始不命中,则仍需要按原来经过代表或者业表得到的地址去直接访存。得到物理地址(内存地址)后,先访问cache,如果命中,转换成cache地址访问,如果没命中,则进行访存。