进程地址空间

首先我们先了解不同数据存储的位置 

堆区:动态分配的内存,例如使用malloc或者new出来的对象

栈区:存储局部变量

静态区:存储全局变量或者静态变量

只读常量区(数据段):存储只读数据例如字符串常量

代码段:存储执行代码

可以通过以下代码验证

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 int val_1;                                                                                                                                            
  4 int val_2 = 10;
  5 int main()     
  6 {         
  7     printf("code=%p\n", main);
  8     printf("uninited=%p\n",&val_1);
  9     printf("inited=%p\n",&val_2);  
 10     static int sta_val = 10;     
 11     printf("static=%p\n", &sta_val);
 12     const char* str_1 = "abc";      
 13     printf("const string=%p\n",str_1);
 14     int a;                            
 15     int b = 10;
 16     int c = 20;
 17     int* d = (int*)malloc(sizeof(int));
 18     int* e = (int*)malloc(sizeof(int));
 19     printf("stack=%p\n", &a);          
 20     printf("stack=%p\n", &b);
 21     printf("stack=%p\n", &c);
 22     printf("heap=%p\n", d);  
 23     printf("heap=%p\n", e);
 24     return 0;              
 25 }            

 

进程的地址是虚拟地址

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 int val = 10;
  6 int main()
  7 {
  8     pid_t id = fork();
  9     if(id==0)
 10     {
 11         //子进程
 12         int cnt = 5;
 13         while(cnt--)
 14         {
 15             if(cnt == 2)
 16             {
 17                 printf("val changed");
 18                 val = 20;                                                                                                                             
 19             }
 20             printf("child:%p\n",&val);
 21         }
 22     }
 23    else{
 24        //父进程
 25        int cnt = 5;
 26        while(cnt--)
 27        {
 28            printf("parent:%p\n",&val);
 29        }
 30    }
 31     return 0;
 32 }

可以看到,父子进程都使用了一个全局变量,但是当子进程修改变量值的时候,父子进程的变量地址都不变,可是一个地址不可能存放两个不同的变量,那么原因只有一个,进程的地址空间是虚拟出来的。

什么是进程(虚拟)地址空间

上面的画的简单的数据存储结构就是实际进程的地址空间结构,在Linux操作系统中定义了一个mm_struct结构体用来存储各个数据段的地址,例如:代码段地址可以用两个成员表示,code_start记录代码段的首地址,code_end记录代码段的末尾地址。这样就可以为存储的每个数据段都划分出一个空间。

进程的地址是虚拟的和物理地址有一种映射关系,这种关系被放到了页表这个结构中

 为什么要有进程地址空间

1.让系统以统一的视角看待内存,例如,如果一个进程处于挂起状态,那么它的数据会先放到硬盘中。当再次加载到内存时,为它分配的物理地址肯定会发生变化,但是虚拟地址不会变。有了页表这个媒介,无需在意物理地址的变化。

2.保护物理内存,当我们要对数据修改时,会先在页表中查找,如果页表对应的r-w是只读,就会拦截防止对物理地址的异常操作。

以创建子进程为例,讲一下这个过程:

父子进程的数据和代码在一开始是共享的。子进程在创建时,页表也会创建,子进程页表的虚拟地址映射关系和父进程的一样。当我们对其中一个的数据进行修改时,数据对应的页表的r-w是r状态,系统检测到写时拷贝,会为该数据再建立一个映射关系,使得父子进程不相同的数据都能分别存储。

3.页表将进程管理块和内存管理块解耦合(每次程序运行,页表地址都会被加载到cr3寄存器中)。再次回顾一下进程的概念,程序对应的内核数据结构(task_struct,mm_struct,页表...)和代码数据;内存管理模块是由物理地址空间和页表等部分构成。为什么说解耦合呢? 我们都玩过50+G的游戏,目前一般电脑的内存都是16G/32G,内存怎么装的下的?

进程数据的加载是一种惰性加载,即不会一次性把整个程序的数据都加载到内存,只会先加载一部分,当进程需要数据时,通过页表查找,页表中还有一个记录数据是否被加载的标识,如果未加载就会触发系统的一个机制——缺页中断,将数据加载到内存。这样就做到的进程不需要过多关心内存的问题,内存也不需要关心进程。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值