• linux地址空间


    内存空间示意图

    进程是在内存中运行的,为了便于管理,不同的数据会存储在不同的区域,因此内存就被分为几部分,如下图所示:

    在这里插入图片描述
    而这些区域都存什么东西呢?我们有下面一段程序:

    #include
      2 #include<stdlib.h>
      3 
      4 int un_g_val;
      5 int g_val = 2;
      6 
      7 
      8 int main(int argc, char* argv[], char* env[])
      9 {
     10   printf("adress: %p\n",main);
     11   printf("init: %p\n",&g_val);
     12   printf("uninit: %p\n",&un_g_val);
     13   char *p1 = (char*)malloc(10);
     14   char *p2 = (char*)malloc(10);
     15   char *p3 = (char*)malloc(10); 
     16 
     17   printf("heap_p1: %p\n",p1); // 堆地址
     18   printf("heap_p2: %p\n",p2);
     19   printf("heap_p3: %p\n",p3);
     20   printf("stack_p1: %p\n",&p1); //栈地址
     21   printf("stack_p2: %p\n",&p2);
     22   printf("stack_p3: %p\n",&p3);
     23 
     24   int i = 0;
     25   for(i=0;i<argc;i++)
     26   {
     27     printf("argv[%d]:%p\n",i,argv[i]);
     28   }
     29                                                                                                                                                                         
     30   for(i=0;env[i];i++)
     31   {               
     32     printf("env[%d]:%p\n",i,env[i]);
     33   }          
     34   return 0;   
       }
                                                                                                                      
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这个程序打印了各种变量的地址,印证了地址空间是确实存在的!
    在这里插入图片描述


    我们再看这个代码能编过吗?

     11   //常量                                                                                                                                                                
     12   "hello world";               
     13   50;                          
     14   'xxxx;       
    
    • 1
    • 2
    • 3
    • 4

    可以编译过,并且该东西是字符常量它会硬编码进代码(因为代码也不可更改),所以字符常量会在代码区。

    虚拟地址空间

    看下面一段代码:该程序创建了一个子线程,共享父进程的全局变量,子进程会更改全局变量。

      1 #include<stdio.h>
      2 #include<stdlib.h>
      3 #include<unistd.h>
      4 int g_val = 2;
      5 
      6  
      7 int main()
      8 {
      9   pid_t id = fork();
     10 
     11   if(id == 0)
     12   {
     13     //child
     14     int cnt = 0;
     15     while(1)
     16     {
     17       printf("I am a child, pid_id:%d,  ppid_id:%d , g_val:%d, &g_val:%p\n",getpid(),getppid(), g_val,&g_val);
     18 
     19       sleep(1);
     20       cnt++;
     21       if(cnt==5)
     22       {
     23         g_val = 200;
     24         printf("child chamge g_val 2 -> 200 success\n");
     25       }
     26     }
     27   }
     28   else
     29   {                                                                                                                                                                     
     30     //father
     31     while(1)
     32     {
     33       sleep(1);
     34       printf("I am a father, pid_id:%d,  ppid_id:%d , g_val:%d, &g_val:%p\n",getpid(),getppid(), g_val,&g_val);
     35     }
     36   }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    I am a child, pid_id:20414,  ppid_id:20413 , g_val:2, &g_val:0x60105c
    I am a father, pid_id:20413,  ppid_id:18692 , g_val:2, &g_val:0x60105c
    child chamge g_val 2 -> 200 success
    I am a child, pid_id:20414,  ppid_id:20413 , g_val:200, &g_val:0x60105c
    I am a father, pid_id:20413,  ppid_id:18692 , g_val:2, &g_val:0x60105c
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们从运行的结果可以看出来,子进程修改全局变量后,子进程的值改变,父进程的值没变,但是全局变量的地址空间一样!!!

    为什么会产生这种情况呢??
    我们大胆猜测,程序打印的地址,一定不是物理地址,因为同一地址不可能存储两个不一样的值 。因为打印出来的是虚拟地址!!!

    虚拟地址

    每一个程序都有自己的虚拟地址空间,就像PCB一样,每一个程序都有一个属于自己的虚拟地址空间,记录着各种数据的位置,供操作系统使用。
    如下图所示:代码在准备执行时,会编译成可执行程序,此时程序里面,即代码与代码是有地址的(这个地址就是虚拟地址),程序被加载到内存后:

    1. 首先系统会创建PCB
    2. PCB会有一个指针,指向程序虚拟内存
    3. 根据虚拟内存的地址所对应的物理内存地址,填充页表,完成映射。

    在这里插入图片描述
    页表 :页表的作用就是完成虚拟地址到物理地址的转换

    下图是解释上面代码例子的虚拟空间问题:子进程创建后,获得父进程一模一样的地址空间,当触发写时拷贝时,才复制资源。这样会节省空间。
    在这里插入图片描述

    进程地址空间生命周期图解

    1. 程序被读进内存的过程
      在这里插入图片描述

    2. 程序执行的流程
      在这里插入图片描述

    为什么要有地址空间呢?

    1.为了保护物理内存,页表将各个进程隔离起来了。因为地址空间和页表是OS创建并且维护的,意味着想要使用地址空间和页表映射,必须在OS的监管之下进行访问。因此保证了进程之间的独立性,互不影响。

    1. 因为有地址空间和页表的存在,OS可以实现,对未来的数据进行任意位置的加载,只需根据页表即可找找到杂乱无章的数据。这样物理内存的分配和进程的管理就可以分开进行管理。内存管理模块和进程管理模块就完成了解耦合。

    2. 如果我申请了物理空间,但是我不立马使用,我会造成空间的浪费!但是如果我在虚拟空间上申请,当我没使用时,也没有为我开物理空间 。当我使用时,OS才帮我开辟空间,这样可以保证内存的使用率是100%。

    3. 物理内存上的数据理论上可以在任意位置加载,这会导致物理内存上的所有数据都是乱序的。但是有VA+页表的存在,我们可以方便的直接找到各种数据,在进程的视角,数据是那么的竟然有序!因此,可以将内存分布有序化!

    小结

    OS的系统是深而庞杂的,此文章只是浅显的谈论了以下内存空间的概念及其分布。更详细的内容,还请读者自行查阅。

  • 相关阅读:
    WPF实现一个表格数据从cs获取动态渲染
    Git版本控制管理——Git和GitHub
    常用的Selenium WebdriverAPI
    力扣:166. 分数到小数(Python3)
    docker安装jenkins以及Permission denied错误的解决方法!
    Java学习 --- 面向对象之继承
    Android前台服务和通知
    灵魂一问:一个Java文件的执行全部过程你确定都清楚吗?
    java集合框架综述
    计算机毕业设计 基于SpringBoot的医院档案管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • 原文地址:https://blog.csdn.net/weixin_45153969/article/details/134254450