进程(4)——进程地址空间【linux】


一.什么是进程地址空间

地址空间按照我们的现有的理解来说。

可以说成这样
在这里插入图片描述

这里可以算是我们的现有的对地址空间的认识
内存分区,不同的变量存储在进不同的区中

但是今天要告诉大家这个进程地址空间不是真正的地址空间

接下来就开始进行验证

二.进程地址空间不是真实地址?

子进程和父进程的值进行全局变量修改后
值不一样,但是全局变量地址却是一样的

这说明了打印出来的变量的地址不是真正的物理地址

#include<stdio.h>
#include<unistd.h>
int i=0;
//定义全局变量 i
int main(){
   
  size_t i=fork();
  if(i>0)
  {
   
     //分别让子进程和父进程打印地址i的值和地址
    //并且全都让i重新赋值
    i=10;
    while(1)
    {
   
      sleep(1);
     printf("i am father,val=%d ptr=%p\n",i,&i);
    }
  }
  else
  {
   
    i=100;
    while(1)
    {
   
    sleep(1);
    printf("i am child,val=%d ptr=%p\n",i,&i);
    }
  }


}

接下来就用这个代码践行测试一下

在这里插入图片描述

这里能惊奇的发现
父进程和子进程中的变量i值全都不同,但是它们的地址全都一样

这个结果就证明了,地址空间指向的肯定不是真实物理内存。

如果是地址指向真实的物理内存,那这样的情况肯定是不存在的,内存中的一个空间内,不可能存在一个变量有两个值的情况。

那真实的情况是什么样的?

记下来就带来物理地址和地址空间之间的大致关系

三.物理地址与进程地址空间的关系(整体部分)

这里我们就用一幅图来代表这其中的大致关系
(这里只是大致表示它的逻辑关系,其中细节没有补足)

在这里插入图片描述

这里是地址空间都是虚拟地址
真实地址是真实的存储区域
页表则是记录了虚拟地址对应的真实物理地址
充当了地址空间和真实内存中间的媒介。

现在我们就可以来思考一下

在这里插入图片描述

上面这个代码为什么运行结果是这样

因为地址一样并不指的是真实物理内存的地址
而是地址空间的虚拟地址

上面这个问题就是写实拷贝。

在这里插入图片描述

在这里插入图片描述

大致过程就是这样了
实际物理内存这两个是不同的
但是虚拟地址是一样的。

大致过程是这样了,但是接下来我们就来补充一些细节

四. 细节

4.1 进程地址空间的本质:

这里有个细节,我们以前都叫做内存地址空间
但是现在改成了进程地址空间
这说明了现在这个地址空间是属于进程的

什么叫属于进程?
在我们之前讲解进程的时候
提到了进程是由
进程=task_struct+代码和数据 组成的

这里提到:进程地址空间同样属于进程

所以我们这里需要更正一下我们对进程的理解了
进程=内核数据结构(task_struct && mm_struct && 页表结构)+代码和数据

这个mm_struct就是进程地址空间
在这里插入图片描述

那这里也就能确定:
进程地址空间是用来进行内存可视化的数据结构,同样也能被系统进行管理,同样符合和进程PCB一样的先描述再组织

所以每个进程pcb创建时,同样也要创建属于他们的虚拟内存——mm_struct

4.2 为什么要有进程地址空间?

我们这里讲完了进程地址空间是啥玩意后
接下来就要来讲讲为什么要有它

i. 具体应用

这里我们来举一个小例子
我们在前面讲过:进程有独立性
每个进程不知道别的进程的存在,它们只管自己的进程申请和使用

每个进程有了进程地址空间后
就能记录下每个进程的空间范围,用来方便申请内存
同时不妨碍
在这里插入图片描述
只要每个类中有一个起始和结束的地址
这样就不会互相冲突,更好维护进程的独立性

这里只是个具体的实际应用

接下来就讲宏观上的整体作用

ii. 宏观好处

1.
进程地址空间相当于进程和物理地址间加了个媒介
可以让进程在进行寻址请求或者申请时进行审查,所以一旦访问异常,直接拦截
使关于进程的内存操作简单方便了很多。

2.
因为有地址空间和页表的存在,将进程管理模板和内存管理模板进行解耦合

这里通俗的讲就是:

当我们用户进行进程的创建和使用的时候。
不再需要关心内存的申请和创建,操作系统自己就会去调用
让使用计算机的门槛和难度降低了很多

4.3 页表

这里提个小细节
还记得之前提过在切换进程的时候,cpu中存储的是进程的上下文

页表就存在寄存器中,算是进程的上下文的一部分

i. 权限位

mm_struct中进行了分区。
分为了常量区和代码区。
其中的代码都是不能进行更改的
这里我们能猜到其实这个:
不能进行更改肯定不是真实的物理内存进行的权限划分
而是进程地址空间实施的。

因为如果真实物理地址进行权限划分的话
那在常量区数据最开始怎么进行写入?
所以权限的划分是体现在进程地址空间中的

那代码是如何知道什么权限有没有被划分的?

这里就要牵扯到页表了
在这里插入图片描述

页表中有一个专门的权限位
用来表示地址指向的空间是否具有对应的权限

ii. 标志位

还记得我们讲进程的时候提到过进程挂起的这个概念

当进程长时间未使用或者资源没有准备时
系统会将该进程的代码和数据从内存中去掉,让其他进程使用内存资源

那如何知道进程有没有被挂起?(本质是问系统如何知道代码和数据是否在内存中读取)
这里我们就要提一下
系统为了防止空间和时间的浪费,所以页表中使用的是惰性加载
就是说会将进程的代码和数据先读取在进程内存地址空间,而不去使用实际内存的空间
就是说会在地址空间处先占个位置,在实际内存中不会先去开辟空间

所以为了让系统知道什么地址被成功加载到了物理内存中
页表又加了一个标志位:
在这里插入图片描述

用来专门标识数据和代码是否被加载到了内存空间当中
这样就可以做到进程的挂起和惰性加载了。

如果当操作系统发现进程的代码和数据没有被加载到实际内存中时,会触发缺页中断,将进程暂停,重新将进程的代码和数据从磁盘加载到内存中,然后再继续执行。
前面我们提到的写实拷贝同样也是缺页中断的功能实现。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2023-12-06 04:32:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-06 04:32:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-06 04:32:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-06 04:32:01       20 阅读

热门阅读

  1. 设计模式_策略模式 更改激光雷达类型

    2023-12-06 04:32:01       33 阅读
  2. spring boot spring-retry重试机制

    2023-12-06 04:32:01       34 阅读
  3. php许愿墙代码包括前端和后端部分

    2023-12-06 04:32:01       34 阅读
  4. 生命周期标注

    2023-12-06 04:32:01       40 阅读
  5. pywin32后台键鼠

    2023-12-06 04:32:01       29 阅读
  6. 机器人算法——costmap膨胀层InflationLayer

    2023-12-06 04:32:01       39 阅读
  7. AIGC: 关于ChatGPT中对输出文本进行审核

    2023-12-06 04:32:01       37 阅读
  8. webpack 打包前端项目

    2023-12-06 04:32:01       29 阅读
  9. C++多线程之通过成员函数作为线程入口

    2023-12-06 04:32:01       33 阅读