内存地址空间
物理内存空间的有限(16G内存条就是有16G). 但如果要运行70G的GTA5, 怎么办?
操作系统非常重要的一个功能就是虚拟化物理内存. 除了内存条等场景下的提法, 在软件地址, 指针地址等指向的地址都是虚拟化后的内存空间地址.
许多操作系统提供虚拟内存,也就是说程序永远不需要处理物理地址,也不需要知道有多少物理内存是有效的。作为代替,程序处理虚拟地址,它被编码为从0到M-1,其中M是有效虚拟地址的大小。虚拟地址空间的大小取决于所处的操作系统和硬件。
Q:为什么32位操作系统给进程分配的是4G的虚拟地址空间?A:32位操作系统一共有32根地址线,每个地址线能模拟的数字位0/1。
多道程序设计进行有效的内存管理,不仅方便用户使用存储器,提高内存利用率,通过虚拟技术从逻辑上扩充存储器。操作系统的内存管理模块负责逻辑地址和物理地址的转换,因此该部分需要紧密结合
虚拟内存空间
实现
- 传统:一次性和驻留性
- 虚拟内存:多次性,对换性,虚拟性
空间局部性、时间局部性
虚拟内存技术建立了内存-外存两极存储结构,利用局部性原理实现高速缓存
一定容量的内存和外存
页表机制,离散分配
中断机构:缺页内部异常,缺页进程阻塞,调页完成唤醒。
地址变换机构(需要硬件支持)
容量问题
容量:虚拟存储器的容量(地址空间)由计算机地址总线的数量来决定。虚拟存储器的页表是虚地址到内存地址或者外存地址的映射。通过页表项的status位来决定。缺页中断将外存中的块置入内存。
虚存实际容量由CPU地址长度和外存容量决定。一般情况下CPU的地址长度能表示的大小都大于外存容量。
- 当CPU的地址长度能表示的大小远远大于外存容量时,虚存的实际容量为内存和外存容量之和
- 当外存容量远大于CPU字长能表示的大小时,虚存的实际容量由CPU字长决定
进程的虚拟内存空间组成
虚拟内存使每个进程都以为自己独占了主存。每个进程看到的内存都是一致的,即虚拟地址空间。
在linux中,每个进程看到的虚拟地址空间由以下几个部分内存段组成.
| 段名 | 存储内容 | 分配方式 | 生长方向 | 读写特点 | 运行态 |
|---|---|---|---|---|---|
| 代码段 | 程序指令、字符串常量、虚函数表 | 静态分配 | 由低到高 | 只读 | 用户态 |
| 数据段 | 初始化的全局变量和静态变量 | 静态分配 | 由低到高 | 可读可写 | 用户态 |
| BSS段 | 未初始化的全局变量和静态变量 | 静态分配 | 由低到高 | 可读可写 | 用户态 |
| 堆(运行时) | 动态申请的数据 | 动态分配 | 由低到高 | 可读可写 | 用户态 |
| 映射段 | 动态链接库、共享文件、匿名映射对象 | 动态分配 | 由低到高 | 可读可写 | 用户态 |
| 栈(用户栈) | 局部变量、函数参数与返回值、函数返回地址、调用者环境信息 | 静态+动态分配 | 由高到低 | 可读可写 | 用户态 |
| 内核空间 | 操作系统、驱动程序 | 静态+动态分配 | 由低到高+由高到低 | 不能直接访问 | 内核态 |
地址从低到高,最高层的内核虚拟内存保存的是操作系统中的代码和数据,这部分每个进程都一样。
程序代码和数据
对所有进程来说,代码都是从同一个固定地址开始,紧接着是与全局变量对应的数据区。代码和数据区都是按照可执行文件的内容初始化的。代码和数据区在进程开始运行时就被指定了大小。
- 静态存储区内的变量在程序编译阶段已经分配好内存空间并初始化。这块内存在程序的整个运行期间都存在,它主要存放静态变量、全局变量和常量。
- 这里不区分data区和BSS区,静态存储区内的变量若不显示初始化,则编译器会自动以默认的方式进行初始化,即静态存储区内不存在未初始化的变量。(C++)
- 静态存储区内的常量分为常变量和字符串常量,一经初始化,不可修改。静态存储内的常变量是全局变量,与局部常变量不同,区别在于局部常变量存放于栈,实际可间接通过指针或者引用进行修改,而全局常变量存放于静态常量区则不可以间接修改。
- 字符串常量存储在静态存储区的常量区,字符串常量的名称即为它本身,属于常变量。
堆
而运行时堆是根据 malloc 和 free 函数的调用在运行时动态地扩展和收缩的。
手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间。使用 malloc 或者 new 进行堆的申请,堆的总大小为机器的虚拟内存的大小。
说明:new操作符本质上是使用了malloc进行内存的申请,new和malloc的区别如下:
- malloc是C语言中的函数,而new是C++中的操作符。
- malloc申请之后返回的类型是
void *,而new返回的指针带有类型。 - malloc只负责内存的分配而不会调用类的构造函数,而new不仅会分配内存,而且会自动调用类的构造函数。
共享库
地址空间的中间部分用来存放共享库的代码和数据。如 C 标准库、数学库等都属于共享库
栈
用户栈和堆一样,在程序执行期间可以动态的扩展和收缩,编译器用它来实现函数调用。当调用函数时,栈增长,从函数返回时,栈收缩
由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。整个程序的栈区的大小可以在编译器中由用户自行设定,VS中默认的栈区大小为1M,可通过VS手动更改栈的大小。64b的Linux默认栈大小为10MB,可通过ulimit -s临时修改。
地址翻译
地址变换机构是内存管理单元MMU中将逻辑地址映射为内存中物理地址的硬件系统。大多数处理器提供了内存管理单元MMU,位于CPU和主存之间。MMU在VA和PA之间执行快速的翻译。
逻辑地址logical address是程序产生的与段相关的偏移地址部分。源代码经过complie编译:生成可执行代码,产生若干代码块Object Module后,目标程序所用的地址就是逻辑地址。逻辑地址的范围叫逻辑地址空间。物理地址physical address在CPU外部地址总线上的寻址物理内存的地址信号。物理地址用户透明,转换过程硬件自动完成,转换过程叫做地址重定位。
当cache作用时:
逻辑地址是由程序员给出的。经过查询快表页表得到物理地址。但此时的物理地址并不一定是最终地址,因为如果cache命中则需要再转换成cache地址。转换后的开始中的地址才是最终地址。但如果开始不命中,则仍需要按原来经过代表或者业表得到的地址去直接访存。得到物理地址(内存地址)后,先访问cache,如果命中,转换成cache地址访问,如果没命中,则进行访存。