linux就该这么学【进程地址空间】

在这里插入图片描述

进程地址空间

我们都知道fork创建进程后,进程的执行是随机的

	1 #include <stdio.h>                                                                                                     
    2 #include <unistd.h>
    3 #include <stdlib.h>
    4 
    5 int g_val = 100;  //定义全局变量
    6 
W>  7 int main(int argc, char *argv[], char* env[] )//命令行参数个数、命令行参数、环境变量 
    8 {
    9    pid_t id = fork();  //使用fork创建子进程
   10    if(id == 0)   //fork调用玩之后,给子进程返回0
   11    {
   12       g_val = 1000;
   13       while(1){
   14           printf("child[%d] : g_val:%d &g_val:%p、child\n", getgid(), g_val, &g_val);  
   15           sleep(1);
   16 
   17       }
   18    }
   19    else if(id > 0) //fork调用完之后,会给父进程返回子进程的id
   20    {
   21       
   22      while(1){
   23         printf("parent[%d] : g_val:%d &g_val:%p、parent\n", getgid(), g_val, &g_val);  
   24         sleep(1);
   25 
   26      }
   27    }
 		else
   30    {
   31       perror("fork");
   32       return 0;
   33    }
   
       return 0;
      }           

程序运行结果:我们可以看出父进程的g_val的值和子进程g_val的值是不一样的,但是他们的地址值确实相同的
在这里插入图片描述
问题探索:
内存中同一个地址的值,会存在不同的进程读取之后表现出不同的值吗?
两个进程读取g_val的值他们不同可以地址确实相同的,从推理上来讲进程访问的g_val地址肯定不是真正的物理地址,那么问题来了他是怎么做到了呢?实际上我们进程访问的址究竟是那一块的呢?他又起什么作用?

针对这个问题的探讨我们需要引入进程地址空间概念,在我们学习语言的时候相信这张图我们应该并不陌生。
在这里插入图片描述
其实进程能看到的所有的内存布局的情况,是被地址空间解释的

在这里插入图片描述
进程地址空间,是对物理内存的一种虚拟化表示,虚拟空间最终一定要以某种方式转化到物理内存,就比如上图的将进程地址空间的正文代码通过映射的方式转化到对应的物理空间的进程代码 所以进程地址其实是一个虚拟地址,他并不是一个真正的物理地址,而父子进程访问的数据绝对是被保存到了不同的物理内存中去。

其实父进程创建子进程后,会将父进程的PCB和地址空间拷贝一份到子进程中去,所以在子进程中可以看到g_val的地址并且能够访问它,如下面这幅图中的关系
在这里插入图片描述

  • 假设一开始父进程创建完子进程,由于子进程的虚拟地址是父进程虚拟地址的一份拷贝(所以他们访问的g_val是一样的),我们都知道进程运行时是具有独立性的,在数据层面先独立!,其实操作系统本着不浪费资源的情况下,只要父子进程二者并未对数据进行修改那么他们之间将会共享一块资源(也就是用一个虚拟地址映射出同一块物理内存一起共用),而如果二者之间任何其中的一方对数据进行了修改,那么操作系统为了完成进程里面数据的独立就会将修改数据方进程的虚拟地址映射到其他的物理地址位置,注意虚拟地址是不会发生变化的,变化的是使用虚拟地址映射出来的物理地址会不一样,如下图
    在这里插入图片描述

所以我们也就不难理解为什么子进程和父进程明明是访问同一个地址的变量,但是他们打印的值确实截然不同的这个原因了!

总结:
当父进程创建子进程后,子进程会拥有父进程的PCB和进程地址空间的一份拷贝,二者是直接共享一个物理内存的(前提是父子进程并未对g_val变量进行修改),而如果子进程对g_val的值进行了修改,那么子进程的物理地址也会发生修改(读者可以理解为使用虚拟地址映射的方式更新了对应的物理地址),而虚拟地址并不是变量g_val的真正地址,他只是一个虚拟地址,作用是映射出g_val的物理地址以方便找到g_val的真正地址对他修改

进一步理解地址空间

  • 什么是地址空间?

    • 我们都知道进程是由(代码 + 数据)和进程的PCB 组成的,如果没有地址空间存在,那么我们的进程,访问的地址都是物理地址,如果当我们的指针发生了越界或者野指针的问题,可能会写坏别人的空间或者数据,而为了保护物理内存发生上述的情况,就引入了虚拟地址,那么即使出现了野指针的问题也不能直接损害物理内存。
  • 为什么要有地址空间?

    • 如果没有进程地址空间则可能会存在这种问题,一个进程的数据存放的内存位置,是不连续的,如果空间不连续则可能在访问数据的时候不够方便,增加了异常越界的概率。站在进程的角度当他去访问几个变量的时候,访问的虚拟地址可以看作是一样的,但是映射出来的物理地址绝对是不一样的,其实实际上在访问变量的时候,将变量的虚拟地址转换为物理地址这个工作是操作系统做的(会生成一张映射表,而这张页表会记录虚拟地址和物理地址对应的关系),所以操作系统可以通过这张页表中对应的关系完成变量的值初始化。

    • 那么即使是发生了越界,或者野指针的问题,则对应的虚拟地址绝对不会出现在页表之中,所以当操作系统想要通过这个页表中不存在的虚拟地址去映射出物理地址自然也是不可能的!

    • 总结:
      1、地址空间的作用其实是为了保护内存。
      2、地址空间的作用是为了将空间连续初始化。

    在这里插入图片描述

  • 地址空间是如何工作的?

    1、每一个进程都有一个进程的地址空间,系统可能会存在多个进程,那么多个进程就会对应地出现多个进程地址空间,那么进程地址空间是如何被管理起来的呢?答案是先描述再组织!!!

  • 先描述:地址空间本质上就是一个数据结构struct,在linux上这个进程地址空间是struct mm_struct结构
  • 再组织:

    地址空间上所呈现暴露给上层的地址都叫做虚拟地址,而实际上当进程访问虚拟地址空间的时候都是要通过页表进行映射转化物理内存然后拿到对应的代码和数据

我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  • 但地址值是一样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做 虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理

进程总结:

  • 进程是什么?
    • 进程是被加载到内存的程序,由进程常见的数据结构struct task_struct(控制块)和struct mm_struct(地址空间)再加上代码和数据构成的。
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱生活,爱代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值