程序地址空间
在学习C的时候,我们知道在32位系统下4G内存的分布,如图:
接下来我们通过在Linux下写几段代码来对这个内存分布更加深入的研究
写一个这样的代码:
运行后,我们发现子进程和父进程的val都是0,因为子进程继承了父进程的所有东西,然后val的地址也是一样的
如图,我们对代码进行修改:
我们对子进程的val值进行更改,然后却发现变量val的值得地址在子进程和父进程中还是一样的
我们知道,物理地址是唯一的,所以我们打印出来的val的地址 绝对不是物理地址,在Linux下,这种地址叫虚拟地址
我们在c/c++语言中看到的地址都是虚拟地址,物理地址用户是看不到的,由os同一管理
我画了一个示例图,一个使用虚拟地址的系统的示例图,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址,将一个虚拟地址转换为物理地址的任务叫做地址翻译,CPU上的叫做内存管理单元(Memory Management Unit)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址。
其实我们以前的程序地址空间是不准确的,准确的应该成为进程地址空间,接下来我们看看进程的地址空间
进程地址空间
我们刚刚知道了虚拟地址,那么为什么会有虚拟的地址空间,它又是如何使用的呢?又为什么需要虚拟的地址空间呢?
我们来看一下早期的内存管理机制
早期没有虚拟内存的时候,如果想要运行一个程序,会吧这些程序都装入内存,当计算机同时运行多个程序的时候,必须保证这些程序占用的内存总量要小于计算机实际物理内存的大小。
而且早期进程的物理空间是不隔离的,由于程序都是直接访问物理内存,所以恶意程序可以更改别的进程的数据
为了避免这种情况,采用了分段的方法
分段
在编写代码时,只要指明了所属段,代码段和数据段的所有地址都是从0开始,映射关系也都是由操作系统维护的
CPU把内存分为不同的段,所以指令和数据的有效地址并不是真正的物理地址而是相对于段首地址的偏移地址
如图,这样可以解决互相访问的问题,进程之间不会相互篡改数据,保证了安全性
因为段寄存器的存在,使得进程的地址空间得以隔离,越界问题就很容易被判断出来
实际代码和数据中的地址都是偏移量,所以第一条指令给可以从0开始,系统会自动的转化映射,也就解决了程序运行时地址不确定的问题
但是还有一个问题:
早期物理内存还会出现效率低的情况,如上图,有A和B两个进程,如果这时候想要运行C进程,而且进程C需要的内存大小大于剩余的内存,这时候系统就需要在已经运行的程序中选择一个把程序的数据暂时拷到硬盘上,但是我们知道硬盘的读取写入速度是远远慢于内存的,所以就会造成效率低的情况
分段并没有解决性能的问题,在内存空间不足的情况下依旧要唤出整个程序或者整个段,所以又有了一种解决方法
分页和虚拟地址空间
分页是一种很好的解决上面问题的方法,概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每个字节都有一个唯一的虚拟地址,作为到数组的索引。VM系统通过将虚拟内存分隔为虚拟页的大小固定的块,把物理内存被分割为物理页。
对虚拟内存和物理内存进行分页似乎并不能解决问题,但是对分页以后,我们有了页表,就可以实现共享内存的实现(几个进程同时共享物理内存)
最后我们再解释一下一开始的那个代码的问题
一开始的那个代码中,变量val,在父子进程中的虚拟地址相同,但是内容不同,被映射到了不同的物理地址中
下面这两篇博客是我从网上找到的博客,都是关于这块的知识的