目录
进程地址空间的排布
操作系统中的地址排布如下:
静态分布
用代码来查看内存的静态分布:
动态开辟
堆开辟空间是由下到上开辟
栈开辟空间是由上到下开辟
代码验证:
static修饰的变量
被static修饰的变量,作用域不变,生命周期改变--相当于一个全局变量的生命周期, 是一个定义在函数中的全局变量:
进程地址空间
进程地址空间是什么,和之前的地址空间有什么不同吗?
以前定义变量、代码调试等操作,以为自己在监视窗口中看到的空间就是物理空间,其实是不准确的;我们以前认为的,其实不是物理地址,而是虚拟地址--进程地址空间。
为何说进程地址是虚拟的?
因为它可以实现同一个地址,存储的值不同!!!如下:
当父子其中一个进程中修改了全局变量的值后:
如果我们这里获取到的是物理地址空间,那么是不可能存在一个相同的地址存储了不同的值
进程是具有独立性的,当全局变量未修改时,父子进程共享同一段物理内存;
修改后,虚拟地址相同,但是获取到的值不同,那么一定是有两份物理内存来分别存储不同值,只是虚拟地址相同罢了。
如何划分区域?
在 mm_struct 这个结构体中,还存有一些结构体的指针,这些结构体中定义了每个区域的开始 start 和结束 end;由此堆进程地址空间进行了区域划分。
通过页表和物理内存建立联系
虚拟地址如何形成?
在程序在磁盘上要加载到内存形成进程,文件编译时(按照0000~FFFF进行编址),就会在磁盘中生成逻辑地址(相对地址),加载到内存中时,会以统一的标准:对逻辑地址进行一定的偏移,生成虚拟地址
不同区域的虚拟地址连起来组成了进程地址空间,进程的 task_struct 中 mm_struct 指向它。
由此我们可以知道程序编译后,没有加载到内存时,就已经有地址和区域了,加载只是将所有地址进行了二次加工变成虚拟地址而已。
分析为何一个地址两个不同变量?
进程具有独立性,代码共享数据独立。那么数据是如何独立?子进程会继承父进程 task_struct 大部分数据,包括 mm_struct 以及页表:
当在子进程下数据发生改变后,页表左侧的虚拟地址不改变,但与之对应的右侧物理地址发生改变,系统为确保数据的独立,分配新的物理内存给子进程
因此我们得到的地址是没有改变的虚拟地址,因为进程的独立性,页表也独立,虚拟地址不变,但是各自的页表上虚拟地址对应的物理地址不同。
上图所示的这中情况叫做写时拷贝,当进程需要内存空间时,先改变 mm_struct 中控制对应区域的大小--提前调节所需要的内存,相当于先声明在哪个区域,需要多少。等到需要写入数据时,才会根据区域为其开辟对应内存。
为什么要有虚拟地址?
1. 因为硬件只会被动的读取和写入,可以对转化过程进行审核,拦截非法操作,保护内存;
2. 通过地址空间,进行功能模块的解耦,令申请空间与访问空间分离;申请空间声明我要多少,访问空间时进行分配,写时拷贝,提高空间利用率;
3. 让进程或者程序用统一的视角看待内存,都在编译时转换为虚拟地址,需要访问时只管分配内存即可,简化进程本身的设计和实现。