Linux进程地址空间(1)
虚拟地址
我们在学习C/C++的时候经常看到这张图,那这张图的布局是物理上的内存布局吗?也就是包含的代码段
,静态区(全局区)
,堆
,栈
,还多了一个命令行参数和环境变量区
我们用一段代码来验证一下:
可以看到结果从代码段往下的地址在依次增加,说明代码段地址最低,然后是已经初始化的全局变量,未初始化的全局变量,堆,栈,然后是命令行参数以及环境变量。另外在堆内是从下往上增长的,和图中地址增长方向一致,heap1
<heap2
<heap3
,而栈当中是相反的,相当于一个开口向下的栈,新增一个变量是从下入栈的。
另外这里的&argv
以及&env
是表的地址,argv[]
和env[]
才是表中存放的指向变量的指针,这个C语言中应该很熟悉了就不再多说,可以验证一下,它们的地址都是要比栈区地址高的。
了解了图中的布局后我们再回归刚开始的问题,这张图是真实的物理内存的布局吗?下面来看一段代码:
创建子进程,打印g_val的值
可以看到打印出来的值相同并且地址也是一样的,这很正常对吧,毕竟子进程并没有对变量进行修改之类的操作
下面我们就让子进程对变量修改,来看看修改后的结果
我们让子进程把g_val的值修改成了10,奇怪的事情来了,为什么修改后打印出来g_val
一个值是0一个值是10,它们的地址为啥会是一样的捏??
原因是这个地址存放了一个变量的两个值吗?这显然不对。于是我们就引出了一个概念:虚拟地址!并且我们平常在语言中所接触到的都是虚拟地址,而不是物理上的内存地址!!!
进程地址空间的管理
什么是进程地址空间呢?这个等一下再说,我们先来解释我们是怎么把虚拟地址和物理中的实际内存联系起来的。
我们都知道进程具有独立性,也就是子进程对于变量的修改不会影响到父进程,操作系统中有一个叫页表
的东西,它把我们虚拟地址和内存物理地址形成映射也就是对应的关系最终形成一张表
,通过这张表我们就能访问对应的数据。
并且每一个进程都有一张页表,子进程会继承父进程的页表,当子进程对数据修改的时候,操作系统在内存中又额外开辟了一块空间,让g_val
变成10,然后再重新更新页表,把虚拟地址和内存映射起来,也就是说从始至终虚拟地址都没有变过,改变的是虚拟地址映射的内存地址,而我们在语言中所见到打印出来的都是虚拟地址,这也就是为什么一个变量两个值它们的地址是相同的!!!!这也就是写实拷贝。
什么是进程地址空间?
接下来我们来看看进程地址空间,所谓进程地址空间,其实上也是一个结构体mm_sstruct
,它里面存放了很多的信息,就和PCB很像,存放的是进程地址空间的一些属性,同样的操作系统需要管理这些结构体,所以也存在一个指针,通过它把这些进程地址空间链接起来管理!其实上它也就是一种数据结构。
当有一个新的进程时,操作系统不仅要创建一个PCB,还要创建这个进程地址空间的结构体,在PCB中存在着一个指针指向这个进程地址空间。
而这个结构体中是如何划分内存的呢?
可以看到源码中存在的这些start
,end
,就是用来描述每个区域是从哪里到哪里截止,如start_code
是代码段的开始,end_code
就是代码段的结束,
同时也存在一个指针指向对应的页表,另外还有其他属性在这里不多谈,大家可以自行去看源码。
页表的硬件层面
我们知道了页表的存在后,那硬件层面是如何实现的呢?
硬件中有一个MMU
模块,它里面有一个寄存器CR3
负责把虚拟地址转换成对应的物理地址,转换的逻辑我们后面再谈,当CPU执行到对应的代码需要读取数据的时候,先拿到虚拟地址,然后通过页表找到内存中的数据最终得到访问。
举个例子:
我们定义一个变量int a=1,并且输出a的值,cout<<a,写好程序后这个程序存放在磁盘当中,当运行时被加载到内存中,当程序执行到
int a = 0; 时,a 被分配在进程的虚拟地址空间中的某个位置。虚拟地址空间中的数据通过页表映射到物理内存,当程序尝试访问变量 a
的值时,CPU发出虚拟地址请求,MMU 使用页表将虚拟地址转换为物理地址,最后进行读取,并打印出它的值。
这应该不难理解,那好奇的是为什么我们不直接访问物理内存,反而要创建进程地址空间这么麻烦,又是映射又是管理呢?
进程地址空间的作用
1、把物理中的无序变成有序,让进程以统一的视角看待内存
解释一下就是:实际的物理内存可能是分散和无序的。物理内存中的地址可能不连续,且由于内存管理策略和系统的内存分配行为,物理地址的布局可能是复杂和动态变化的。进程地址空间使每个进程都有自己独立的虚拟地址空间。进程在自己的虚拟地址空间内访问内存时,不需要关心实际的物理内存布局。每个进程看到的地址空间都是从0开始的连续区域,这种视角是统一的。
2、把内存管理和进程管理解耦合
对于磁盘的程序在内存中的加载只需要做好加载程序的工作不需要考虑进程是如何读取数据的,对于进程而言不需要考虑内存中的布局,只需要通过页表的映射去读取数据罢了。
3、地址空间+页表是保护内存的重要手段
通过进程地址空间的映射,每个进程都有自己的内核数据结构,自己的代码,自己的数据,相互之间完全独立互不影响。
以上就是对进程地址空间的初步了解,如有错误,欢迎指正,谢谢大家!