C/C++程序地址空间
思考一个问题,这里的地址是什么地址???
使用程序打印程序地址空间:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int g_unval;
int g_val = 100;
int main()
{
// 验证地址空间
const char* s = "hello world!";
printf("code addr: %p\n", main);
printf("string randonly addr: %p\n", s);
printf("uninit addr: %p\n", &g_unval);
printf("init addr: %p\n", &g_val);
char* heap = (char*)malloc(10);
printf("heap addr: %p\n", heap);
printf("stack addr: %p\n", &s);
printf("stack addr: %p\n", &heap);
int a = 1;
int b = 2;
printf("stack addr: %p\n", &a);
printf("stack addr: %p\n", &b);
return 0;
}
还可以发现栈向低地址生长,堆向高地址增长。
看另外一段代码,使用fork创建子进程,在第3秒的时候改变了子进程的数据,观察父子进程该数据的地址:
可以发现数据修改后,父子进程数据的地址还是一样的,但是变量的内容不一样!
1、变量内容不一样,父子进程输出的变量绝对不是用一个变量。
2、地址值一样,说明,该地址绝对不是物理地址!
3、在Linux下,这种地址叫做虚拟地址(线性地址)
4、所以,我们在C/C++看到的地址,全部都是虚拟地址!用户看不到物理地址,OS必须负责把虚拟地址转成物理地址(通过页表+MMU)
进程虚拟地址空间
和进程控制块的思维一样,进程虚拟地址空间也要采用先描述的方式!
struct mm_struct {
int code_start;
int code_end;
int init_val_start;
int init_val_end;
// ...
}
每个进程都认为自己有一个地址空间,都认为自己在独占物理内存。每个进程都认为自己有4G,认为地址空间的划分是按照4GB空间去划分的。每个人,都用相同的方式去看待内存。
代码上,地址空间上进行区域划分时,对应的线性位置 即 虚拟地址。
为什么要有地址空间?
1、通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质目的是为了保护物理内存以及各个进程的数据安全。
2、将内存申请和内存使用的概念在时间上划分清楚,通过虚拟地址空间,屏蔽OS底层申请内存的过程,达到进程读写内存,OS进行内存管理操作,进行软件上的分类。
比如,我们申请了1000个字节,我们不一定能立马使用1000个字节。在OS角度,如果这块空间现在给你,意味着会有一部分空间浪费,可以给别人用地现在却被你闲置了!所以只是一张空头支票,等你真的要用的时候,会发生 缺页中断 进行物理内存的申请。
类似的还有,数据的写时拷贝。
3、站在CPU和应用层角度,进程统一使用4GB空间,而且每个空间区域的相对位置比较确定。
OS设计的最终目的,都是达到一个目标:每一个进程都认为自己是独占系统资源的。
这里需要再次区分进程和程序的概念。
所以回过来看上面的程序,同一个变量的地址相同,其实说的是她的虚拟地址相同,内存不同是因为被映射到了不同的物理空间。如下图所示: