Linux基础内容(12)—— 程序地址空间

目录

1.误区和它的由来

2.虚拟地址的证明

3.虚拟地址的实现

1.虚拟空间的解释

2.操作系统管理和规划虚拟空间

3.虚拟地址与物理地址的联系

4.多进程的虚拟地址解释

5.磁盘中可执行文件的地址

6.进程地址空间出现的原因


接上面内容

Linux基础内容(11)—— 进程理解


1.误区和它的由来

初学者会理解该地址空间就是一个容器,它用来存放不同的变量,并且会伴随一个地址的产生,使得我们以为这个程序地址空间是真实存在的,毕竟c程序还能传回指针。但是我们要知道,其实这些都是假的。

Linux基础内容(12)—— 程序地址空间_第1张图片


2.虚拟地址的证明

 那么如何知道它是一种虚拟的呢?

编辑这一串代码:fork产生出父子进程,子进程循环打印,当进行10次后global变为300;父进程循环打印。 

  1 #include
  2 #include
  3 #include
  4 
  5 int global_value = 100;
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id < 0)
 11     {
 12         printf("fork error\n");
 13         return 1;
 14     }
 15     else if(id == 0)
 16     {
 17         int cnt = 0;
 18         while(1)
 19         {
 20             printf("我是子进程,pid:%d,ppid:%d,global_value:%d,&global_value:%p\n",getpid(),getppid(),global_value,&global_value);
 21             sleep(1);
 22             cnt++;
 23             if(cnt==10)
 24             {
 25                 global_value = 300;
 26                 printf("子进程已经更改了全局的变量了..............\n");
 27             }
 28         }
 29     }
 30     else
 31     {
 32         while(1)
 33         {
 34 
 35             printf("我是父进程,pid:%d,ppid:%d,global_value:%d,&global_value:%p\n",getpid(),getppid(),global_value,&global_value);
 36             sleep(2);
 37         }
 38     }
 39     sleep(1);
 40     return 0;
 41 } 

如果我们相信程序地址空间的格式遵循那个表图,那么运行该文件后会出现这样吊诡的结果:两个子进程的地址一致,但是地址中的变量不一样。因为一个地址不能同时存在不同的变量,所以地址空间并不是真的。

Linux基础内容(12)—— 程序地址空间_第2张图片

 结论:语言学习过程中的地址概念,指针绝对不是物理地址,它是虚拟地址(线性地址/逻辑地址)


3.虚拟地址的实现

1.虚拟空间的解释

1.进程认为自己独占系统的资源(运行时占领资源,不运行时不会察觉到)

就像存钱,只要你的钱存进去,你就没有了对钱的控制权,存钱方拿这比钱利滚利。当年想那会,存存钱方会把它偌大的资金堆中拿出你指定的金额让你使用。

2.操作系统运行多个进程,给每一个进程分配进程地址空间

那么操作系统要怎么分配的?操作系统对程序执行后的虚拟地址空间进行管理,用数据结构进程描述(mm_struct)后进行管理。

2.操作系统管理和规划虚拟空间

回到虚拟地址空间的思想(以32位为例,64同理可推):

1.地址空间的存储是字节存储

2.32位下,每个地址是4字节,共可以表示4G(2^32)个地址

3.每个存储单元的地址唯一,那么每个单元都可以被标记上32位的01数字,显然是独一无二的地址,从低到高依次编制数字。那么我们默认进程空间就这么大,毕竟地址最大只能描述这么大了。

4.地址空间中划分的每个区域的规定:就是从某个地址到某个地址加以规定后记录,便是所谓的区域,形成了所谓的栈区、堆区...等。这些划分的地址被操作系统描述出数据结构类型进程描述并且存储记录,并且在适当的时候(某个区域大小不够)进行调整划分,所以进程空间中对应的地址空间的划分就形成了。

所以,我们就能理解为什么是虚拟地址了,因为本质每个单元后面并没有地址标明单元到底在哪个位置,只是操作系统存储了地址的范围,其实改变地址就是改变数据结构里那32位的变量。malloc等扩大空间、free等缩小空间的行为就是修改数字而已。

当然此时我们理解的地址空间不在是依附于C/C++的空间了,其实本质是进程的地址空间。

3.虚拟地址与物理地址的联系

要知道,物理地址就是磁盘的执行文件加载到内存变为进程,这些进程存出的空间就是物理地址。虚拟地址和物理地址通过页表联系;页表一侧记录虚拟地址,一侧记录物理地址。上层拿的是虚拟地址,虚拟地址通过页表联系物理地址,使得物理地址执行命令。页表的介绍以后再解释。


4.多进程的虚拟地址解释

我们回归到之前让我们觉得吊诡的结果,其实是不同进程,操作系统开辟不同位置的物理内存,并且用虚拟地址加以描述,搞得我们觉得一个进程就决定了整个内存,其实内存被分为了很多个小片段来执行这些进程。那么虚拟地址的也只是反映了进程空间中的地址,真正的物理地址空间被页表给隐藏了,不能得到物理地址。

那么上面代码出现父子进程地址一样原因是fork创建子进程会复制父进程的地址空间和变量数据。但是后来子进程变量改变了,为了不让父子进程冲突,子进程的虚拟地址所指的页表映射不同,所有在物理地址中的变量地址也就不同了。这样的行为被叫做“写时拷贝”,这些行为都是操作系统自动执行的。


5.磁盘中可执行文件的地址

那么我们编译文件时,那些函数调用是怎么来的,进程都没有生成啊?其实不矛盾,首先PCB指向的空间进程管理的是堆区和栈区等地址。但是它是要在加载进程的时候调整的,那么进程没有生成前,其实代码段和数据区这些由编译给出的。编译器在生成可执行文件时,可执行文件中调用的代码的地址和C库函数链接生成函数表时就已经整理好函数对应的地址了,并且执行文件的变量也生成了地址,都一并在变成进程时加载到内存中去了。程序的函数等地址是逻辑地址,加载到内存之中就拥有了物理地址。最后把虚拟地址填入到页表,再联系内存的物理地址,然后加载进程后调整栈区和堆区的地址,达到整体进程空间的完整。

Linux基础内容(12)—— 程序地址空间_第3张图片

最后CPU访问进程的所有数据的地址,返回的地址都是虚拟地址即进程空间对应的地址。


6.进程地址空间出现的原因

1.进程如果直接访问内存,越界访问其他的文件,那么对物理地址本身有很大的影响。所以需要进程地址间接访问物理地址空间。越界也不会伤害到物理地址的内容,因为页表会进行越界判断。即保护物理内存。

2.方便进程与进程之间数据代码的解耦,保证进程的独立性

3.让进程以统一的视角来看待进程对应的代码和数据等各个区域,编译器也以统一视角编译代码,方便使用。

你可能感兴趣的:(c++,linux,centos)