一、虚拟内存空间
虚拟内存空间是系统的一种技术,当程序被载入内存时,运用虚拟内存空间技术让程序误认为自己目前独占电脑内存,能够占用电脑所有的内存,访问所有内存地址。
以32位操作系统为例:
32位系统程序的指针为32位(4字节),2^32 = 4GB,也就是说指针可以取值的方法有2^32种,可以访问2^32地址。这也就为什么有种说法:32位系统支持装最高4g内存。当程序载入内存后,系统为程序赋予4GB虚拟空间。而程序理解在虚拟空间中的地址就是逻辑地址。但是逻辑地址只是一种假象,并不是指系统真的为程序分配了4GB内存。如下图,程序载入虚拟内存空间:
上图中程序的.text段基址是0x08048000,0x08048000就是虚拟地址,每个被载入内存的程序都有可能基址是0x08048000。这是运用了虚拟内存空间技术,实际中是不可能存在的,如果每个程序都使用真实地址0x08048000内存,那两个程序就互相干预,导致数据的混乱。
那是不是这 4GB 的虚拟地址空间应用程序可以随意使用呢? Windows 系统中,这个虚拟地址空间被分成了 4 部分: NULL 指针区、用户区、64KB 禁入区、内核区等。
1、NULL指针分区是NULL指针的地址范围。对该区域的读写将引发访问违规。
2、用户分区是应用程序能使用的,大约 2GB 左右。 用户分区又进行了详细的分区。PE文件结构对程序进行了分区
3、禁止访问分区只有在win2000中有。这个分区是用户分区和内核分区之间的一个隔离带,目的是为了防止用户程序违规访问内核分区。
4、 内核方式分区对用户的程序来说是禁止访问的,操作系统的代码在此。内核对象也驻留在此。
由上会产生一个疑问?虽然程序被分配在虚拟空间,但是程序要想运行的话最终还是要回归到真实内存中来。如果程序需要内存大于4GB,而系统只有4GB内存。那程序不还是运行不了。
实际上程序载入内存时并不是一次性全部载入内存。程序运行时存在局部性现象,就是说程序在短时间内只会运行某一局部代码。因此仅需将那些当前需要的少数页面或段载入内存即可运行,但系统继续往下运行,发现缺页或缺段就会触发中断请求,由操作系统将程序请求的页或段载入内存,继续运行。
二、分段 分页
多个程序的运行,对内存的使用和销毁会使得内存产生许多碎片。但需要载入一个新程序为20M,而内存目前存有三个碎片:5M,15M,8M。任何一个碎片都不能完整载入整个程序。那么我们可不可以那20M程序分成5M,15M,5M分别载入这三个内存碎片中呢?基于此思想产生离散式内存分配。分为如下三种:
分页存储管理:
将用户程序的地址空间分成若干个固定大小区域,称为页,同时将内存空间分成对应大小物理块。系统中维护一个页表,通过页与物理块的对应,完成逻辑地址到物理地址(实际内存的地址,比如内存条)的映射。
当进程访问某个逻辑地址中数据,分页系统地址变换机构,用页号检索页表,如果页号大于或等于页表长度,则产生地址越界中断。否则将页表初始地址与页号和页表项长度乘积相加得到页表物理号的地址,获取到物理块号。再将物理块得到的地址与页内地址组合得到物理地址。
如果选择的页面太小,虽然可以提高内存利用率,但是每个进程使用过多页面,导致页表过长。降低页面换入换出效率。
分段存储管理:
将程序地址空间分成若干段,段大小不定,每段定义一组相对完整信息,在内存分配时以段为单位。
其地址变换与分页类似。
分段的使用为了满足用户的编程和使用要求。用户通常将自己的程序按逻辑分成若干段,每段从0开始编址,有名称和长度,如:程序段text,数据段data。栈等。所以汇编中程序员访问的地址由段号和段内地址决定的。常有段寄存器:
段(segment)寄存器:
代码段(code segment)寄存器CS;
堆栈段(stack segment)寄存器SS;
数据段(data segment)寄存器DS;
附加段寄存器ES;
而且段的使用有利于信息共享,分页存储的话一个共享的过程往往需要占用数十个页面;信息保护同样可以以逻辑单位为基础,且经常以一个过程、函数或文件为基本单位;同理段存储很好解决了数据段动态增长,程序的动态链接等问题。
段页式存储管理:
同时利用分页存储和分段存储优势,过程类似。
三、系统对逻辑地址的管理
程序有时候需要分配一段连续内存,物理内存由于多次重复分配产生很多碎片。目前逻辑地址内存和物理内存占用如下:
在使用GlobalAlloc等函数时,指定GMEM_MOVABLE(允许系统对逻辑地址进行管理)和GMEM_FIXED参数(允许在物理内存中移动内存块,但是必须保证逻辑地址是不变的。),对上面逻辑内存使用GMEM_MOVABLE参数时形成如下连续内存: