进程的地址空间
前面的很多次,文章里提到过很多次,程序地址空间是这样的:
之前提到过很多次,这就是我们常说的,4G地址空间。可是我们还说了,它并不是真正的内存
先看一段代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int g_val = o;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork error");
return 0;
}else if(id == 0){ //子进程
printf("child[%d]: %d : %p\n",getpid(),g_val,&g_val);
}else{ //父进程
printf("parent[%d]: %d : %p\n",getpid(),g_val,&g_val);
}
sleep(1);
return 0;
}
这段代码的运行结果是:
我们发现,输出的变量和地址是一模一样的,这个很好理解,因为子进程按照父进程为模板,父子并没有对变量做任何修改,所以输出的变量和地址是一样的。
如果我们把代码这样稍微改一下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork error");
return 0;
//子进程,子进程肯定先跑完,也就是说子进程先进行修改,完成之后,父进程再读取
}else if(id == 0){ //子进程
g_val = 100;
printf("child[%d]: %d : %p\n",getpid(),g_val,&g_val);
}else{ //父进程
sleep(3);
printf("parent[%d]: %d : %p\n",getpid(),g_val,&g_val);
}
sleep(1);
return 0;
}
代码改成这个样子,运行的结果是:
到这我们就奇怪了,父子进程输出的地址都是一样的,为什么变量内容却不一样。
所以我们就得出了如下结论:
1.变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。
2.通过输出的地址是一样的,说明,这个地址绝对不是实际的物理地址。
3.这种地址,在Linux下被称为虚拟地址。
4.我们在使用C/C++编程的时候,所看到的地址,都是虚拟地址,物理地址,用户一概看不见,由操作系统同一管理。
所以,我们一开始画的那张图准确来说,叫做 4G虚拟地址空间。
操作系统必须负责将 虚拟地址 转换为 物理地址。
所以,我们一开始说的程序的地址空间也是不准确的,应该叫做 进程地址空间。那我们应该怎么理解这个进程地址空间呢
先来看看早期的内存管理机制:
早期的内存分派方式
1.要运行一个程序的时候,会把这些程序都放入内存中。
2.当计算机要同时运行多个程序的时候,必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。
3.地址空间不隔离。由于每个程序都是直接访问物理内存,所以恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的。
4.内存使用效率低,在A和B都运行的情况下,如果用户又运行了程序C,而C程序需要5M大小的内存才可以运行,而此时内存只剩下4M空间可以使用,所以此时系统必须在已运行的程序中选择一个将该程序的数据暂时拷贝到硬盘上,释放出部分空间来供C程序使用,然后再将C程序的数据全部装入内存中运行。
5.程序运行的地址不确定,当内存中剩余空间可以满足程序C的需求后,操作系统会在剩余的空间中随机分配一段连续的20M大小的空间给程序C使用,因为是随机分配的,所以程序运行的地址是不确定的,这种情况下,程序的起始地址都是物理地址,而物理地址都是在加载之后才能确定的。
因为这中管理机制的缺点比较多,就有了后来这种分页&虚拟地址空间。
这张图就解释之前那段代码,同一个变量,地址相同,其实是虚拟地址相同,内容不同是被映射到了不同物理地址。