前言
在操作系统中,内存分为以下几个区域,从下往上按照从小到大排列
一、程序地址的分布
代码
#include <stdio.h>
#include <stdlib.h>
int noval;
int val = 1;
int main(int argc,char*argv[],char*env[]){
printf("code addr %p\n",main);
printf("init data addr %p\n",&val);
printf("uninit data addr %p\n",&noval);
char*heap =(char*)malloc(20);
char*heap1 =(char*)malloc(20);
char*heap2 =(char*)malloc(20);
char*heap3 =(char*)malloc(20);
printf("heap addr %p\n",heap);
printf("heap1 addr %p\n",heap1);
printf("heap2 addr %p\n",heap2);
printf("heap3 addr %p\n",heap3);
printf("stack addr %p\n",&heap);
printf("stack1 addr %p\n",&heap1);
printf("stack2 addr %p\n",&heap2);
printf("stack3 addr %p\n",&heap3);
for(int i=0;i<3;i++){
printf("&argv[%d]:%p\n",i,argv+i);
}
for(int i=0;i<3;i++){
printf("&env[%d]:%p\n",i,env+i);
}
return 0;
}
现象
[yw@hcss-ecs-e53a test3]$ ./mybin -1 -2 -3
code addr 0x40057d
init data addr 0x60103c
uninit data addr 0x601044
heap addr 0xf80010
heap1 addr 0xf80030
heap2 addr 0xf80050
heap3 addr 0xf80070
stack addr 0x7ffe667d4790
stack1 addr 0x7ffe667d4788
stack2 addr 0x7ffe667d4780
stack3 addr 0x7ffe667d4778
&argv[0]:0x7ffe667d4888
&argv[1]:0x7ffe667d4890
&argv[2]:0x7ffe667d4898
&env[0]:0x7ffe667d48b0
&env[1]:0x7ffe667d48b8
&env[2]:0x7ffe667d48c0
通过以上结果可以得到以下结论:
1.地址从代码区到命令行参数环境变量区域依次增大
2.堆区和栈区之间留有巨大的内存空间
3.堆区的地址是逐步增大的而栈区的地址是逐步减小的,简称堆栈相向而生
二、物理地址与虚拟地址
物理地址是内存中真实存在的地址空间,而虚拟地址是划分给进程,进程内再进行划分的内存空间,其每一块虚拟地址都对应着一个物理地址,但是这种对应关系在用户层面是看不到的,所以需要反向验证虚拟地址的存在。
代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int val = 1;
int main(){
pid_t id = fork();
if(id){
int cnt=0;
while(1){
cnt++;
printf("Parent,pid=%d,ppid=%d,val=%d,&val=%p\n",getpid(),getppid(),val,&val);
sleep(2);
if(cnt==5){
val=100;
printf("val:1->100\n");
}
}
}
else{
while(1){
printf("Child,pid=%d,ppid=%d,val=%d,&val=%p\n",getpid(),getppid(),val,&val);
sleep(2);
}
}
return 0;
}
现象
在父进程与子进程之间,如果数据不被修改的情况下,父子进程是共用一份数据的,但是如果有一方对数据进行修改,则会发生写时拷贝,即会将数据拷贝一份到自己的进程内存空间中,两份数据互不影响,在上面的结果可以看到,在数据val被修改前,父子进程的val的地址一致,在val被修改后,二者的值不一样,但是其地址却还是一致。同一块地址不可能会有两个不同的值,说明父子进程的地址是不一样的,但是地址值相同,说明这个地址绝对不可能是物理地址,所哟进程中存在虚拟地址。
在最上面的那张地址分布叫做进程地址空间
三、进程地址空间
1)什么是进程地址空间
在进程运行时都会有对应的PCB,在PCB里面会存在一张表,每个进程都有对应的地址空间,在地址空间中,存在一张哈希表,该哈希表叫做页表,存放虚拟地址和物理地址之间的映射关系。子进程会继承父进程的地址空间,所以一开始父子进程的数据共享,在数据修改后,在物理地址中会对父进程的地址空间复制一份,并且修改子进程中的映射关系,所以父子进程在打印val的值时,两者通过各自的地址空间的映射关系,获取到的值是不同的。
每个进程都存在一个地址空间,在32位环境下取值范围是0-4GB;进程地址空间的本质就是数据结构,将多个进程地址空间连接起来。
2)为什么要有地址空间
地址空间存在的意义是1)划分内存区域,防止进程之间内存发生冲突,并且对进程之间的大小进行动态调整,从而使物理内存由无序变为有序,可以让进程以统一的视角看待进程。2)通过映射,可以将进程管理和内存管理进行解耦,方便操作系统的设计。3)保障物理内存的安全,防止进程产生非法访问。在CPU中,存在一个寄存器,叫做CR3,用于存放页表的第一个物理地址,在CPU上面还存在一个硬件,叫做Memory Manager Unit(MMU,内存管理单元),专门用于通过页表解析物理地址。
3)进程的本质
进程=内核数据结构+可执行程序
4)new/malloc问题
在new/malloc申请空间之后,操作系统只会在进程虚拟内存空间中分配一块内存空间,此时并没有真正在物理内存层面开辟物理空间并创建虚映射关系,只有当用户要对内存空间进行访问的时候才会开辟空间并且建立映射关系,这样的好处是可以加快new/mallc的速度,并且充分保证内存的使用效率。