【Linux】进程地址空间

在上一篇博客中,我们介绍了环境变量,今天我们讲解进程地址空间。

进程地址空间

学习过 任意一门编程语言的同学可能都见过下面这幅图:各种数据在内存上的分布图。

在这里插入图片描述
我们写一个小程序来验证一下:
在这里插入图片描述
我们发现我们打印出的地址由上到下 是递增的。这也就大致上验证了这一幅图:
在这里插入图片描述

进程地址空间,会在进程的整个生命周期内一直存在,直到进程退出。


但是 这里所说的 进程地址空间就是我们 的 物理内存上的真实分布了吗?

我们还是先用一段程序来推测一下:

我们定义一个 全局变量 g_val 并初始化,它属于当前进程。然后我们生成一个子进程。让父子进程分别打印一些参数。并在第五秒的时候,我们在子进程里把g_val 重新赋值。我们观察g_val的 地址。
在这里插入图片描述
打印结果如下:
在这里插入图片描述
我们发现。前五秒,父子进程中的g_val 地址相同。后五秒,g_val 的地址也是一样的。
这就很有问题了,同一个地址 读出不同的值,这只能说明我们所说的地址根本不是物理地址。

**所以,我们曾经所学到的所有的地址以及地址相关操作 都不是 物理地址,本质是一种虚拟地址.**这种虚拟地址 是由操作系统提供的。

但是,数据和代码 是一定在 物理内存上的(冯诺依曼规定),所以OS必须负责将 虚拟地址 转化成 物理地址 。

虚拟地址

什么是虚拟地址

在这里插入图片描述

  • 进程地址空间
    其本质是进程看待内存的方式,是抽象出来的概念。内核是一个mm_struct 结构体,这样每个进程,都认为自己独占整个内存资源。

  • 区域划分
    将线性地址空间划分为一个一个的aera,[start,end].

  • 虚拟地址
    在[start,end]之间的各个地址叫做虚拟地址

如何联系虚拟地址与物理地址


  • 页表

虚拟地址 和 物理地址 是通过用户级页表来映射的。
在这里插入图片描述
这也就解释了我们之前的疑惑,父子进程同一个地址读出两个数据:

在这里插入图片描述

我们要运行代码,首先会将磁盘中的【代码与数据】加载到【物理内存】任意位置中,同时会在OS中产生父进程和子进程的task_struct.(PCB),以及各自的 【虚拟地址空间】和【页表】。

task_struct 中有指针去指向它的mm_struct(虚拟地址空间),然后虚拟地址通过页表可以找到物理内存中对应的代码与数据。

由此可见,由于每个进程都拥有一块完整的 虚拟地址空间 ,所以所谓的读出两个数据,实际上是两个 虚拟地址空间 的同一个线性位置 存储了 不同的数据。

对于上面那个程序,在前五秒,父子进程 通过页表映射到 物理内存上的 同一段代码 ,同一组数据。五秒过后,子进程修改了数据,那么就会开辟一段新的空间,并把原来指向的数据拷贝过来并修改,同时子进程的页表也会相应的修改。这叫做写时拷贝。(如下图)
在这里插入图片描述


为什么需要虚拟地址空间

  1. 保护物理内存,不受任何进程内的地址直接访问,方便进行合法性检验

比如说,在C语言中我们定义一个指针指向一个常量字符串。如果我们想修改指针,当然会报错。
实际上 物理内存 的任意位置都是可以改变的,只是因为 我们在访问 地址空间上划分好的 常量区的时候,页表会做一个检测,发现这片区域应该只能读不能写,判定为非法访问,从而不会映射到物理内存,终止进程。

  1. 将我们的内存管理 和 进程管理 进行 解耦

  2. 让每个进程,以同样的方式,看待数据与代码

关于这一点,我们需要补充一些知识。
在这里插入图片描述

对于磁盘中的 可执行程序 ,其实是已经被划分为一个个的区域,比如代码区,全局数据区,只读数据区(一个基本划分单元是 4kb,叫做叶帧)。这是为了方便程序的 “链接”步骤。

这样的划分也导致了在磁盘将数据与代码 加载进物理内存时是 按照 区域加载的,各个区域不一定分布在一起。每一个4kb 的区域叫做 叶匡

由于物理内存的不连续性,这给系统找数据增加了难度,但经过页表的映射与整合,在地址空间的虚拟内存上,数据与代码是线性连续分布的。


猜你喜欢

转载自blog.csdn.net/qq_53268869/article/details/122942410