引言
今天我们来学习进程概念中的最后一个内容:进程地址空间。相信大家平时在写代码时都有过一个疑问:我们通过取地址获得的地址真的是内存上的物理地址吗?如果不是,它们是什么呢?我们的数据到底是怎么存储的呢?今天一篇文章带你了解清楚。
认识进程地址空间
引入
我们先通过一个简单的代码来引入今天的知识:
cpp
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
a = 20;
printf("我是子进程,我的a值是:%d, 地址是:%p\n", a, &a);
}
else
{
printf("我是父进程,我的a值是:%d, 地址是:%p\n", a, &a);
}
return 0;
}
通过在命令行中运行这个程序,我们会发现一个很奇怪的问题,为什么在子进程和父进程中,它们数值a的地址是相等的?为什么一个地址同时存了两个数值?这是怎么做到的呢?接下来,我就来带大家认识其中的原因

虚拟地址空间
首先,告诉大家一个事实,我们平常通过取地址所看到的地址全部都不是实际上的物理内存空间。
物理内存空间
物理上的内存空间就这么多,不可能每次都将其单独分配给一个程序,那么操作系统是怎么做的呢?操作系统会假装让每个进程都独占整个内存空间,在每个进程内部都会构建一个假的地址空间,我们一般将其称为虚拟地址空间。一个进程,一个虚拟地址空间。
虚拟地址空间本质上也就是一个数据结构,在Linux系统里叫做mm_struct,它实际上就是一个结构体变量,在这个结构体变量中,只存储了每一个区域的开始位置的虚拟地址和结束位置的必虚拟地址。如果想要对区域的大小进行调整,只需要对表示虚拟地址开始和结束的变量进行加或减就行了。
认识页表
除了虚拟地址空间之外,每个进程还会有一个对应的页表,这个页表是做什么用的呢?这个页表中的每一个位置都会存储两个地址,一个虚拟空间地址,一个物理地址,当进程想访问这个寻你地址时,操作系统会自动在页表中查找这个虚拟地址对应的物理地址,找到物理地址,进而访问这个变量。也就是说,页表是用来将虚拟地址映射为物理地址的。
存储数据的过程
既然每个进程所拥有的地址空间都是虚拟的,那它们是怎么存储数据的呢?首先,当我们在代码中定义一个新变量时,进程会先在虚拟地址空间中的对应区域申请指定大小的空间,将该空间的地址存进页表中,然后操作系统会在物理地址空间中也申请一块空间作为数据实际存储的位置,将该地址也存进页表中,加载程序,通过页表的映射关系,将数据存储到实际的物理地址上。当要使用这个数据时也是如此,也是通过页表的映射关系来从物理地址上获取这个数据。
父子进程的地址问题
通过上面的讲解我们大致了解了进程中的数据是怎么进行存储和读取的,但是我们还不知道为什么父子进程明明虚拟地址一样,数据却不一样。
因为进程的独立性,子进程实际上也会有自己独立的虚拟地址空间和页表,但是就像我们之前说过的那样,子进程在创建时,会将父进程的所有内容都拷贝一份,虚拟地址空间和页表也不例外。
这就是为什么在默认情况下父子进程的代码和数据都是共享的,因为它们的虚拟地址和与物理地址的映射关系都是相等的。当子进程不修改数据时,操作系统认为没有必要单独再开一块空间出来存储子进程的数据,于是两个进程就共用一块物理地址空间。
那为什么子进程在修改自己数据内容时不会影响父进程的数据呢?因为一旦操作系统发现子进程要修改变量,就会先在内存当中开辟一块新空间,将原本的内容也拷贝到新空间里,然后将页表中的映射关系改为映射到新的空间上,这样操作后,虽然子进程的虚拟地址没有改变,但是该虚拟地址映射的物理地址已经改变,指向了一块新的物理地址,这样的操作就称为写时拷贝,意思就是在改变变量时才进行拷贝。
写时拷贝
这就是为什么父子进程的变量地址会相等的原因,因为那实际上就不是真实的地址,而虚拟地址映射的物理地址不同,存着不同的数据。
为什么要有虚拟地址空间
1、将地址从无序变得有序。
2、转化过程中,也可以对你的地址和操作进行合法性判定,进而保护物理内存。所以为什么当我们的程序中出现野指针时就会直接结束,就是因为系统在根据你提供的虚拟地址查页表时,发现找不到对应的映射关系,就会判断该操作有问题,将你的进程杀掉。
3、让进程管理和内存管理进行一定程度的解耦合。解耦合就是解除两个模块的强绑定,让它们只通过标准接口交互。虚拟地址空间引入地址翻译层,进程只使用虚拟地址,不用关心物理内存的真实情况;内存管理器负责页表映射、物理页管理。进程逻辑和底层物理内存管理互不侵入,实现了解耦合。
结语
虚拟地址空间不只是简单的地址翻译,更是操作系统实现解耦的核心抽象。它让进程无需关心物理内存的分布,只需操作虚拟地址;内核则通过页表独立管理物理内存。二者隔离开来,既实现内存隔离与安全,也让内存调度、换页等机制得以落地,是理解现代内存管理的关键。