浅析物理内存、虚拟内存和进程的地址空间

   ●在一个系统中,进程是和其他进程共享CPU和主存资源。但是共享资源会造成一些不可避免的问题,例如由于存储器不够而         进程无法运行,亦或是存储器被外来的恶意进程破坏等。

早期的内存管理机制:

 分派方式一:程序载入

1》CPU要访问进程中的数据,必须通过访问访问物理内存的方式物理寻址

        当CPU要执行某条指令时,它会生成一个有效物理地址,通过存储器总线,把它传递给主存,主存取出从物理地址4处开始的四个字节的字,并将它返回给CPU,CPU会将它放在一个寄存器里。

2》一个程序要运行,必须把这些程序加载到内存中。

3》进程的地址空间是连续的。因为程序是直接访问物理内存,所以恶意的程序会随意修改别的进程的内存数据以达到破坏的目         的;有些程序不是恶意的,但有bug的程序也会有可能修改了其他程序的内存,危害系数高。

4》内存使用效率低。如上图,硬盘中三个可执行文件不能同时加载到内存中当A和B同时运行,如果还想运行C,就必须把

     已经运行过的A或B的数据暂时拷贝到硬盘上,释放出空间C才能加载到内存运行。

5》程序运行的地址是起始地址,而起始地址要加载到内存中才能确定。当A或者B释放出空间够C运行时,操作系统会在这段空         间中随机分配连续的30M的空间给C程序,由于是随机分配,所以程序运行的地址并不能确定。

 分派方式二:段式管理

说明:

  ●把程序和内存通过某种关系进行分段,每段有独立的名字,如代码段、数据段。

  ●在编写的时候,只要指明了所属的段,代码段和数据段中出现的所有地址。都是从0开始,操作系统维护其映射关系。

  ●由于CPU将内存分割成不同的段,因此数据指令的有效地址并不是真正的物理地址,而是相对地址(相对于段首0的偏移地址)。

优点:

1》可以分别编写和编译。

2》由于段寄存器的存在,使得进程的地址空间得以隔离,越界问题就可以被判定出来。

3》实际代码和数据中的地址都是偏移量,第一条指令从0开始,而实际的物理地址可以通过计算得出,解决了程序运行地址不确定的问题。

缺点:

         分段管理在内存空间不足时,依旧要唤出整个程序或者整个程序段,在工程量大的情况下,就会造成内存和硬盘之间数据的大量拷贝,导致时间和效率都不高,进而影响性能。

虚拟地址空间的提出

     为了改善早期内存管理机制而造成的性能问题,为了保护物理内存,提出了虚拟地址空间的概念。

     增加一个中间层,利用间接的地址访问方法访问物理内存:程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到合适的物理内存地址上。这样一来,只要操作系统处理好虚拟地址到物理地址的映射关系,就可以保证不同的程序最终访问不同的区域,从而达到内存地址空间的隔离。

上图就是进程的虚拟地址空间,它并不是内存!!而是一个结构体(mm_struct)。

进程的虚拟地址空间是连续的,地址从00000000到ffffffff,共有2^31字节个地址,也就是4G,每一块空间可以用初始地址和终止地址来表示这块区域。

分析一段代码:

 1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int g_val=100;
  5 
  6 int main()
  7 {
  8     pid_t id=fork();
  9     if(id==0){//child
 10         printf("I am child,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 11     sleep(2);
 12     }else{//parent
 13         sleep(3);
 14         printf("I am parent,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 15     sleep(1);
 16     }
 17     return 0;
 18 }   

输出结果:

            结果看出,父进程和子进程输出的变量值都是一样的。很好理解,因为子进程以父进程为模板,父子进程没有对变量进行任何修改。

           ● 回顾内容:父进程创建出子进程,代码共享,数据以写时拷贝的方式各自私有一份。

                写时拷贝:只有需要的时候再给,物尽其用;只有要修改的时候才复制一份父进程的内容。

如果上述代码子进程将全局变量g_val进行修改:

 1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int g_val=100;
  5 
  6 int main()
  7 {
  8     pid_t id=fork();
  9     if(id==0){//child
 10         g_val=300;
 11         printf("I am child,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 12     sleep(2);
 13     }else{//parent
 14         sleep(3);
 15         printf("I am parent,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 16     sleep(1);
 17     }
 18     return 0;
 19 }
 20 

输出结果:

从结果可以看出,因为子进程对变量进行了修改,因此变量值不同,但输出的地址依旧相同。

1》父子进程输出变量值不一样,说明输出的变量不是同一个变量。

2》输出地址相同但变量值不同,说明这个输出的地址一定不是物理地址,这个地址叫做虚拟地址(不是真实存在的)。

3》物理地址存在于物理内存中,同理,虚拟地址存在于虚拟地址空间中。要进行数据访问,必须由操作系统将虚拟地址转化为物理地址。

4》我们在C语言和C++中看到的地址都是虚拟地址。

5》用户是看不到物理地址的,物理地址由操作系统统一管理。

上面的例子我们用地址空间简单表示:

  ●第一次是父子进程将虚拟地址通过页表映射到了同一个物理地址,实现了共享

  ●第二次子进程修改了变量,父子进程以写时拷贝的方式私有数据,相同的虚拟地址被映射到不同的物理地址,实现了私有

虚拟地址空间:

 同一个变量,地址相同其实是虚拟地址相同,内容不同是因为OS通过页表和+MMU将虚拟地址映射到了不同的物理地址。

操作系统是通过软硬件结合的方式完成了映射。(软件:页表   硬件:MMU)

猜你喜欢

转载自blog.csdn.net/a_struggling_monkey/article/details/83655246
今日推荐