Linux操作系统 虚拟地址空间

article/2025/9/30 8:31:42

目录

1、一段代码引出一个问题

运行结果:

讨论:

2、Linux下进程虚拟地址空间分布

3、什么是虚拟地址空间?

4、虚拟地址出现之前:进程直接访问物理内存

5、再述虚拟地址空间

虚拟地址空间结构体是如何进行区域划分的呢?

解答最初的问题:

解答一个疑问:一个id变量怎么可能保存不同的值?

为什么存在虚拟地址空间?(虚拟地址空间的好处

一、保护物理内存

二、内存管理 和 进程管理 低耦合

三、延时分配,提高整机效率

四、使内存分布有序化,实现进程独立性

重新理解挂起


1、一段代码引出一个问题

  1 #include<stdio.h>2 #include<unistd.h>3 int g_val = 100;4 5 int main()6 {7     pid_t id = fork();8     if(id == 0)9     {10         // 子进程11         int i = 0;12         while(1)13         {14             printf("I am child process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);15             i++;16             if(i == 5)17             {18                 g_val = 200;19                 printf("Child process changed g_val success!!!\n");20             }21             sleep(1);                                                                                                                                                                                        22         }23     }24     else {25         while(1)26         {27             printf("I am parent process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);28             sleep(1);29         }30     }31     return 0;32 }

运行结果:

[yzl@VM-4-5-centos tmp]$ ./proc
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
Child process changed g_val success!!!
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:200, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:200, &g_val:0x601054

讨论:

上述代码为,创建子进程,若干秒后,子进程改变全局变量值,发现子进程与父进程打印此全局变量值时,值不同,且地址相同。

同一个地址处的值在同一时刻不可能不同,于是引出了虚拟地址空间的概念。即这里子进程与父进程打印的地址并非实际的物理地址,而是一种虚拟地址(线性地址)。

2、Linux下进程虚拟地址空间分布

虚拟地址空间使得每个进程看待内存时都有一个统一的视角,并且在他们看来,内存的分布是井然有序的。具体分布如下图

1. 栈堆相向增长,堆向高地址增长,栈向低地址增长。这两个区域是动态变化的。

2. 虚拟地址空间分为两个空间:1. 内核空间,在32位下占1G   2. 用户空间,在32位下占3G
即[0, 3GB] 用户空间    [3GB, 4GB] 内核空间

3. static修饰局部变量,本质上是将此变量属性变为全局属性,存储在全局区。而语法的限制使得此static变量仅能在局部可见。

4. 上图虚拟内存分布仅适用于Linux操作系统,不适于Windows。

5. 一个有关堆区的知识:当C语言使用malloc函数时,申请10字节空间,实际在内存中会占用大于10字节的空间,多出的空间用于存储一些属性。这也是为什么free时传首地址即可,而不需要传空间大小。

3、什么是虚拟地址空间?

1. 虚拟地址空间(进程地址空间)在操作系统内核中是一个数据结构,在Linux内核中,就是一个struct结构体

2. 在Linux下,进程地址空间是一个名为mm_struct的结构体,主要存储各区域(堆,栈,全局数据区,只读代码区等)的范围,即start和end,用于划分各个区域。

3. 页表是和虚拟地址空间结构体配套的内核数据结构,页表的作用是:保存对应进程中每一个虚拟地址到物理内存中的物理地址的映射关系。即起一个映射配对的作用。   因为实际上数据,代码,变量等肯定最终要存储在物理内存中。

4. 页表起映射作用,就类似于C++中的map数据结构,key 是虚拟地址, value是对应的物理地址

5. 每一个进程都有一份地址空间结构体变量mm_struct和页表实例化对象。在磁盘中的二进制可执行程序加载到内存中时,要创建对应的PCB结构体,同时,也会创建对应的地址空间结构体变量和页表。


4、虚拟地址出现之前:进程直接访问物理内存

在早期计算机操作系统内部,进程直接访问物理内存。这样做有很多弊端。在说弊端之前,有一个点需要明确:内存本身是可以随意读写的,物理内存是不存在只读的情况的。

比如,最典型的野指针问题,一个进程的野指针很容易破坏其他进程,甚至影响操作系统内的安全数据。其次,进程直接访问物理内存,使得进程和物理内存耦合度很大,内存管理变得不方便,从而内存碎片等问题也变得更难处理。

基于进程直接访问物理内存的弊端,衍生出虚拟地址空间。

5、再述虚拟地址空间

 进程PCB,虚拟地址空间,页表,物理内存的关系大致如上图所示。

1. PCB中有一个struct mm_struct* mm指针数据成员指向这个进程对应的mm_struct

 2. 因为内存本身是随意读写的,所以,在地址空间+页表的作用下,可以在某些虚拟地址与物理地址的映射关系中,用某些数据(比如页表中存储)表明这个内存是只读的。以此来保护某些数据。这些都是地址空间+页表的作用,而非内存的权限控制。

3. 基于第二点,地址空间+页表可以对某些内存进行权限管理,比如常量代码区设为只读。同时,对于某些内存的非法访问,也可以及时禁止。从而保护物理内存。

4. 我们知道,进程是具有独立性的,那么,在地址空间+页表的作用下,只要使得各个进程的虚拟地址通过页表映射的物理内存是不同的,则可以保证进程之间互不干扰,即进程独立性。

虚拟地址空间结构体是如何进行区域划分的呢?

通过定义栈区,堆区,常量代码区,全局数据区等区域的start,end。来对这些区域进行划分。

比如栈区,堆区是动态变化的。那么只需要增大或者减小end,即可对栈区堆区的空间大小进行控制。再比如只读代码区,在源文件编译之后,可执行程序内部已经有了虚拟地址。若此文件加载到内存中变为进程,则mm_struct中的常量代码区的start和end即可通过这些编译生成的虚拟地址来确定start和end

Linux内核源码

 如图,为Linux内核源码中mm_struct的定义,即虚拟地址空间的定义。可以看到,它确实是通过定义各个区域的start,end来划分各个区域的。类型是unsigned long


解答最初的问题:

  1 #include<stdio.h>2 #include<unistd.h>3 int g_val = 100;4 5 int main()6 {7     pid_t id = fork();8     if(id == 0)9     {10         // 子进程11         int i = 0;12         while(1)13         {14             printf("I am child process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);15             i++;16             if(i == 5)17             {18                 g_val = 200;19                 printf("Child process changed g_val success!!!\n");20             }21             sleep(1);                                                                                                                                                                                        22         }23     }24     else {25         while(1)26         {27             printf("I am parent process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);28             sleep(1);29         }30     }31     return 0;32 }

 最初,父进程与子进程打印出同一个变量地址相同,但是值不同。我们现在知道了,这个地址其实是虚拟地址,而非物理地址。

一个事实:父进程创建子进程时,除了一些子进程独有的属性,比如典型的pid。其余大部分属性和数据都是从父进程那里拷贝过来的。包括mm_struct 和 页表。

所以,起初,在子进程执行g_val = 200;之前,也就是修改这个全局变量之前。因为子进程的mm_struct和页表是直接从父进程那里拷贝过来的。故父子进程的g_val的虚拟地址,以及这个虚拟地址映射的物理内存中的数据都是一样的。这样做的原因是:如果有某些数据,父子进程都是只读的,也就是不会修改,那么这份数据在内存中只保存一份即可,没必要给子进程在内存中再创建一份相同的,只读的数据。

而当子进程执行g_val = 200;时,这是子进程对这个全局数据执行写操作。因为父子进程访问的g_val不应该互相干扰。故此时,OS在内存中的其他区域,拷贝了一个新的,子进程的g_val,赋值为200,并改变子进程的页表的映射关系(不需要改变g_val的虚拟地址)。从而当子进程改变g_val后,父子进程打印的这个全局数据的虚拟地址相同,但是映射到物理内存不同区域,值不相同。才有了最初的现象。

这种子进程写数据时进行拷贝的操作,称为写时拷贝!

解答一个疑问:一个id变量怎么可能保存不同的值?

pid_t id = fork();

如上代码,一个id怎么会保存不同的值呢?

我们知道,fork函数内部的主体逻辑就是创建子进程,而当fork函数return时,子进程已经创建好了。所以两个进程,两个执行流会执行两次return。

fork return的本质就是对id变量进行写操作!

所以,此时发生了写时拷贝,父子进程各自在物理内存中,都有属于自己的id变量空间!只不过在用户层用同一个变量(虚拟地址)来标识了。


为什么存在虚拟地址空间?(虚拟地址空间的好处

一、保护物理内存

凡是非法的访问或者映射,OS都会识别到,并终止你的进程。

原因就是:比如const char* p = "abcd"; p指针保存的是虚拟地址,且这个虚拟地址所映射的物理内存不可被写。这都是基于地址空间+页表。除了这种保护只读的数据,当存在野指针或者非法访问时,地址空间+页表也能以某种方式告诉OS,从而OS可以发信号终止这个进程。

这样一来,物理内存的访问都在OS的监管之下。保护了物理内存,物理内存中的数据,其他进程,以及内核的相关有效数据。

二、内存管理 和 进程管理 低耦合

因为有虚拟地址空间+页表,物理内存中的数据可以随意存储。只要保证虚拟地址可以通过映射找到对应的数据即可

=》物理内存管理 和 进程管理因此可以做到关联性很低

=》内存管理模块和进程管理模块 完成了解耦合。在操作系统层面,这两个模块关联性很低,维护成本也会降低

三、延时分配,提高整机效率

我们在C语言中进行malloc时,申请内存本质是在虚拟地址空间中申请,并不会因为malloc,就立即申请内存空间。

=》原因是:如果我malloc时就立刻申请物理内存,且不立刻使用,则这就是一种内存资源浪费。

=》所以,因为有地址空间存在,上层申请内存,其实是在地址空间中申请。而当你进行对物理内存的访问时,才执行相关的内存管理算法,帮你申请内存,构建页表映射关系。然后再让你进行内存访问。 (这些是由操作系统完成的,进程0感知)

=》虚拟地址存在,但是物理内存中没有对应的空间。称为缺页中断。

=》那么,这样延时分配的好处就是:确保物理内存中的有效使用是100%的,并不会出现物理内存中申请空间但不使用的情况。

=》延时分配的策略,提高整机效率。

四、使内存分布有序化,实现进程独立性

第二点中:物理内存可以随意存储。但是因为页表的存在,可以建立虚拟地址和物理地址的映射关系。从而使得从每个进程的视角看来,内存分布都是有序的。

=》 地址空间+页表的存在,可以使得内存分布有序化!

通过让不同内存的虚拟地址映射到物理内存的不同区域   =》 实现进程独立性


重新理解挂起

1. 创建进程时,并非一定需要将程序所有的代码和数据都加载到内存中,并创建内核数据结构,才能执行。

2. 极端情况下,当只有内核数据结构被创建出来时,就是新建状态。

3. 理论上,可以实现对程序的分批加载。同样可以实现程序的数据/代码的分批唤出。

4. 当一个进程等待某些响应时,或者短时间内不会再执行了,比如阻塞了。   此时将进程的数据和代码换出,就叫做挂起。


后言:

综上,可以说理解了虚拟地址空间到底是一个什么东西——OS内核中的一种数据结构,主要保存各个数据区的start和end。  可是,编译好的程序运行起来之后,是如何与虚拟地址空间,页表,物理内存配合的呢?  其实就是:程序的指令执行的详细过程,还没有弄清楚。 有一个点是:虚拟地址空间的这一套策略并非只要操作系统遵守即可,编译器也必须遵守相同的策略。那么,编译器编译好的程序内部,其实已经有了虚拟地址了,这些可以用来初始化mm_struct的某些属性。

进程地址空间使得每个进程都认为自己独占4GB内存,它们也看不到其他进程的存在。那么,内存一共就是16GB/32GB(大多情况)。操作系统是如何进行控制,使得几十个进程顺利执行呢?大致是:当进程需要内存空间时,OS会通过一些内存管理方式,来置换/管理内存空间。从而分配一些内存空间给进程。

这里大概说的是一些疑惑的点,这些东西的理解都需要更多知识的学习。


http://chatgpt.dhexx.cn/article/8sHvIuBc.shtml

相关文章

虚拟地址、虚拟内存、物理地址、物理内存

虚拟地址、虚拟内存、物理地址、物理内存 物理内存和虚拟内存虚拟地址、物理地址 物理内存和虚拟内存 物理内存 物理内存时有限的&#xff0c;当有多个进程要执行的时候&#xff0c;假设都要给4G内存&#xff0c;很显然你内存小一点&#xff0c;这很快就分配完了&#xff0c;…

虚拟地址空间【详解】 虚拟地址空间是什么 | 为什么要有虚拟地址空间

目录 一、什么是虚拟地址空间 / 虚拟地址空间是如何被设计的 1.先看一下linux空间分布 I.示意图: II.验证: 2. 在已知Linux内存分布之后&#xff0c;我们来看一个奇怪的现象 I.代码 : II. 输出: III.思考&#xff0c;引出--虚拟内存&#xff08;虚拟地址空间&#xff0…

虚拟地址转物理地址

概述 CPU是如何利用页目录和页表 等数据结构将一个32位的虚拟地址翻译为32位的物理地址的。其过程可以概括为如下步骤。 ① 通过CR3寄存器定位到页目录的起始地址&#xff0c;正因如此&#xff0c;CR3寄存器又称为页目录基地址寄存器&#xff08;PDBR&#xff09;。取线性地址…

虚拟地址与物理地址

虚拟地址 恰如其名&#xff0c;这个地址是虚拟的&#xff0c;与具体环境是解耦的&#xff0c;这样可以是程序员在编写程序时只需要关注代码逻辑本身&#xff0c;而不需要考虑地址分配。这些虚拟地址并不是真正运行在机器上的内存地址&#xff0c;每个程序的虚拟地址都是独立存在…

虚拟机ip地址

文章仅为自己在建立虚拟机过程中遇到的问题进行记录。 虚拟机查看IP地址输入 ip address 即可查看&#xff1b; 新建虚拟机的时候会遇到输入ip addr出现没有看到ip地址&#xff0c;例如如下情况时&#xff1a; 每个人上图可能会有不同显示&#xff0c;例如我的就是以eno16777…

浅谈虚拟地址转换成物理地址(值得收藏)

这里&#xff0c;我们讲解一下Linux是如何将虚拟地址转换成物理地址的 一、地址转换 在进程中&#xff0c;我们不直接对物理地址进行操作&#xff0c;CPU在运行时&#xff0c;指定的地址要经过MMU转换后才能访问到真正的物理内存。 地址转换的过程分为两部分&#xff0c;分段…

虚拟地址

虚拟地址 虚拟地址是程序运行在保护模式下&#xff0c;这样程序访问存储器所使用的逻辑地址称为虚拟地址。 那为什么需要虚拟地址呢 我们要说到寻址方式 寻址&#xff1a;根据指令内容确定操作数地址的过程&#xff0c;称为寻址 在16位的cup或者8086cpu的时候&#xff0c;他…

虚拟地址如何访问到物理地址

环境&#xff1a;32bit CPU 一、通过二级页表映射的方式访问物理地址 1、取一级页表的基地址Abase1 2、取虚拟地址的前12bit[31:20]地址O1 3、计算得到新地址Apgd(Abase1&0xFFFFF000)O1&#xff0c;此地址是PGD页表上的地址&#xff0c;取此地址中的数据Abase2 4、取虚拟地…

总线地址、物理地址、虚拟地址

1、总线地址 地址总线&#xff08;Address Bus&#xff09;是一种计算机总线&#xff0c;是CPU或有DMA能力的单元&#xff0c;用来沟通这些单元想要访问&#xff08;读取/写入&#xff09;计算机内存组件/地方的物理地址。 其实就是CPU能够访问内存的范围。 CPU寻找外部的内…

虚拟地址空间和物理地址空间

1.概念 物理地址&#xff1a;物理地址空间是实在的存在于计算机中的一个实体&#xff0c;在每一台计算机中保持唯一独立性。我们可以称它为物理内存&#xff1b;如在32位的机器上&#xff0c;物理空间的大小理论上可以达到2^32字节(4GB)&#xff0c;但如果实际装了512的内存&a…

Linux虚拟地址空间

目录 父子进程地址相同的变量值不同问题运行结果 Linux下进程虚拟地址空间分布什么是虚拟地址空间&#xff1f;进程直接访问物理内存&#xff08;无虚拟空间&#xff09;再述虚拟地址空间&#xff01;虚拟地址空间结构体是如何区域划分?解答最初的问题延伸问题: 一个pid变量怎…

虚拟地址空间

对于每一个进程都会对应一个虚拟地址空间&#xff0c;对于32位的操作系统&#xff08;其指令的位数最大为32位&#xff0c;因此地址码最多32位&#xff09;&#xff0c;虚拟地址空间的大小为B即0~4GB的虚拟地址空间&#xff0c;其中内核空间为1GB&#xff0c;如下所示&#xff…

逻辑地址、物理地址、虚拟地址

文章目录 物理地址(physical address)虚拟地址(virtual memory)逻辑地址(logical address)线性地址(linear address)或也叫虚拟地址(virtual address)地址转换 物理地址(physical address) 用于内存芯片级的单元寻址&#xff0c;与处理器和CPU连接的地址总线相对应。 虽然可以…

CPU中虚拟地址、逻辑地址(有效地址)、线性地址、物理地址

虚拟地址、逻辑地址&#xff08;有效地址&#xff09;、线性地址、物理地址 1、虚拟地址2、逻辑地址&#xff08;有效地址&#xff09;3、线性地址4、物理地址5、总结 1、虚拟地址 在实模式下&#xff0c;虚拟地址是指由程序产生的由段选择符和段内偏移地址组成的地址。经过CPU…

虚拟地址和物理地址

1、地址概念 物理地址&#xff1a;物理内存就是真实的内存&#xff0c;CPU的地址线可以直接进行寻址的内存空间大小。比如在32位平台下&#xff0c;寻址的范围是2^32也就是4G&#xff0c;并且这是固定的。在实际的应用中&#xff0c;很多的应用程序都比较大&#xff0c;计算机…

Linux操作系统~什么是虚拟地址?深度剖析进程地址空间

目录 1.所以进程的地址空间是什么呢&#xff1f; 2.mm_struct内部有什么&#xff1f; 3.虚拟地址空间与物理内存如何关联 页表 4.为什么设计这样一个进程地址空间&#xff0c;不让程序直接访问内存 Q&#xff1a;为什么子进程修改值以后&#xff0c;地址还是相同&#xf…

初识虚拟地址空间

物理地址和虚拟地址 物理寻址&#xff1a;CPU访问存储器的最原始方法就是直接用物理地址&#xff08;Physical Address, 可简称PA&#xff09;。物理地址是唯一的。 虚拟寻址&#xff1a;CPU通过生成一个虚拟的地址来访问内存&#xff0c;在访问前会把虚拟地址转化为物理地址…

虚拟地址空间,虚拟文件系统

1、虚拟地址空间 1、概念与原因 虚拟地址空间是一个抽象的概念&#xff0c;在IBM中&#xff0c;这样说道&#xff1a;它存在&#xff0c;但你看不见&#xff0c;就是虚拟的。虚拟地址空间就是这样一个东西。&#xff08;注意区分虚拟内存与虚拟地址空间&#xff09; 虚拟地址空…

彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

程序经过编译后&#xff0c;变成了可执行的文件&#xff0c;可执行文件主要包括代码和数据两部分&#xff0c;代码是只读的&#xff0c;数据则是可读可写的。 可执行文件由操作系统加载到内存中&#xff0c;交由CPU去执行&#xff0c;现在问题来了&#xff0c;CPU怎么去访问代…

使用POI导出Excel(并使用公式)

使用POI导出Excel&#xff08;并使用公式&#xff09; 使用java直接生成Excel并填充数据 可以参考POI官方文档&#xff0c;就是对Sheet&#xff0c;row&#xff0c;cell&#xff0c;Formula等操作&#xff0c; https://poi.apache.org/components/index.html 这种方式也可以生成…