进程地址空间
Posted 两片空白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程地址空间相关的知识,希望对你有一定的参考价值。
目录
在博客C/C++内存管理一文中,我们了解到了C/C++的内存管理,大概了解了内存分布情况。实际完整的进程地址空间如下。
来一段代码验证一下:
1 #include<stdio.h>
2 #include<stdlib.h>
3 int g_val1=1;
4 int g_val2;
5 int main(int argc,char *argv[],char *env[]){
6 //代码区地址
7 printf("%p\\n",main);
8 //全局数据区
9 printf("%p\\n",&g_val1);
10 printf("%p\\n",&g_val2);
11
12 int *m=(int *)malloc(10);
13 //堆地址
14 printf("%p\\n",m);
15 //栈地址
16 printf("%p\\n",&m);
17 //命令行参数地址
18 printf("p\\n",argv[0]);
19 //环境变量地址
20 printf("%p\\n",env[0]);
21
22 return 0;
23 }
但是,学了操作系统的进程地址空间后,发现这并不是我们所理解的内存。
一.发现问题
先来一段代码:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int g_val=0;
6 int main(){
7 pid_t id=fork();
8 if(id==0){
9 printf("i am child... pid:%d, g_val:%d, &g_val:%p\\n",getpid(),g_val,&g_val);
10 sleep(1);
11 }
12 else if(id>0){
13
14 printf("i am father... pid:%d, g_val:%d, &g_val:%p\\n",getpid(),g_val,&g_val);
15 sleep(1);
16 }
17 else{
18 perror("fork");
19 exit(1);
20 }
21
22 return 0;
23 }
但是将代码稍加改动,在保证子进程先跑,并且在子进程里改动全局变量g_val的值。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int g_val=0;
6 int main(){
7 pid_t id=fork();
8 if(id==0){
9 g_val=100;
10 printf("i am child... pid:%d, g_val:%d, &g_val:%p\\n",getpid(),g_val,&g_val);
11 sleep(1);
12 }
13 else if(id>0){
14 //父子进程顺序有调度器决定
15 sleep(2);//保证子进程先跑
16 printf("i am father... pid:%d, g_val:%d, &g_val:%p\\n",getpid(),g_val,&g_val);
17 sleep(1);
18 }
19 else{
20 perror("fork");
21 exit(1);
22 }
23
24 return 0;
25 }
这就发现一个问题,如果系统的内存像我们上面那样分布,在同一个地址空间里的内容不可能不一样。(值是先改变的)
二.得出结论
- 如上问题,同一地址空间的内容不一样,所以,父子进程输出的变量绝对不是同一个变量。
- 但是他们的地址值相同,所以这个地址绝对不是实际的内存物理地址。
- 在Linux地址下,这种地址叫做虚拟地址,我们用C/C++语言看到的地址全部都是虚拟地址,物理地址用户看不到,有操作系统管理。
- 操作系统负责将虚拟地址转化为物理地址
进程地址空间不是实际的内存。
三.实际的进程地址空间
可能你现在还看不懂这张图,看完下面的说明,你再来看可能就很清楚了。
3.1 什么是进程地址空间?
进程地址空间是一张描述进程占有资源的一张表。再内核中的体现为一个数据结构mm_struct,将实际内存划分成了各种区域。
3.2 进程地址空间是怎么工作的?
虚拟地址通过页表映射到实际物理内存上的物理地址。
页表:页表上保存着虚拟地址与实际内存物理地址的关系,还保存着其它的管理权限。比如一个变量的可读可写权限。虚拟地址通过这张表来对应上内存的实际地址。页表也将内存管理和进程管理分割成了两个独立的部分,便于管理。
虚拟地址通过页表转化成实际地址动作是操作系统做的。
3.3 为什么存在进程地址空间。
- 通过虚拟内存实现空间分布更加合理,相同数据放一起。
如果没有进程地址空间,进程都是在物理地址空间上直接开辟,如果一个进程空间地址后面紧跟着一个进程,这个进程要扩容时,可能会导致一个进程的空间不连续。数据在实际内存地址是随便放的。
- 保护物理地址空间
进程地址空间与实际地址空间通过页表进行转化,有一个一一对应关系。如果有一个野指针访问到了别的地址空间。第一:可能页表上根本没有对应关系,直接保错。第二:页表保存的其它管理权限也可能导致,访问不了,直接报错,所以说将进一步保护了物理地址空间。
3.4.解决上面问题
为什么父子进程全局变量的地址相同而内容不同?
因为进程地址空间不是实际的内存,与实际内存存在一种映射关系。每一个进程都会对应一个进程地址空间,父进程创建子进程,子进程拷贝了父进程的进程地址空间,所以两个全局变量g_val的地址相同。当子进程将g_val改变时。操作系统会改变子进程页表中虚拟地址和物理地址的映射关系,重新在实际物理空间开辟一空间,保存改变的值,但是进程地址空间没有变化,变化的是实际物理地址。
注意:有多少进程就有多少进程地址空间和页表。
四.OS怎么管理进程地址空间
每一个进程都会有一个进程地址空间,系统有多个进程,所以系统会有多个进程地址空间,那么进程地址空间是如何被管理的?
先描述后组织
4.1描述
进程地址空间实际也被OS描述成了一个数据结构,mm_struct。这个数据结构的主要作用是将实际内存划分成各种区域。
简单描述一下,表现为:
C/C++中申请空间的本质是:向实际物理内存索要一空间,得到物理地址。再到得到进程地址空间特定区域没有被使用的虚拟地址,再页表上建立映射关系和其它管理权限,再返回虚拟地址。
4.2组织
进程与进程地址空间强相关,每一个进程里都有一个指针指向进程地址空间,OS只要找到进程就找到进程地址空间了。
PS补充:
进程的PCB中包括数据和代码,代码和数据都保存在实际的物理空间。进程的代码都会有一个虚拟地址,进程想执行代码,先拿虚拟地址到页表中找到对应的实际物理地址,找到代码,就可以执行代码了。
以上是关于进程地址空间的主要内容,如果未能解决你的问题,请参考以下文章