初识进程的地址空间、页表

一、🌟问题引入

🚩代码一:

   #include<stdio.h>
   #include<unistd.h>
   int g_val=100;
   
   int main()
   {
     pid_t id=fork();
     if(id==0)
     {
      //子进程
      while(1)
      {
        printf("I am a child  pid:%d ppid:%d  g_val:%d\n",getpid(),getppid(),g_val);
        sleep(1);
      }
    }
     else
    {
  
      while(1)
      {
        printf("I am a parent  pid:%d ppid:%d  g_val:%d\n",getpid(),getppid(),g_val);
        sleep(1);
      }

     }
    return 0;                                                                                                                                                                                  
  }

可以看出子进程与父进程中g_val的地址值是一样的,这是因为子进程继承了父进程的代码和数据,两者共享数据

🚩代码二:(子进程修改数据)

   #include<stdio.h>
   #include<unistd.h>
   int g_val=100;
   
   int main()
   {
     pid_t id=fork();
     if(id==0)
     {
      //子进程
      int cnt=5;
      while(cnt)
      {
        printf("I am a child  pid:%d ppid:%d  g_val:%d &g_val:%p\n",getpid(),getppid(),g_val,&g_val);
        sleep(1);
        cnt--;
      }
      if(cnt==0)
      {
        g_val=0;
        printf("g_val changed: g_val:%d &g_val:%p\n",g_val,&g_val);
  
      while(1)
      {
        printf("I am a child  pid:%d ppid:%d  g_val:%d &g_val:%p\n",getpid(),getppid(),g_val,&g_val);
        sleep(1);
      }

      }
    }                                                                                                                                                                                            
    else
   {
      while(1)
      {
        printf("I am a parent  pid:%d ppid:%d  g_val:%d &g_val:%p\n",getpid(),getppid(),g_val,&g_val);
        sleep(1);
      }
  
    }
    return 0;
  }

我们发现,父进程与子进程中g_val的值不一样,这可以理解,因为进程之间具有独立性,但是为什么他们地址是一样的呢?

得出以下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  • 但地址值是一样的,说明,该地址绝对不是物理地址
  • 在Linux地址下,这种地址叫做 虚拟地址

二、🌟地址空间

2.1 💬如何理解地址空间

进程 = 进程控制块(task_struct)+ 代码和数据

        操作系统维护进程不止通过进程控制块(task_struct),每个进程其实还存在一个地址空间,它将进程的代码和数据分成不同的区管理起来,地址从低到高有正文区初始化数据区未初始化数据区堆区共享区栈区命令行参数与环境变量区,但真正的数据并不保存在地址空间中,地址空间仅提供连续线性的一串地址,这种地址叫虚拟地址, 通过虚拟地址映射到数据真正存储的物理地址,我们在用C/C++语言所看到的地址,全部都是虚拟地址,物理地址用户一概看不到,由OS统一管理,操作在Linux操作系统中地址空间是用一个叫 mm_struct 的结构体维护起的

        虚拟地址映射到物理地址要通过页表,目前可以把他理解为保存着虚拟地址到物理地址映射关系的表

        

2.2 💬问题解释(写实拷贝)

        父进程创建子进程的时候,子进程会将父进程许多的内核数据结构都拷贝一份,其中就包括了页表,父进程在页表中保存了g_val的映射关系,所以子进程也可以访问到g_val,这就是父子进程中g_val的值与地址相同的原因。

        当子进程要修改g_val时,由于父子进程对g_val映射到相同的物理地址,父进程中的g_val也会随之修改,但是为了满足进程独立性的特点,操作系统会在物理内存中重新开辟一段新的空间,

并将g_val的值拷贝过来,这时子进程的页表就会重新映射物理地址,这时子进程修改g_val,父进程就不会收到影响了,这个操作全程由操作系统自主完成,称之为写实拷贝

ps:如果一个全局变量父子进程都不进行修改,两者是进行共享的,并不会直接让子进程拷贝一份,这时因为许多变量父子进程一般不会进行修改,但这些数据却很大,比如说环境变量等,直接拷贝一份会很浪费空间,所以当其中一方要修改时,才会进行拷贝,通过调整拷贝的时间顺序,达到节省空间的效果

2.3 💬地址空间的意义

1. 让无序变成有序,让进程以同意的视角看待物理内存,以及自己运行的各个区域

【解释】:

        一个进程的代码和数据在物理内存中的存储不一定是连续的,可能会根据数据类型的不同分别存储在物理内存的各个地方,此时进程要管理这些数据,就需要在task_struct中讲这些散乱的数据分别管理起来,当进程很多的时候,就会造成数据混乱,不利于操作系统的内存管理。

        而每个进程拥有自己独立的地址空间后,就不会被物理内存中的杂乱的数据影响了,管理数据只需要将虚拟地址通过页表映射到物理内存中即可

2. 进程模块与内存管理模块发生解耦

【解释】:

        当我们在写C语言程序时,比方说要使用堆空间,可以利用malloc函数开辟好,但是可能代码跑了很长时间后才会使用这段空间,那这段时间别的进程就无法利用这段空间了,会造成空间的浪费,有了虚拟地址和页表的概念,当进程需要开辟空间时,操作系统只需要将虚拟地址填入页表,但并不构建映射关系,当进程需要使用这段空间时,操作系统才会在物理内存开辟好空间,再构建映射关系,这样就可以大大提高空间的利用率。通过虚拟地址和页表构建映射关系等操作为进程的管理,在物理内存开辟空间等操作为内存管理,地址空间可以使两模块发生解耦,提高系统的资源利用

3. 拦截非法请求,保护操作系统

  【解释】:   

        当进程想访问一个地址的内容时,操作系统会先检查页表中是否存在该虚拟地址,如果不存在,就拦截这个请求,并报错提醒,防止其向物理内存修改数据,这就是为什么我们写程序有非法访问时程序会报错,而不是操作系统直接崩溃的原因

2.4 深入理解页表与写实拷贝

        其实页表并没有上述讲的这么简单,其内部还有许多寄存器和字段信息等,例如数据是否存在内存中、rwx权限等,我们可以举两个例子理解一下,本文只是初识地址空间,关于页表的更多信息后续会讲。

(1) 理解常量区修改

        我们在写程序时,对常量区的内容进行修改时程序会发生报错,那为什么修改别的区的内容是程序就不报错呢?这是由于页表中存在着对数据rwx权限判断的字段,操作系统会讲常量的数据权限修改为只读,当我们要修改常量区内容时,操作系统拿着虚拟地址在页表中找映射关系进行修改,发现该数据没有写权限,就会拦截这个请求,并报错,保护物理内存。

(2)系统是如何检测写实拷贝的

        当父进程创建子进程时,会将父子进程对数据的rwx权限仅保留读权限,当父子任意一方想要修改数据时,就会检测到错误,这时操作系统就会检测是否要发生写实拷贝

  (3) 如何理解fork函数返回两个返回值

        当fork( )函数运行完代码,会return一个返回值,pid_t id = fork() ,return的本质就是修改id

的值,此时就会发生写实拷贝,父子进程就会各自返回一个值

      

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

张呱呱_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值