目录
一、回顾我们以前学习的地址空间
这个内存布局真是的我们实实在在的内存嘛? 答案是不是的
下面我们来验证
cpp
1 #include<stdio.h>
2 #include<assert.h>
3 #include<unistd.h>
4
5 int myval=100;
6
7 int main()
8 {
9 pid_t id=fork();
10 assert(id>=0);
11 if(id==0)
12 {
13 //子进程
14 myval=200; //修改myval的值
15 while(1)
16 {
17 printf("我是子进程,我的pid是:%d,我的父进程是:%d,myval: %d, &myval: %p\n",getpid(),getppid(),myval,&myval);
18 sleep(1);
19 }
20 }else if(id>0)
21 {
22 //父进程
23 while(1)
24 {
25 printf("我是父进程,我的pid是:%d,我的父进程是:%d,myval: %d, &myval: %p\n",getpid(),getppid(),myval,&myval);
26 sleep(1);
27 }
28 }
29
30 return 0;
31 }
可以看到,父进程和子进程中的g_val的地址是一摸一样的,那么按理说将子进程中的g_val改变后,由于他们使用的是一块空间,所以父进程中的g_val的值也应该改变,可这里为什么没有变化??
如果C/C++打印出来的地址是物理内存的地址,这种现象绝不可能存在!而这里使用的地址是虚拟地址。
在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
所以最上面那张图应该叫做,进程虚拟地址空间。
二、进程地址空间
每个进程都有一个地址空间 ,都认为自己在独占物理内存。而这个地址空间在内核中是一个结构体 struct mm_struct.
mm_struct 中的分布类似下面这种:
c
struct mm_struct {
unsigned int code_start; //地址空间上进行区域划分时,对应的线性位置,称为虚拟地址
unsigned int code_end;
unsigned int init_data_start;
unsigned int init_data_end;
unsigned int uninit_data_start;
unsigned int uninit_data_end ;
unsigned int heap_start;
unsigned int heap_end;
unsigned int stack_start;
unsigned int stack end;
}
虽然这里只有start和end,但每个进程都可以认为mm_struct代表整个内存的所有的地址为0x0000...000~0xFFFF...FFF(即每个进程都认为自己拥有4GB的空间,至于到底有没有,是OS要做的事)
真实内存的样子
页表:是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。
三、进程地址空间的作用
1.防止访问权限越界
通过添加一层软件层,完成有效的对进程操作内存进行权限管理,本质目的是为了保护物理内存以及各个进程的数据安全。
2.将内存申请和内存使用的概念划分清楚
通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存和OS申请内存管理操作,进行软件上面的分离。
进程A想申请1000字节空间,进程A马上就能使用这1000字节吗?这是不一定的,可能会存在暂时不会全部使用的情况。
在OS角度,如果空间马上给进程A,就意味着整个系统会有一部分空间本来可以给其他进程立即使用,先在却被进程A闲置着。
这样就会存在空间浪费的情况。操作系统不允许出现浪费和不高效的行为
所以在这种情况下,OS会在进程A使用空间的时候才将内存申请给进程A 。(相当于是类似写时拷贝的思想)
3.站在CPU和应用层的角度,进程同意可以看作统一使用4GB空间,而且每个空间区域的相对位置是比较确定的。
如果同时存在多个进程,而每个进程代码的其实位置是不确定的,那么CPU在执行时,需要找到代码在哪里,比较混乱。
而使用虚拟地址空间和页表的方式,将内存划分为代码段、常量区、堆、栈等区域,CPU执行进程时,每次从同一个位置开始即可,而不同的进程通过不同的页表映射到自己的物理内存中存放代码和数据的位置,提高了CPU的执行效率。
所以通过虚拟地址和页表,程序的代码和数据可以被加载到物理内存的任意位置!!极大的减少内存管理的负担。
OS最终这样的目的,为了达到一个目标:每个进程都认为自己是独占系统资源的。
四、解决一个地址出现两个值的问题
在开始那段代码中,我们可以看到myval的值在被子进程修改后,父进程值没有改变,同时打印出来的myval的地址相同,出现了一个地址两个值的情况,我们来解决。
子进程在创建时会以父进程为模板,即能够拷贝父进程的地方就拷贝,例如虚拟地址,只读区的映射关系(代码共享)。
所以子进程和父进程的虚拟地址是相同的,而页表的映射关系是不同的,所以他们的物理地址也不同。
所以就出现了,子进程改变myval的值,而父进程不变,但打印出的地址却是一样的情况了。