目录
一、程序地址空间的基本概念
二、程序地址空间的结构编辑
三、虚拟地址和物理地址的关系
四、为什么要使用程序地址空间
一、程序地址空间的基本概念
要理解我们的程序地址空间,首先就要认识下面这张图:
这张图上所表示的内容,想必大家之前也有所了解,比如:栈是向下增长的,堆向上增长的。这里还给大家补充一点:其实我们的常量所在区域是紧挨着我们的正文区的(代码段),很显然正文区是不允许被修改的,所以我们的常量也是不能被修改的。
接下来我们来写一段代码:
之前我们已经知道了,fork()会有两个不同的返回值,现在让我们再来观察观察这返回值的储存情况:
有两个返回值,我们还能理解,可以为什么他们的地址也一样呢,这就很离谱了,一个地址却有两个不同的返回值。所以在这个地方的地址,一定不是我们的物理地址,那它是什么呢?
- 在Linux地址下,这种地址叫做虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
那什么是虚拟地址呢,什么又是程序地址空间呢?
我们可以这样理解,我们的进程肯定需要需要一些内存,于是我们的OS就给我们的进程画了一张饼(意思就是给你多少空间呀),而这个画的饼就是我们的地址空间,而我们的大饼并不是实实在在的东西,并不真的物理地址空间,所以我们又可以称其为虚拟地址。
二、程序地址空间的结构
这张图其实就是我们的程序地址空间的基本结构,这部分空间我们的OS肯定也是要进行管理的,那怎么进行管理呢?从而可以推出地址空间一定是一个内核级别的数据结构对象,其实就是一个内核结构体struct mm_struct,我们的进程PCB肯定也会有指针指向这段空间。。那这个结构体中会存些什么呢?
struct mm_struct
{
long code_start;
long code_end;
long data_start;
long data_end;
long heap_start;
long heap_end;
...
}
根据这几个字段名,我们可以看出来,其实这个结构体的内容就是对所分配的空间进行区域划分,start表示区域的开始,end表示区域的结束。那么其实区域的变化就是对start和end做加减。
三、虚拟地址和物理地址的关系
物理内存必须存在(根据冯诺依曼原理,数据一定从内存上读取)。每个进程运行之后,都会有一个进程地址空间的存在,就是上述图的结构。
OS会为我们的进程维护一张映射表(页表),通过虚拟地址空间去找物理地址,读去内容。什么是我们的映射关系呢,其实可以简单理解成我们的KV模型,其中一个对象的元素与另一个对象的元素建立起一一对应的关系。用一张图来理解一下:
首先我们可以明确的是每个进程都要在系统中有一个自己的页表映射结构。子进程创建是会拷贝父进程的PCB,进程地址空间,页表(包括虚拟地址,物理地址)。
当我们子进程修改部分变量的值时,为了确保进程间的独立性,所以在物理地址上重新开辟一块空间,拷贝父进程物理空间的数据,然后再进行写时拷贝,最后再改变子进程页表的映射关系。
四、为什么要使用程序地址空间
- 可以让进程以统一的视角看待内存,所以任意的一个进程,可以通过地址空间+页表可以将乱序的内存数据,变成有序,分门别类的规划好。
页表是如何将无序变为有序的呢?首先我们的进程不会关心其在内存是如何分布的,也就是说我们各种数据在内存中可能是东一块西一块的,是乱序的。但是通过页表,通过映射关系,我们可以将这种无序态转变为我们的有序态。 - 存在虚拟地址空间,可以有效的进行进程访问内存的安全检查。
- 将我们的进程管理和内存管理进行解耦。
通过虚拟内存技术,操作系统为每个应用程序提供了一个独立的虚拟地址空间。应用程序的内存地址空间是虚拟的,与物理内存地址空间分离。这使得操作系统可以在不同的物理内存地址上动态映射和重定位虚拟地址,从而实现了内存的动态管理和分配。 - .通过页表,进程映射到不同的物理内容处,从而实现进程的独立性。