在学习进程时,提到虚拟内存空间 这个概念,但由于涉及到的知识比较多,当时并没有细讲。要搞清楚虚拟地址空间需要大量时间,本文打算先浅浅地讲述一番,以后会在这个话题有更多更广的讲解。看完本文,进程部分的一些问题将得到解答,如:虚拟内存空间是什么?有什么用?
何为虚拟地址空间
在进程部分,我们知道进程是由 PCB+数据 构成的,PCB 是一个结构体,它的具体实现叫做 task_struct 。这个 task_struct 管理着一块空间,这块空间就是 虚拟地址空间 。
既然叫 " 虚拟 "的空间,那它肯定不是指的我们那块真正的空间------内存 ,其实学过 C 语言就可以自己验证:创建一个死循环不断开辟空间,如果编译器拿到的是真是的内存,那么你的电脑就会因为内存爆满而崩溃,但事实是只是程序崩了,电脑屁事没有。
虚拟地址空间是模拟出的一块 " 空间 ",它的容量和内存一样大,每个进程都有自己的一个虚拟地址空间。这里可能有人要问了:你说虚拟地址空间的大小和内存一样,而且每个进程都有自己的一个虚拟地址空间,但是电脑里面会有很多进程啊,就算是模拟出来的空间,你存放的数据总是真的吧!你哪里来那么多的空间模拟呢?
这时候系统说话了:进程啊,我给你开了 4GB 的空间(假设电脑有 4GB 的内存),你要用空间的话你尽管用,只要没超过 4GB,你要用多少我都给你补。
系统这家伙看着挺大方,给你补还。但当内存满了,它没有多余内存了,怎么办?这时候某些进程就遭殃了。

系统会把一些不常用的进程暂时放到硬盘的 Swap 分区里,如果这里也满了,系统甚至会把一些进程直接杀死。
虚拟地址空间和内存的关系
通过上面的内容,相信你已经对虚拟地址空间有了一定的了解,接下来讲讲虚拟地址空间和内存的关系。前面也说,我们知道,电脑是把真实的内存空间中的地址隐藏起来的,无法查看。那么,虽然虚拟地址空间是模拟出来的,但是你的数据是真实的呀!你总要在内存中开辟空间存放它们,我们只有虚拟的地址,怎么在内存中找到它们呢?
想通过虚拟地址找到真实的内存空间,我们要用到一个叫 " 页表 " 的东西,这个在进程部分也讲过。页表存储着虚拟的地址,同时它又存储着真实的地址,一个虚拟地址对应真实地址,有点类似哈希表。
一个进程,一套页表。
对于页表,有一个特殊的词 " 缺页中断"。我们之前也看到,电脑中是有多个进程同时占有内存的,但是给每个进程一开始都分配完整的内存是不可能的,既是做不到也是不值得,做不到这个不用多少,不值得呢?你想啊,一个进程分配 4GB 的空间它用的完吗?大部分情况是用不完的吧,所以这会导致资源的浪费。
系统的做法是:进程申请虚拟内存时,内核只会在页表里做标记,分配好虚拟地址,不会立刻分配物理地址和物理内存。当进程第一次读写这块虚拟内存 时,CPU 就会去查页表,发现没有对应的物理地址,就会立刻触发缺页中断,找一块空闲物理内存填上,再更新页表。
写时拷贝再深入
有了以上知识,我们现在可以对进程部分的写时拷贝这个知识点进行更加深刻的理解了。
我们之前说过,父进程拷贝出的一份副本作为子进程。对于子进程的一切,在没有做出改变时,其实和父进程大差不差,页表也是直接拷贝过来的。子进程页表直接继承父进程页表中的虚拟地址,然后开辟新的内存空间供自己使用。这也是为什么在查看父子进程的数据地址时,我们会发现不同的数据拥有同一个地址。
那么,我们也能很好地解释接收 fork 返回值的变量为什么可以有不同的值了,其实就算改变量在父子进程中都有相同的虚拟地址,但是真实的内存地址是不同的。
写时拷贝和 "缺页中断" 都是是 " 懒加载 " 思想的应用,即:能不复制就不复制,能拖就拖,直到万不得已才做。