在C语言刚入门的时候我们曾经见过这一个图片
当时我们认为这就是内存,但是今天我们要推翻这个结论,它的真实名字叫进程的地址空间。我们对地址空间先描述再组织,它是一个结构体存在很多的区间,通过调节区间的开始值和结束值来管理虚拟地址。
运行如下代码
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
int main()
{
int f = 100;
pid_t id = fork();
if(id == 0)
{
cout<<f<<endl;
cout<<&f<<endl;
}
if(id > 0)
{
f = 200;
cout<<f<<endl;
cout<<&f<<endl;
}
}
运行结果如下
很明显,他们的显示的地址相同,但是真实的值不同,可以确定的是,这个地址并不是数据储存的真实地址,而是虚拟地址。
实际上虚拟地址和物理地址的关系如下
有父子关系的进程有相同的虚拟地,在通过页表的映射和硬件mmu的管理下,确定物理内存的地址。
需要注意的页表通过分级页表节省空间
通常在32位的地址中系统会将其分为三部分,将32个比特位分为10,10,12的部分,所以理论上有2^10个一级页表,已经之上的二级页表,和最终4k大小的三级页表,一级页表是存在的,但是二级页表会根据情况来确定是否存在,三级页表也是如此,节省了空间。
级页表是基于虚拟地址的分段来划分等级的,最低等级的页表上保存了最终的虚拟页号和物理页号的对应关系。
例如拿32位的虚拟地址来说,如果页面的大小为4K,也就是12位,那么地址空间内将有20位,也就是1M的页表项目,每个项目对应一个虚拟页面。
那么对于地址空间中用于表示页号的20位地址再次分级,分成10位的一级页号和10位的二级页号呢
也就是说根据一级页号可以知道1K个连续的页面中是否已经有被加载到内存或者被置换到交换空间中的,如果一级页表中这1K个页面没有任意一个页面被加载或者置换郭,那么就不需要在为这1K个页面维护2级页表了。
理论上来说,一个进程开始运行的时候只需要3个1K的页面就可以运行了,即一K的代码段页面,一K的数据段页面和一K的桟页面。因此,只需要1K个一级页表单元一级3K个二级页表单元即可,远远小于连续页面类型的页表需要的1M的页表单元
这么做的好处如下:
- 可以防止某些恶意进程对物理内存进行破坏
- 每一个进程都认为自己独占内存资源便于管理