目录
- 快速了解:
- 引入最基本的理解:
- 细节:
- 如何理解地址空间:
- a.什么是划分区域:
- b.地址空间的理解:
- 为什么要有进程空间?
- 进一步理解页表与写时拷贝:
快速了解:
先来看这样一段代码。
运行结果:
因为子进程会继承父进程的代码和数据,所以这种结果我们是可以接受并理解的
但是如果我们对这段代码进行一个小修改:
运行结果:
我们说过父子进程是有独立性的,
而就现在来说进程 = 操作系统内核数据结构 + 代码与数据
,那么一个新的进程创建势必会创造新的操作系统内核数据结构 ,而代码是只读的,那么数据是怎样进行处理的呢?
对于上边的例子:
地址是一样的,但是数据却不一样!这就说明&val的地址不是真实地物理地址,是虚拟地址
引入最基本的理解:
我们在学习C语言时肯定见过这样图
我们申请的堆上的空间,以及上篇文章讲过的命令行参数与环境变量等等都在这里申请的。
实际上这个东西并不是真的物理内存,而是虚拟内存,
根本上是个结构体(在linux中叫做struct mm_struct
),有那么多的结构体我们肯定要管理,那就用到了“先描述,在组织
”的
物理内存与虚拟内存的关系如下图:
解释:
我们写好的可执行程序先创建一系列内核数据结构,再将代码和数据加载到内存。
我们的g_val的虚拟地址在mm_struct内,通过页表映射到物理内存。
因为我们的子程序会继承父进程的代码和数据(故虚拟地址与页表的映射关系等会被子进程完全继承),因此我们的父子进程是指向同一个物理内存。
重点来了!
因为我们说过进程之间具有独立性,那么必然父子进程之间是不能相互影响的,那我们现在指向的都是同一块物理内存空间,怎样做才能不破坏独立性的规则呢?
答案是写时拷贝
!
意思是在写入数据时,先开辟一块新空间,将旧空间数据拷贝过去,在进行写入。
这就解决了这个问题。
可能这里有人会说:
既然都要求独立性了,那我们创建子进程时能不能直接就开辟好新的空间呢?
答案是没有必要,因为有时我们并不写入,要写入时在写时拷贝,这就是按需申请
,通过调整拷贝的时间达到节约空间的目的,况且我们有非常多的数据实际上是并不会发生写入操作的。
细节:
目前我们大概理解了虚拟地址,
但是我们刚刚说的只是很笼统的框架,很多细节还没有说到。
如何理解地址空间:
研究这个问题前我们需要先研究两个子问题。
a.什么是划分区域:
我们的虚拟地址空间实际上是个结构体,这个结构体里存放着很多的start和end属性进行空间划分
这就很像我们上学时划的38线,今天你多一点,明天我又夺回来。
b.地址空间的理解:
回到问题本身。
持续更新~