☰
【Linux系统】虚拟地址空间
2026/10/3 2:07:42 网站建设 项目流程

文章目录

  • 一、引入问题
  • 二、虚拟地址空间与页表
    • 2.1、初谈虚拟地址空间与页表
    • 2.2、再谈页表
  • 三、虚拟地址的管理
    • 3.1、描述虚拟地址空间
    • 3.2、理解区域划分
    • 3.3、如何管理各区域??
  • 四、虚拟地址空间的意义

一、引入问题


学习C/C++的过程中,我们一定见识过这样一种空间布局图。
可是,我们对它的理解并不深刻。难免会有疑问:

问题:物理内存的空间布局是这样的吗🧐??

  • 直接输出结论:该结构不是物理内存,而叫做进程地址空间或者虚拟地址空间。
  • 至于如何验证,下一小节将给出答案。

本文将以该图为出发点,粗滤谈论虚拟地址空间相关知识📚。


二、虚拟地址空间与页表

2.1、初谈虚拟地址空间与页表

我们首先来看下面一段代码:

#include<stdio.h>#include<unistd.h>intmain(){intval=0;printf("我是一个进程,pid:%d, ppid:%d, val:%d, &val:%p\n",getpid(),getppid(),val,&val);pid_t id=fork();if(id==0){//fatherwhile(1){printf("我是一个父进程,pid:%d, ppid:%d, val:%d, &val:%p\n",getpid(),getppid(),val,&val);sleep(1);}}elseif(id>0){//childwhile(1){val++;printf("我是一个子进程,pid:%d, ppid:%d, val:%d, &val:%p\n",getpid(),getppid(),val,&val);sleep(1);}}return0;}


观察现象,我们不免会有疑惑:

问题:怎么同一个地址的值即可以大于0,又可以等于0呢🤯??

  • 这一现象进一步验证了我们先前的说法:图中所显现的不是物理地址;语言上学习的地址都称为虚拟地址,它实际上就是一个编号。

  • 📖每一个进程都各自会有一套虚拟地址空间和页表。虚拟地址空间我们相对来说比较熟悉,而页表,实际上就是用来实现物理内存的地址和虚拟地址之间的相互映射的表。

  • 当我们fork()出一个子进程的时候,子进程会以父进程为模板,创建出自己的tesk_struct结构体,从而创建出自己的虚拟地址空间和页表,其当前内容与父进程的完全一致(类似拷贝),这也说明了,为什么fork后父子进程会共享代码与数据。

  • 我们已经知道进程之间具有独立性。那么,既然父子进程共享代码与数据,我们如何保证父子进程之间的独立性呢?? 代码与数据、内核数据结构都不相同。并且操作系统规定:📖父子进程中,任何一个进程尝试对共享的变量进行修改操作,都无法直接修改,而是会发生“写时拷贝”,修改一个进程对应的页表中对应变量所映射的物理内存的地址,开辟一个新的物理地址。这也很好地保证了父子进程之间的独立性。

  • 有了以上对虚拟地址空间的认知,我们也能够很好地解释为什么一个变量能够同时接收不同的返回值的问题了。这也是我们在fork()部分遗留的一个问题

2.2、再谈页表

经过上面的初步认识,我们已经基本了解页表的功能。
此时,我们就已经可以解答我们在学习C/C++时经常感到疑问的点:

问题1️⃣:常量字符串和代码为什么是只可读的呢🧐??

  • 页表中实际上存在着许多标志位,其中就写明了每一块对应空间的权限属性。r、w、x

    代码与常量字符串的地址,被操作系统在页表中仅仅标识为可读权限,本质上是不让进程能够再写入,因此常量字符串与代码只可读。

标志位除了可以标识对应地址的权限,还可以标识该虚拟地址所指向的数据是否存在于物理内存。看到这里,肯定会有人感到奇怪,明明页表上都写明了虚拟地址与物理地址的映射关系,为什么还需要这样一个标识符来记录数据是否存在于物理内存中呢??难道我们的数据还可能不保存在物理内存中吗🧐??
还真的有这种可能😋,当我们的物理内存容量不足的情况下,操作系统会将内存中一些暂时无用的进程与数据放入磁盘的swap分区中,以此减缓内存的压力。此时我们的对应的进程的页表中,便会将一些数据标识为0状态(即不存在于物理在内存中),相反,若存在于物理内存中,则被标识为1。当我们重新访问这些数据的时候,操作系统会将对应数据从swap分区中拉回内存。

补充:查表的操作由MMU内存管理单元进行,MMU是存放于CPU内部的一种硬件。


三、虚拟地址的管理

3.1、描述虚拟地址空间

由上文我们已经大致了解了虚拟地址空间与页表的作用,作为操作系统的重要组成部分,我们一定是要将它好好的管理起来!!那么,我们如何进行管理呢??
我们曾经说过,管理的本质就是“先描述,再组织”,因此,我们首先应该描述虚拟地址空间。如何描述🤔?当然是使用一个内核数据结构!!

在Linux中,描述虚拟地址空间的所有的信息的结构体是mm_struct。每个进程只有一个mm_struct结构体,在每个进程的task_struct 结构中,有一个指向该进程的mm_struct结构体指针。可以说,mm_struct结构体是对整个用户空间的描述。每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间才能互不干扰。


3.2、理解区域划分

问题1️⃣:如何对虚拟地址空间进行空间划分呢🧐??

  • 我们既然知道虚拟地址空间本质上就是一个结构体,那么,我们只需要在mm_struct结构体内记录某段区域开始的位置以及结束的位置即可。

  • 下图则为mm_struct结构体中对应的区域划分相关内容。

  • 语言中的地址本质上就是一个编号,归根结底是一个整数上图也能够验证这段话,内核中用unsigned long类型来表示。

  • 我们知道堆区与栈区的范围是可变的,有了以上的知识储备,我们应该能够想到:调整区域的大小,本质上是修改内核中对应的变量。


3.3、如何管理各区域??

我们已经了解区域划分相关内容,但是仍然不够,虚拟地址空间极其庞大,我们应该更加细粒度地管理各个区域!!例如以下问题:

问题1️⃣:区域划分后,大部分区域在使用时都是连续的,但是我们使用堆区地空间时是不连续的,此时我们应该如何管理这些离散的空间呢🧐??

  • mm_struct结构体中有一个mmap指针,指向由vm_area_struct结构体所组成的链表结构。
  • vm_area_struct结构体更加细粒度地划分了每一个区域,其中包含两个变量vm_end和vm_start用来表明用户所使用的每一块内存的上下限。与之相比,之前所说的mm_struct中的变量则是更加宏观的划分,用于指向各个区域的上下限。

四、虚拟地址空间的意义

如果要更加深刻地理解虚拟地址空间,我认为我们必须先明白为什么要有虚拟地址空间。

假设没有虚拟地址空间,我们用户就得直接与物理内存打交道,无疑,这种行为绝对很危险,就像我们不能够直接接触操作系统内核一样。因此,虚拟地址空间的存在是极其必要的,它能够控制进程的行为,拦截非法的操作(例如:野指针问题,修改常量字符串等),保护物理内存。

倘若就非得让我们用户直接与物理内存打交道,物理内存如此复杂,我们是否必须对其进行管理??无疑,肯定是要的。比起管理物理内存,管理虚拟地址内存相对来说还是要更加轻松,并且有了虚拟地址空间与页表进行映射,原则上进程的代码与数据可以在内存中随意地加载(无序变为有序)。

并且,有了虚拟地址空间,进程则只需考虑虚拟地址空间与页表部分,而无需在意物理内存等硬件之间地操作;相反,物理内存等硬件也无需考虑进程相关地问题。这就体现了进程管理与内存管理解耦合。

问题1️⃣:LinuxOS中创建一个进程,是先创建task_struct还是先加载代码与数据🧐??

  • 当然是先创建一个task_struct,再加载对应的代码与数据。
  • 可是,当我如果不着急执行这个进程,可以需要执行代码或者访问数据的时候,再进行加载吗??当然可以,这种行为被称为懒加载。懒加载也很好地体现了虚拟地址空间对进程管理与内存管理解耦合的作用。用效率换取空间的合理使用
  • C/C++中的new和malloc本质上就是在虚拟地址空间的堆区上申请空间,在真正使用这些空间的时候,操作系统才会在物理内存中申请空间。这也被称为“缺页中断引起的内存二次申请”

综上所述,虚拟地址空间的意义总结如下:

  1. 控制进程的行为,拦截非法的操作野指针问题,修改常量字符串等,保护物理内存。
  2. 有了虚拟地址空间与页表进行映射,原则上进程的代码与数据可以在内存中随意地加载无序变为有序
  3. 进程管理与内存管理解耦合。

完🥳🥳🥳

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询