1. 进程虚拟空间
在32位的操作系统下,总共有32根地址线(32字节),而每根地址线只能表示两种电信号,即0:低电频,1:高电频,因此对应到内存中,每个程序在运行时会分配4G的空间(1G = 210MB = 220KB=230B)。可画出对应的程序虚拟空间如下图:
数据段中存的是初始化的数据、未初始化的数据和一些静态的数据
虽说,从刚学C开始,老师就经常讲这个,但是我们真的对他了解吗?
1.1 探索进程虚拟空间
我们首先用一段代码,来看一下进程创建后,父进程和子进程的进程虚拟空间是怎么样的,我们可以给一个全局变量,然后调用fork函数创建出子进程,在子进程中查看全局变量的地址,和父进程进行比较。
代码如下:
运行结果如下:
可以发现父子进程中g_val的值都为0,且地址相等
值都为0都可以理解,因为之前说过创建出的子进程会拷贝父进程大部分的PCB,其中包括了内存指针,内存指针指向的是进程的虚拟空间,因此子进程也拷贝了父进程的进程虚拟空间,所以g_val的值相等是可以理解的,但是为什么地址会相等呢?按理说,如果地址相等的话,在子进程中对g_val 进行修改的话,父进程中g_val的值也会改变,那创建出的子进程就没多大意义了。话不多说,我们实际验证一下。
1.2 疑惑验证
还是上面的代码,这次我们直接在子进程中修改g_val的值,看看父进程中会发生什么变化,按照之前来说的,地址一样,在子进程中对其进行修改,父进程也会进行相应的修改。
代码如下:
运行结果如下:
!!,奇怪奇怪,很是奇怪,地址是相同的,在子进程中进行了修改,但是父进程却没有随预想的那样发生改变,那为什么会这样呢?接着向下看。
关于进程的相关概念,可以看我之前写的详解进程的相关概念
2.分页式、分段式和段页式存储
在上面的探索中,我们发现父子进程,输出地址是一致的,但是变量内容不一样,由此可引出以下结论:
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
- 但地址值是一样的,说明,该地址绝对不是物理地址!
- 在Linux地址下,这种地址叫做 虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
因此,程序员在代码中看到的地址并不是真正的物理内存地址,而是OS内核虚拟出来的地址,虚拟地址并不能直接的保存数据,保存数据是在物理内存中进行保存。那么问题来了:进程是如何通过虚拟地址找到物理内存的地址呢?
这里就用到了页表,它是一种映射关系,OS用它来通过虚拟地址来找到物理内存中它所映射的那块空间地址。
此图也说明了程序在物理内存中的存储时离散的。
疑惑解答
- 子进程在被创建时,子进程会拷贝父进程的大量的PCB内容,其中就有内存指针,而内存指针对应着进程虚拟地址,进程虚拟地址中又保留有页表的信息,凭借着和父进程相同的页表,子进程可以访问到和父进程相同的值,由于虚拟地址都一样,因此,我们看到的打印出的父子进程的地址就是一样的,对应的物理地址其实也是一样的。
- 当然,这只是基于子进程只是单纯的访问,并不做修改的情况,如果当子进程中修改了某个值,那么OS会立即在物理内存中划分出一段空间,用来表示子进程的空间
不想看话语?直接看图
如图:
初始时:
子进程发生修改,修改为100
这种方式也被称为写时拷贝。
碎片知识:进程虚拟地址空间构成了进程的独立性,即一个进程修改数据,不会影响另一个进程
2.1 分页式存储
分页式存储:将虚拟地址空间分成了一页一页的小块,将物理地址分成了一块一块的小块,一块的大小是4096字节。
通过虚拟地址查找物理地址的方法:页号+页内偏移
- 页号:虚拟地址 / 块大小
- 页内偏移:虚拟地址 % 块大小
2.2 分段式存储
分段式:和分页式一样,只不过是将物理地址分成了一段一段的小段(这一个小段通常都是大于一个小块的)
通过虚拟地址查找物理地址的方法:段号+页内偏移
- 段号:虚拟地址 / 段大小
- 页内偏移:虚拟地址 % 段大小
2.3 段页式存储
段页式:将一个页表分为了两个,一个为页表,一个为段表,物理空间和分页式一样,被分为一块一块的
通过虚拟地址查找物理地址的方法:段号+页号+页内偏移
- 段号:虚拟地址 / 段大小
- 页号:虚拟地址 / 块大小
- 页内偏移:虚拟地址 % 块大小